!--------------------------------------------------------------------------------------------------!
!   CP2K: A general program to perform molecular dynamics simulations                              !
!   Copyright (C) 2000 - 2018  CP2K developers group                                               !
!--------------------------------------------------------------------------------------------------!

MODULE qs_fb_env_types

   USE kinds,                           ONLY: dp
   USE qs_fb_atomic_halo_types,         ONLY: fb_atomic_halo_list_associate,&
                                              fb_atomic_halo_list_has_data,&
                                              fb_atomic_halo_list_nullify,&
                                              fb_atomic_halo_list_obj,&
                                              fb_atomic_halo_list_release,&
                                              fb_atomic_halo_list_retain
   USE qs_fb_trial_fns_types,           ONLY: fb_trial_fns_associate,&
                                              fb_trial_fns_has_data,&
                                              fb_trial_fns_nullify,&
                                              fb_trial_fns_obj,&
                                              fb_trial_fns_release,&
                                              fb_trial_fns_retain
#include "./base/base_uses.f90"

   IMPLICIT NONE

   PRIVATE

! public types
   PUBLIC :: fb_env_obj

! public methods
   PUBLIC :: fb_env_release, &
             fb_env_nullify, &
             fb_env_has_data, &
             fb_env_create, &
             fb_env_get, &
             fb_env_set

   CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'qs_fb_env_types'
   INTEGER, PRIVATE, SAVE :: last_fb_env_id = 0

! **********************************************************************
!> \brief wrapper to the simulation parameters used for filtered basis
!>        method
!> \param rcut   : cutoff for included filtered basis set centred at
!>                 each atom. These defines the ranges of the atomic
!>                 halos. rcut(ikind) gives the range for atom of
!>                 global kind ikind
!> \param atomic_halos  : stores information on the neighbors of each
!>                        atom ii, which are defined by rcut
!> \param filter_temperature : parameter controlling the smoothness of
!>                             the filter function during the construction
!>                             of the filter matrix
!> \param auto_cutoff_scale  : scale multiplied to max atomic orbital
!>                             radii used for automatic construction of
!>                             rcut
!> \param eps_default        : anything less than it is regarded as zero
!> \param collective_com     : whether the MPI communications are
!>                             to be done collectively together
!>                             at the start and end of each
!>                             filter matrix calculation. This makes
!>                             communication more efficient in the
!>                             expense of larger memory usage
!> \param local_atoms        : atoms corresponding to the
!>                             atomic halos responsible by this processor
!> \param id_nr : unique id of this object
!> \param ref_count : reference counter of this object
!> \author Lianheng Tong (LT) lianheng.tong@kcl.ac.uk
! **********************************************************************
   TYPE fb_env_data
      INTEGER :: id_nr, ref_count
      REAL(KIND=dp), DIMENSION(:), POINTER :: rcut
      TYPE(fb_atomic_halo_list_obj) :: atomic_halos
      TYPE(fb_trial_fns_obj) :: trial_fns
      REAL(KIND=dp) :: filter_temperature
      REAL(KIND=dp) :: auto_cutoff_scale
      REAL(KIND=dp) :: eps_default
      LOGICAL :: collective_com
      INTEGER, DIMENSION(:), POINTER :: local_atoms
      INTEGER :: nlocal_atoms
   END TYPE fb_env_data

! **************************************************************************************************
!> \brief the object container which allows for the creation of an array of
!>        pointers to fb_env
!> \param obj : pointer to a filtered basis environment
!> \author Lianheng Tong (LT) lianheng.tong@kcl.ac.uk
! **************************************************************************************************
   TYPE fb_env_obj
      TYPE(fb_env_data), POINTER, PRIVATE :: obj
   END TYPE fb_env_obj

CONTAINS

! **********************************************************************
!> \brief retains the given fb_env
!> \param fb_env : the fb_env to retain
!> \author Lianheng Tong (LT) lianheng.tong@kcl.ac.uk
! **************************************************************************************************
   SUBROUTINE fb_env_retain(fb_env)
      TYPE(fb_env_obj), INTENT(IN)                       :: fb_env

      CHARACTER(len=*), PARAMETER :: routineN = 'fb_env_retain', routineP = moduleN//':'//routineN

      CPASSERT(ASSOCIATED(fb_env%obj))
      CPASSERT(fb_env%obj%ref_count > 0)
      fb_env%obj%ref_count = fb_env%obj%ref_count+1
   END SUBROUTINE fb_env_retain

