!----------------------------------------------------------------------------
!BOP
!  
! !MODULE:  GSI_EnsCouplerMod ---
!
! !INTERFACE:

module GSI_EnsCouplerMod

! !USES:

use abstract_ensmod, only: abstractEnsemble
use gsi_bundlemod, only: gsi_bundle
use mpeu_util, only: tell,warn
implicit none
private

! !PUBLIC MEMBER FUNCTIONS:

  public GSI_EnsCoupler_localization_grid
  public GSI_EnsCoupler_get_user_ens
  public GSI_EnsCoupler_get_user_Nens
  public GSI_EnsCoupler_put_gsi_ens
  public GSI_EnsCoupler_registry
  public GSI_EnsCoupler_name
  public GSI_EnsCoupler_create_sub2grid_info
  public GSI_EnsCoupler_destroy_sub2grid_info

! !INTERFACES:
  interface GSI_EnsCoupler_localization_grid;  module procedure non_gaussian_ens_grid_;  end interface
  interface GSI_EnsCoupler_get_user_ens;       module procedure get_user_ens_;           end interface
  interface GSI_EnsCoupler_get_user_Nens;      module procedure get_user_Nens_;          end interface
  interface GSI_EnsCoupler_put_gsi_ens;        module procedure put_user_ens_;           end interface

  interface GSI_EnsCoupler_registry ; module procedure typedef_ ; end interface
  interface GSI_EnsCoupler_name;      module procedure typename_; end interface
  interface GSI_EnsCoupler_create_sub2grid_info ; module procedure  create_s2gi; end interface
  interface GSI_EnsCoupler_destroy_sub2grid_info; module procedure destroy_s2gi; end interface

! !CLASSES:

  class(abstractEnsemble),allocatable,target,save:: typemold_
  class(abstractEnsemble),allocatable,target,save:: this_ensemble_

! This flag controls internal debugging messages.
   logical,parameter:: verbose=.false.
  !logical,parameter:: verbose=.true.

  character(len=*),parameter:: myname='GSI_EnsCouplerMod'
contains

