!
! Example usage of Fortran 2003/2008 classes (extended derived types) as
! user-defined contexts in PETSc. Example contributed by Glenn Hammond.
!
module Base_module
#include "petsc/finclude/petscsnes.h"
      implicit none
  private

  type, public :: base_type
    PetscInt :: A  ! junk
    PetscReal :: I ! junk
  contains
    procedure, public :: Print => BasePrint
  end type base_type
contains
subroutine BasePrint(this)
  implicit none
  class(base_type) :: this
  print *
  print *, 'Base printout'
  print *
end subroutine BasePrint
end module Base_module

module Extended_module
  use Base_module
  implicit none
  private
  type, public, extends(base_type) :: extended_type
    PetscInt :: B  ! junk
    PetscReal :: J ! junk
  contains
    procedure, public :: Print =>  ExtendedPrint
  end type extended_type
contains
subroutine ExtendedPrint(this)
  implicit none
  class(extended_type) :: this
  print *
  print *, 'Extended printout'
  print *
end subroutine ExtendedPrint
end module Extended_module

module Function_module
  use petscsnes
  implicit none
  public :: TestFunction
  contains
subroutine TestFunction(snes,xx,r,ctx,ierr)
  use Base_module
  implicit none
  SNES :: snes
  Vec :: xx
  Vec :: r
  class(base_type) :: ctx ! yes, this should be base_type in order to handle all
  PetscErrorCode :: ierr  ! polymorphic extensions
  call ctx%Print()
end subroutine TestFunction
end module Function_module

program ex18f90

  use Base_module
  use Extended_module
  use Function_module
  implicit none

! ifort on windows requires this interface definition
interface
  subroutine SNESSetFunction(snes_base,x,TestFunction,base,ierr)
    use Base_module
    use petscsnes
    SNES snes_base
    Vec x
    external TestFunction
    class(base_type) :: base
    PetscErrorCode ierr
  end subroutine
end interface

  PetscMPIInt :: size
  PetscMPIInt :: rank

  SNES :: snes_base, snes_extended
  Vec :: x
  class(base_type), pointer :: base
  class(extended_type), pointer :: extended
  PetscErrorCode :: ierr

  print *, 'Start of Fortran2003 test program'

  nullify(base)
  nullify(extended)
  allocate(base)
  allocate(extended)
  PetscCallA(PetscInitialize(ierr))
  PetscCallMPIA(MPI_Comm_size(PETSC_COMM_WORLD,size,ierr))
  PetscCallMPIA(MPI_Comm_rank(PETSC_COMM_WORLD,rank,ierr))

  PetscCallA(VecCreate(PETSC_COMM_WORLD,x,ierr))

  ! use the base class as the context
  print *
  print *, 'the base class will succeed by printing out Base printout below'
  PetscCallA(SNESCreate(PETSC_COMM_WORLD,snes_base,ierr))
  PetscCallA(SNESSetFunction(snes_base,x,TestFunction,base,ierr))
  PetscCallA(SNESComputeFunction(snes_base,x,x,ierr))
  PetscCallA(SNESDestroy(snes_base,ierr))

  ! use the extended class as the context
  print *, 'the extended class will succeed by printing out Extended printout below'
  PetscCallA(SNESCreate(PETSC_COMM_WORLD,snes_extended,ierr))
  PetscCallA(SNESSetFunction(snes_extended,x,TestFunction,extended,ierr))
  PetscCallA(SNESComputeFunction(snes_extended,x,x,ierr))
  PetscCallA(VecDestroy(x,ierr))
  PetscCallA(SNESDestroy(snes_extended,ierr))
  if (associated(base)) deallocate(base)
  if (associated(extended)) deallocate(extended)
  PetscCallA(PetscFinalize(ierr))

  print *, 'End of Fortran2003 test program'
end program ex18f90

!/*TEST
!
!   build:
!      requires: defined(PETSC_USING_F2003) defined(PETSC_USING_F90FREEFORM)
!   test:
!     requires: !pgf90_compiler
!
!TEST*/