! **********************************************************************
!> \brief releases a given fb_env
!> \brief ...
!> \param fb_env : the fb_env to release
!> \author Lianheng Tong (LT) lianheng.tong@kcl.ac.uk
! **************************************************************************************************
   SUBROUTINE fb_env_release(fb_env)
      TYPE(fb_env_obj), INTENT(INOUT)                    :: fb_env

      CHARACTER(len=*), PARAMETER :: routineN = 'fb_env_release', routineP = moduleN//':'//routineN

      IF (ASSOCIATED(fb_env%obj)) THEN
         CPASSERT(fb_env%obj%ref_count > 0)
         fb_env%obj%ref_count = fb_env%obj%ref_count-1
         IF (fb_env%obj%ref_count == 0) THEN
            fb_env%obj%ref_count = 1
            IF (ASSOCIATED(fb_env%obj%rcut)) THEN
               DEALLOCATE (fb_env%obj%rcut)
            END IF
            IF (ASSOCIATED(fb_env%obj%local_atoms)) THEN
               DEALLOCATE (fb_env%obj%local_atoms)
            END IF
            CALL fb_atomic_halo_list_release(fb_env%obj%atomic_halos)
            CALL fb_trial_fns_release(fb_env%obj%trial_fns)
            fb_env%obj%ref_count = 0
            DEALLOCATE (fb_env%obj)
         END IF
      ELSE
         NULLIFY (fb_env%obj)
      END IF
   END SUBROUTINE fb_env_release

! **********************************************************************
!> \brief nullifies a fb_env object, note that this does not
!>        release the original object. This procedure is used mainly
!>        to nullify the pointer inside the object which is used to
!>        point to the actual data content of the object.
!> \param fb_env : its content must be a NULL fb_env pointer on input,
!>                 and the output returns an empty fb_env object
!> \author Lianheng Tong (LT) lianheng.tong@kcl.ac.uk
! **************************************************************************************************
   SUBROUTINE fb_env_nullify(fb_env)
      TYPE(fb_env_obj), INTENT(INOUT)                    :: fb_env

      CHARACTER(len=*), PARAMETER :: routineN = 'fb_env_nullify', routineP = moduleN//':'//routineN

      NULLIFY (fb_env%obj)
   END SUBROUTINE fb_env_nullify

! **********************************************************************
!> \brief Associates one fb_env object to another
!> \param a the fb_env object to be associated
!> \param b the fb_env object that a is to be associated to
!> \author Lianheng Tong (LT) lianheng.tong@kcl.ac.uk
! **************************************************************************************************
   SUBROUTINE fb_env_associate(a, b)
      TYPE(fb_env_obj), INTENT(OUT)                      :: a
      TYPE(fb_env_obj), INTENT(IN)                       :: b

      CHARACTER(len=*), PARAMETER :: routineN = 'fb_env_associate', &
         routineP = moduleN//':'//routineN

      a%obj => b%obj
   END SUBROUTINE fb_env_associate

! **********************************************************************
!> \brief Checks if a fb_env object is associated with an actual
!>        data content or not
!> \param fb_env the fb_env object
!> \return ...
!> \author Lianheng Tong (LT) lianheng.tong@kcl.ac.uk
! **************************************************************************************************
   FUNCTION fb_env_has_data(fb_env) RESULT(res)
      TYPE(fb_env_obj), INTENT(IN)                       :: fb_env
      LOGICAL                                            :: res

      CHARACTER(len=*), PARAMETER :: routineN = 'fb_env_has_data', &
         routineP = moduleN//':'//routineN

      res = ASSOCIATED(fb_env%obj)
   END FUNCTION fb_env_has_data

! **********************************************************************
!> \brief creates an empty fb_env object
!> \param fb_env : its content must be a NULL fb_env pointer on input,
!>                 and the output returns an empty fb_env object
!> \author Lianheng Tong (LT) lianheng.tong@kcl.ac.uk
! **************************************************************************************************
   SUBROUTINE fb_env_create(fb_env)
      TYPE(fb_env_obj), INTENT(INOUT)                    :: fb_env

      CHARACTER(len=*), PARAMETER :: routineN = 'fb_env_create', routineP = moduleN//':'//routineN

      CPASSERT(.NOT. ASSOCIATED(fb_env%obj))
      ALLOCATE (fb_env%obj)
      NULLIFY (fb_env%obj%rcut)
      CALL fb_atomic_halo_list_nullify(fb_env%obj%atomic_halos)
      CALL fb_trial_fns_nullify(fb_env%obj%trial_fns)
      fb_env%obj%filter_temperature = 0.0_dp
      fb_env%obj%auto_cutoff_scale = 1.0_dp
      fb_env%obj%eps_default = 0.0_dp
      fb_env%obj%collective_com = .TRUE.
      NULLIFY (fb_env%obj%local_atoms)
      fb_env%obj%nlocal_atoms = 0
      fb_env%obj%ref_count = 1
      fb_env%obj%id_nr = last_fb_env_id+1
      last_fb_env_id = fb_env%obj%id_nr
   END SUBROUTINE fb_env_create