subroutine typedef_(mold)
!-- A high-level interface type-define the concrete multi-ensemble to use.

  use stub_ensmod, only: stub_ensemble => ensemble
  implicit none
  class(abstractEnsemble),optional,target,intent(in):: mold

  character(len=*),parameter:: myname_=myname//'::typedef_'
  class(abstractEnsemble),pointer:: pmold_

        ! argument checking
  pmold_ => null()
  if(present(mold)) then
    pmold_ => mold
    if(.not.associated(pmold_)) &               ! is argument _mold_ a null-object?
      call warn(myname_,'a null argument (mold) is given.  Will typedef to default')
  endif

        ! reset current typemold
  if(allocated(typemold_)) then
    if(verbose) call tell(myname_,'deallocating, typemold_%mytype() = '//typemold_%mytype())
    deallocate(typemold_)
  endif

        ! (re)allocate the new typemold_
  if(associated(pmold_)) then
    allocate(typemold_,mold=pmold_)
    pmold_ => null()
  else
    allocate(stub_ensemble::typemold_)
  endif
  if(verbose) call tell(myname_,'allocated, typemold_%mytype() = '//typemold_%mytype())
end subroutine typedef_

function typename_() result(name)
!-- Return the name of the current concrete multi-ensemble type.

  use abstract_ensmod, only: abstractEnsemble_typename
  implicit none
  character(len=:),allocatable:: name   ! return the type name
  name=abstractEnsemble_typename()
  if(allocated(typemold_)) name=typemold_%mytype()
        ! Note the use of typemold_, instead of this_ensemble_.
end function typename_

   subroutine get_user_ens_(grd,member,ntindex,atm_bundle,iret)
   use kinds, only: i_kind,r_kind
   use gsi_bundlemod, only: gsi_bundle
   use general_sub2grid_mod, only: sub2grid_info
   implicit none
!  Declare passed variables
      type(sub2grid_info)                   ,intent(in   ) :: grd
      integer(i_kind)                       ,intent(in   ) :: member
      integer(i_kind)                       ,intent(in   ) :: ntindex
      type(gsi_bundle)                      ,intent(inout) :: atm_bundle
      integer(i_kind)                       ,intent(  out) :: iret
      call ifn_alloc_()     ! to ensure an allocated(this_ensemble_)
      call this_ensemble_%get_user_ens(grd,member,ntindex,atm_bundle,iret)
   end subroutine get_user_ens_

   subroutine get_user_Nens_(grd,members,ntindex,atm_bundle,iret)
   use kinds, only: i_kind,r_kind
   use gsi_bundlemod, only: gsi_bundle
   use general_sub2grid_mod, only: sub2grid_info
   implicit none
!  Declare passed variables
      type(sub2grid_info)                   ,intent(in   ) :: grd
      integer(i_kind)                       ,intent(in   ) :: members
      integer(i_kind)                       ,intent(in   ) :: ntindex
      type(gsi_bundle)                      ,intent(inout) :: atm_bundle(:)
      integer(i_kind)                       ,intent(  out) :: iret
      call ifn_alloc_()     ! to ensure an allocated(this_ensemble_)
      call this_ensemble_%get_user_Nens(grd,members,ntindex,atm_bundle,iret)
   end subroutine get_user_Nens_

   subroutine put_user_ens_(grd,member,ntindex,pert,iret)
   use kinds, only: i_kind,r_kind
   use general_sub2grid_mod, only: sub2grid_info
   use gsi_bundlemod, only: gsi_bundle
   implicit none
!  Declare passed variables
      type(sub2grid_info),intent(in   ) :: grd
      integer(i_kind),    intent(in   ) :: member
      integer(i_kind),    intent(in   ) :: ntindex
      type(gsi_bundle),   intent(inout) :: pert
      integer(i_kind),    intent(  out) :: iret
      call ifn_alloc_()     ! to ensure an allocated(this_ensemble_)
      call this_ensemble_%put_user_ens(grd,member,ntindex,pert,iret)
   end subroutine put_user_ens_

   subroutine non_gaussian_ens_grid_ (elats,elons)
   use kinds, only: i_kind,r_kind
   implicit none
   real(r_kind),intent(out) :: elats(:),elons(:)
   call ifn_alloc_()     ! to ensure an allocated(this_ensemble_)
   call this_ensemble_%non_gaussian_ens_grid(elats,elons)
   end subroutine non_gaussian_ens_grid_

   subroutine ifn_alloc_()
!-- If-not-properly-allocated(this_ensemble_), do something
   implicit none
   class(abstractEnsemble),pointer:: pmold_

    ! First, check to make sure typemold_ is type-defined, at least to a
    ! default multi-ensemble type.
   pmold_ => typemold_
   if(.not.associated(pmold_)) call typedef_()
   pmold_ => null()

    ! Then, check and possibly instantiate this_ensemble_, which is must be
    ! typed the same as typemold_

   if(allocated(this_ensemble_)) then
     if(same_type_as(typemold_,this_ensemble_)) return      ! Everything seems good.
 
       ! Otherwise, this_ensemble_ must be re-intentiated with a different type.
 
     deallocate(this_ensemble_)
   endif
   allocate(this_ensemble_,mold=typemold_)
  end subroutine ifn_alloc_

! !DESCRIPTION: This module provides general interface for
!               ensemble capability
!
! !REVISION HISTORY:
!
!  19Sep2011 Todling - Initial code
!  30Nov2014 Todling - Update interface to get (bundle passed in)
!  28Jun2018 Todling - Revamp in light of truly abstract ensemble interface
!
!EOP
!-------------------------------------------------------------------------

subroutine create_s2gi(s2gi, nsig, npe, s2gi_ref)
   use kinds, only: i_kind
   use general_sub2grid_mod, only: sub2grid_info
   implicit none
!  Declare passed variables
   type(sub2grid_info),intent(  out) :: s2gi
   integer(i_kind),    intent(in   ) :: nsig
   integer(i_kind),    intent(in   ) :: npe
   type(sub2grid_info),intent(in   ) :: s2gi_ref

   call ifn_alloc_()            ! to ensure an allocated(this_ensemble_)
   call this_ensemble_%create_sub2grid_info(s2gi, nsig,npe, s2gi_ref)
return
end subroutine create_s2gi
subroutine destroy_s2gi(s2gi)
   use general_sub2grid_mod, only: sub2grid_info
   implicit none
!  Declare passed variables
   type(sub2grid_info),intent(inout) :: s2gi

   call ifn_alloc_()            ! to ensure an allocated(this_ensemble_)
   call this_ensemble_%destroy_sub2grid_info(s2gi)
return
end subroutine destroy_s2gi

end module GSI_EnsCouplerMod