! **********************************************************************
!> \brief initialises a fb_env object to become empty
!> \brief ...
!> \param fb_env : the fb_env object, which must not be NULL or
!>                 UNDEFINED upon entry
!> \author Lianheng Tong (LT) lianheng.tong@kcl.ac.uk
! **************************************************************************************************
   SUBROUTINE fb_env_init(fb_env)
      TYPE(fb_env_obj), INTENT(INOUT)                    :: fb_env

      CHARACTER(len=*), PARAMETER :: routineN = 'fb_env_init', routineP = moduleN//':'//routineN

      CPASSERT(ASSOCIATED(fb_env%obj))
      IF (ASSOCIATED(fb_env%obj%rcut)) THEN
         DEALLOCATE (fb_env%obj%rcut)
      END IF
      CALL fb_atomic_halo_list_release(fb_env%obj%atomic_halos)
      CALL fb_trial_fns_release(fb_env%obj%trial_fns)
      fb_env%obj%filter_temperature = 0.0_dp
      fb_env%obj%auto_cutoff_scale = 1.0_dp
      fb_env%obj%eps_default = 0.0_dp
      fb_env%obj%collective_com = .TRUE.
      IF (ASSOCIATED(fb_env%obj%local_atoms)) THEN
         DEALLOCATE (fb_env%obj%local_atoms)
      END IF
      fb_env%obj%nlocal_atoms = 0
   END SUBROUTINE fb_env_init

! **********************************************************************
!> \brief method to get attributes from a given fb_env object
!> \brief ...
!> \param fb_env : the fb_env object in question
!> \param rcut   : outputs pointer to rcut attribute of fb_env (optional)
!> \param filter_temperature : outputs filter_temperature attribute
!>                             of fb_env (optional)
!> \param auto_cutoff_scale  : outputs auto_cutoff_scale attribute
!>                             of fb_env (optional)
!> \param eps_default        : outputs eps_default attribute
!>                             of fb_env (optional)
!> \param atomic_halos       : outputs pointer to atomic_halos
!>                             attribute of fb_env (optional)
!> \param trial_fns          : outputs pointer to trial_fns
!>                             attribute of fb_env (optional)
!> \param collective_com     : outputs pointer to trial_fns
!> \param local_atoms        : outputs pointer to local_atoms
!> \param nlocal_atoms       : outputs pointer to nlocal_atoms
!> \author Lianheng Tong (LT) lianheng.tong@kcl.ac.uk
! **************************************************************************************************
   SUBROUTINE fb_env_get(fb_env, &
                         rcut, &
                         filter_temperature, &
                         auto_cutoff_scale, &
                         eps_default, &
                         atomic_halos, &
                         trial_fns, &
                         collective_com, &
                         local_atoms, &
                         nlocal_atoms)
      TYPE(fb_env_obj), INTENT(IN)                       :: fb_env
      REAL(KIND=dp), DIMENSION(:), OPTIONAL, POINTER     :: rcut
      REAL(KIND=dp), INTENT(OUT), OPTIONAL               :: filter_temperature, auto_cutoff_scale, &
                                                            eps_default
      TYPE(fb_atomic_halo_list_obj), INTENT(OUT), &
         OPTIONAL                                        :: atomic_halos
      TYPE(fb_trial_fns_obj), INTENT(OUT), OPTIONAL      :: trial_fns
      LOGICAL, INTENT(OUT), OPTIONAL                     :: collective_com
      INTEGER, DIMENSION(:), OPTIONAL, POINTER           :: local_atoms
      INTEGER, INTENT(OUT), OPTIONAL                     :: nlocal_atoms

      CHARACTER(len=*), PARAMETER :: routineN = 'fb_env_get', routineP = moduleN//':'//routineN

      CPASSERT(ASSOCIATED(fb_env%obj))
      CPASSERT(fb_env%obj%ref_count > 0)
      IF (PRESENT(rcut)) &
         rcut => fb_env%obj%rcut
      IF (PRESENT(filter_temperature)) &
         filter_temperature = fb_env%obj%filter_temperature
      IF (PRESENT(auto_cutoff_scale)) &
         auto_cutoff_scale = fb_env%obj%auto_cutoff_scale
      IF (PRESENT(eps_default)) &
         eps_default = fb_env%obj%eps_default
      IF (PRESENT(atomic_halos)) &
         CALL fb_atomic_halo_list_associate(atomic_halos, fb_env%obj%atomic_halos)
      IF (PRESENT(trial_fns)) &
         CALL fb_trial_fns_associate(trial_fns, fb_env%obj%trial_fns)
      IF (PRESENT(collective_com)) &
         collective_com = fb_env%obj%collective_com
      IF (PRESENT(local_atoms)) &
         local_atoms => fb_env%obj%local_atoms
      IF (PRESENT(nlocal_atoms)) &
         nlocal_atoms = fb_env%obj%nlocal_atoms
   END SUBROUTINE fb_env_get

! **********************************************************************
!> \brief method to set attributes from a given fb_env object
!> \brief ...
!> \param fb_env : the fb_env object in question
!> \param rcut   : sets rcut attribute of fb_env (optional)
!> \param filter_temperature : sets filter_temperature attribute of fb_env (optional)
!> \param auto_cutoff_scale  : sets auto_cutoff_scale attribute of fb_env (optional)
!> \param eps_default        : sets eps_default attribute of fb_env (optional)
!> \param atomic_halos       : sets atomic_halos attribute of fb_env (optional)
!> \param trial_fns          : sets trial_fns attribute of fb_env (optional)
!> \param collective_com     : sets collective_com attribute of fb_env (optional)
!> \param local_atoms        : sets local_atoms attribute of fb_env (optional)
!> \param nlocal_atoms       : sets nlocal_atoms attribute of fb_env (optional)
!> \author Lianheng Tong (LT) lianheng.tong@kcl.ac.uk
! **************************************************************************************************
   SUBROUTINE fb_env_set(fb_env, &
                         rcut, &
                         filter_temperature, &
                         auto_cutoff_scale, &
                         eps_default, &
                         atomic_halos, &
                         trial_fns, &
                         collective_com, &
                         local_atoms, &
                         nlocal_atoms)
      TYPE(fb_env_obj), INTENT(INOUT)                    :: fb_env
      REAL(KIND=dp), DIMENSION(:), OPTIONAL, POINTER     :: rcut
      REAL(KIND=dp), INTENT(IN), OPTIONAL                :: filter_temperature, auto_cutoff_scale, &
                                                            eps_default
      TYPE(fb_atomic_halo_list_obj), INTENT(IN), &
         OPTIONAL                                        :: atomic_halos
      TYPE(fb_trial_fns_obj), INTENT(IN), OPTIONAL       :: trial_fns
      LOGICAL, INTENT(IN), OPTIONAL                      :: collective_com
      INTEGER, DIMENSION(:), OPTIONAL, POINTER           :: local_atoms
      INTEGER, INTENT(IN), OPTIONAL                      :: nlocal_atoms

      CHARACTER(len=*), PARAMETER :: routineN = 'fb_env_set', routineP = moduleN//':'//routineN

      CPASSERT(ASSOCIATED(fb_env%obj))
      IF (PRESENT(rcut)) THEN
         IF (ASSOCIATED(fb_env%obj%rcut)) THEN
            DEALLOCATE (fb_env%obj%rcut)
         END IF
         fb_env%obj%rcut => rcut
      END IF
      IF (PRESENT(filter_temperature)) &
         fb_env%obj%filter_temperature = filter_temperature
      IF (PRESENT(auto_cutoff_scale)) &
         fb_env%obj%auto_cutoff_scale = auto_cutoff_scale
      IF (PRESENT(eps_default)) &
         fb_env%obj%eps_default = eps_default
      IF (PRESENT(atomic_halos)) THEN
         IF (fb_atomic_halo_list_has_data(atomic_halos)) &
            CALL fb_atomic_halo_list_retain(atomic_halos)
         CALL fb_atomic_halo_list_release(fb_env%obj%atomic_halos)
         CALL fb_atomic_halo_list_associate(fb_env%obj%atomic_halos, atomic_halos)
      END IF
      IF (PRESENT(trial_fns)) THEN
         IF (fb_trial_fns_has_data(trial_fns)) &
            CALL fb_trial_fns_retain(trial_fns)
         CALL fb_trial_fns_release(fb_env%obj%trial_fns)
         CALL fb_trial_fns_associate(fb_env%obj%trial_fns, trial_fns)
      END IF
      IF (PRESENT(collective_com)) &
         fb_env%obj%collective_com = collective_com
      IF (PRESENT(local_atoms)) THEN
         IF (ASSOCIATED(fb_env%obj%local_atoms)) THEN
            DEALLOCATE (fb_env%obj%local_atoms)
         END IF
         fb_env%obj%local_atoms => local_atoms
      END IF
      IF (PRESENT(nlocal_atoms)) &
         fb_env%obj%nlocal_atoms = nlocal_atoms
   END SUBROUTINE fb_env_set

END MODULE qs_fb_env_types
