MODULE physics_interface_mod

  USE prec

  PRIVATE

  TYPE t_physics_inout
     ! Input, time-independent
     INTEGER :: ngrid, it
     REAL(rstd) :: dt_phys
     REAL(rstd), DIMENSION(:), ALLOCATABLE :: Ai, lon, lat, phis
     ! Input, time-dependent
     REAL(rstd), DIMENSION(:,:), ALLOCATABLE :: geopot, p, pk, Temp, ulon, ulat
     REAL(rstd), DIMENSION(:,:,:), ALLOCATABLE :: q
     ! Output arrays
     REAL(rstd), DIMENSION(:,:), ALLOCATABLE :: dTemp, dulon, dulat
     REAL(rstd), DIMENSION(:,:,:), ALLOCATABLE :: dq
  END TYPE t_physics_inout

! physics_inout is used to exchange information with physics
! Field ngrid is initialized by physics.f90/init_physics. Its other fields
! must be defined by XX/init_physics (where XX =  e.g. physics_dcmip.f90) 
! by either pointing to internal data of the physics package
! or by a specific allocation
! size : (ngrid), (ngrid,llm) except p(ngrid,llm+1), (ngrid,llm,nqtot) 

  TYPE(t_physics_inout), SAVE :: physics_inout
!$OMP THREADPRIVATE(physics_inout) 
 
! pack_info contains indices used by pack/unpack routines
! to pack together the data of all the domains managed by the MPI process
! It is initialized by physics.f90/init_physics 

  TYPE t_pack_info
     INTEGER :: ngrid, & ! number of non-halo points in that domain
          nseg ! number of segments (contigous parts) in that domain
     ! size and start of each segment : ij domain index, k packed index
     INTEGER, ALLOCATABLE :: n(:), ij(:), k(:)
  END TYPE t_pack_info

  TYPE(t_pack_info), ALLOCATABLE, SAVE :: pack_info(:)
!$OMP THREADPRIVATE(pack_info)


  INTERFACE pack_field
     MODULE PROCEDURE pack_2D
     MODULE PROCEDURE pack_3D
     MODULE PROCEDURE pack_4D
  END INTERFACE pack_field

  INTERFACE unpack_field
     MODULE PROCEDURE unpack_2D
     MODULE PROCEDURE unpack_3D
     MODULE PROCEDURE unpack_4D
  END INTERFACE unpack_field

  INTERFACE pack_domain
     MODULE PROCEDURE pack_domain_2D
     MODULE PROCEDURE pack_domain_3D
     MODULE PROCEDURE pack_domain_4D
  END INTERFACE pack_domain

  INTERFACE unpack_domain
     MODULE PROCEDURE unpack_domain_2D
     MODULE PROCEDURE unpack_domain_3D
     MODULE PROCEDURE unpack_domain_4D
  END INTERFACE unpack_domain

  PUBLIC :: nb_extra_physics_2D, nb_extra_physics_3D, &
       t_physics_inout, physics_inout, &
       t_pack_info, pack_info, init_pack_before, init_pack_after, &
       pack_domain, pack_field, unpack_domain, unpack_field, &
       garbage_3D

CONTAINS
  
    SUBROUTINE init_pack_before
    USE icosa
    IMPLICIT NONE
    INTEGER :: ind, offset, ngrid

    offset=0
    ALLOCATE(pack_info(ndomain))
    DO ind=1,ndomain
       IF (.NOT. assigned_domain(ind)) CYCLE
       CALL swap_dimensions(ind)
       CALL swap_geometry(ind)
       CALL count_segments(domain(ind)%own, pack_info(ind))
       pack_info(ind)%k = pack_info(ind)%k + offset
       offset = offset + pack_info(ind)%ngrid
    END DO
    physics_inout%ngrid = offset

    ngrid=offset
    ! Input
    ALLOCATE(physics_inout%Ai(ngrid))
    ALLOCATE(physics_inout%lon(ngrid))
    ALLOCATE(physics_inout%lat(ngrid))
    ALLOCATE(physics_inout%phis(ngrid))
    ALLOCATE(physics_inout%p(ngrid,llm+1))
    ALLOCATE(physics_inout%geopot(ngrid,llm+1))
    ALLOCATE(physics_inout%pk(ngrid,llm))
    ALLOCATE(physics_inout%Temp(ngrid,llm))
    ALLOCATE(physics_inout%ulon(ngrid,llm))
    ALLOCATE(physics_inout%ulat(ngrid,llm))
    ALLOCATE(physics_inout%q(ngrid,llm,nqtot))
    ! Output (tendencies)
    ALLOCATE(physics_inout%dTemp(ngrid,llm))
    ALLOCATE(physics_inout%dulon(ngrid,llm))
    ALLOCATE(physics_inout%dulat(ngrid,llm))
    ALLOCATE(physics_inout%dq(ngrid,llm,nqtot))

  END SUBROUTINE init_pack_before

  SUBROUTINE count_segments(own, info)
    USE icosa
    IMPLICIT NONE
    LOGICAL, DIMENSION(:,:) :: own
    TYPE(t_pack_info) :: info

    INTEGER, DIMENSION(jjm) :: n
    INTEGER :: ngrid, nseg, i, j, jj, k
    INTEGER, PARAMETER :: method=4
    SELECT CASE(method)
    CASE(1) ! Copy all points, including halo (works)
       info%nseg=1
       info%ngrid=iim*jjm
       ALLOCATE(info%n(1))
       ALLOCATE(info%ij(1))
       ALLOCATE(info%k(1))
       info%n(1)=iim*jjm
       info%ij(1)=1
       info%k(1)=1
    CASE(2) ! Copy all points, including halo, one at a time (works, slow ?)
       info%nseg=iim*jjm
       info%ngrid=iim*jjm
       ALLOCATE(info%n(iim*jjm))
       ALLOCATE(info%ij(iim*jjm))
       ALLOCATE(info%k(iim*jjm))
       DO jj=1,iim*jjm
          info%n(jj) =1
          info%ij(jj)=jj
          info%k(jj) =jj
       END DO
    CASE(3) ! Copy non-halo points only, one at a time (works, slow ?)
       n=0
       n(jj_begin:jj_end)=COUNT(own(ii_begin:ii_end,jj_begin:jj_end),1)
       ngrid=SUM(n)
       info%ngrid=ngrid
       info%nseg=ngrid
       ALLOCATE(info%n(ngrid))
       ALLOCATE(info%ij(ngrid))
       ALLOCATE(info%k(ngrid))
       jj=1
       DO j=1,jjm
          DO i=1,iim
             IF(own(i,j)) THEN
                info%n(jj)=1
                info%k(jj)=jj
                info%ij(jj) = iim*(j-1)+i
                jj=jj+1
             END IF
          END DO
       END DO
       
    CASE DEFAULT ! Copy non-halo points only, as contiguous segments (works)
       n(:)=0
       n(jj_begin:jj_end)=COUNT(own(ii_begin:ii_end,jj_begin:jj_end),1)
       ngrid=SUM(n)
       info%ngrid=ngrid
       nseg=COUNT(n>0)
       info%nseg=nseg
       ALLOCATE(info%n(nseg))
       ALLOCATE(info%ij(nseg))
       ALLOCATE(info%k(nseg))
       info%n(:)=0
       info%ij(:)=0
       info%k(:)=0
       
       jj=1
       k=1
       DO j=jj_begin,jj_end
          IF(n(j)>0) THEN
             ! find first .TRUE. value in own(:,j)
             DO i=ii_begin,ii_end
                IF(own(i,j)) THEN
                   info%n(jj)=n(j)              ! size of segment
                   info%k(jj)=k                 ! start index in packed array
                   info%ij(jj) = iim*(j-1)+i    ! start index in unpacked array
                   IF(COUNT(own(i:i+n(j)-1,j)) /= n(j)) THEN
                      PRINT *, 'count_segments :', n(j), own(:,j)
                      STOP 'discrepancy detected in count_segments'
                   END IF
                   EXIT
                END IF
             END DO
             k = k + n(j)
             jj=jj+1
          END IF
       END DO

       IF(k-1/=ngrid) THEN
          PRINT *, 'Total number of grid points inconsistent', k-1, ngrid
          STOP
       END IF
       IF(jj-1/=nseg) THEN
          PRINT *, 'Number of segments inconsistent', jj-1, nseg
          STOP
       END IF

    END SELECT
    
    PRINT *, 'count_segments', info%nseg, info%ngrid, SUM(info%n), COUNT(own), iim*jjm
  END SUBROUTINE count_segments

  SUBROUTINE init_pack_after
    USE icosa
    IMPLICIT NONE
    INTEGER :: ind, offset
    DO ind=1,ndomain
       IF (.NOT. assigned_domain(ind)) CYCLE
       CALL swap_dimensions(ind)
       CALL swap_geometry(ind)
       CALL pack_domain_2D(pack_info(ind), Ai, physics_inout%Ai)
       CALL pack_domain_2D(pack_info(ind), lon_i, physics_inout%lon)
       CALL pack_domain_2D(pack_info(ind), lat_i, physics_inout%lat)
    END DO
  END SUBROUTINE init_pack_after

!-------------------------------- Pack / Unpack 2D ---------------------------

  SUBROUTINE pack_2D(f_2D, packed)
    USE icosa
    IMPLICIT NONE
    TYPE(t_field),POINTER :: f_2D(:)
    REAL(rstd)            :: packed(:)
    REAL(rstd), POINTER   :: loc(:)
    INTEGER :: ind
    DO ind=1,ndomain
       IF (.NOT. assigned_domain(ind)) CYCLE
       loc = f_2D(ind)
       CALL pack_domain_2D(pack_info(ind), loc, packed)
    END DO
  END SUBROUTINE pack_2D

  SUBROUTINE unpack_2D(f_2D, packed) 
    USE icosa
    IMPLICIT NONE
    TYPE(t_field),POINTER :: f_2D(:)
    REAL(rstd)            :: packed(:)
    REAL(rstd), POINTER   :: loc(:)
    INTEGER :: ind
    DO ind=1,ndomain
       IF (.NOT. assigned_domain(ind)) CYCLE
       loc = f_2D(ind)
       CALL unpack_domain_2D(pack_info(ind), loc, packed)
    END DO
  END SUBROUTINE unpack_2D

  SUBROUTINE pack_domain_2D(info, loc, glob)
    USE icosa
    IMPLICIT NONE
    TYPE(t_pack_info) :: info
    REAL(rstd), DIMENSION(:) :: loc, glob
    INTEGER :: jj,n,k,ij
    DO jj=1, info%nseg
       n = info%n(jj)-1
       ij = info%ij(jj)
       k = info%k(jj)
       glob(k:k+n) = loc(ij:ij+n)
    END DO
  END SUBROUTINE pack_domain_2D

  SUBROUTINE unpack_domain_2D(info, loc, glob)
    IMPLICIT NONE
    TYPE(t_pack_info) :: info
    REAL(rstd), DIMENSION(:) :: loc, glob
    INTEGER :: jj,n,k,ij
    DO jj=1, info%nseg
       n = info%n(jj)-1
       ij = info%ij(jj)
       k = info%k(jj)
       loc(ij:ij+n) = glob(k:k+n)
    END DO
  END SUBROUTINE unpack_domain_2D

!-------------------------------- Pack / Unpack 3D ---------------------------

  SUBROUTINE pack_3D(f_3D, packed)
    USE icosa
    IMPLICIT NONE
    TYPE(t_field),POINTER :: f_3D(:)
    REAL(rstd)            :: packed(:,:)
    REAL(rstd), POINTER   :: loc(:,:)
    INTEGER :: ind
    DO ind=1,ndomain
       IF (.NOT. assigned_domain(ind)) CYCLE
       loc = f_3D(ind)
       CALL pack_domain_3D(pack_info(ind), loc, packed)
    END DO
  END SUBROUTINE pack_3D

  SUBROUTINE unpack_3D(f_3D, packed) 
    USE icosa
    IMPLICIT NONE
    TYPE(t_field),POINTER :: f_3D(:)
    REAL(rstd)            :: packed(:,:)
    REAL(rstd), POINTER   :: loc(:,:)
    INTEGER :: ind
    DO ind=1,ndomain
       IF (.NOT. assigned_domain(ind)) CYCLE
       loc = f_3D(ind)
       CALL unpack_domain_3D(pack_info(ind), loc, packed)
    END DO
  END SUBROUTINE unpack_3D

  SUBROUTINE pack_domain_3D(info, loc, glob)
    IMPLICIT NONE
    TYPE(t_pack_info) :: info
    REAL(rstd), DIMENSION(:,:) :: loc, glob
    INTEGER :: jj,n,k,ij
    DO jj=1, info%nseg
       n = info%n(jj)-1
       ij = info%ij(jj)
       k = info%k(jj)
       glob(k:k+n,:) = loc(ij:ij+n,:)
    END DO
  END SUBROUTINE pack_domain_3D

  SUBROUTINE unpack_domain_3D(info, loc, glob)
    IMPLICIT NONE
    TYPE(t_pack_info) :: info
    REAL(rstd), DIMENSION(:,:) :: loc, glob
    INTEGER :: jj,n,k,ij
    DO jj=1, info%nseg
       n = info%n(jj)-1
       ij = info%ij(jj)
       k = info%k(jj)
       loc(ij:ij+n,:) = glob(k:k+n,:)
    END DO    
  END SUBROUTINE unpack_domain_3D

  SUBROUTINE garbage_3D(loc,own)
    USE icosa
    IMPLICIT NONE
    LOGICAL :: own(iim,jjm)
    REAL(rstd) :: loc(iim*jjm,llm)
    INTEGER :: i,j,ij
    ! write garbage in non-owned points
    DO j=1,jjm
       DO i=1,iim
          IF(.NOT.own(i,j)) THEN
             ij=iim*(j-1)+i
             loc(ij,:)=-1e30
          END IF
       END DO
    END DO   
  END SUBROUTINE garbage_3D

!-------------------------------- Pack / Unpack 4D ---------------------------

  SUBROUTINE pack_4D(f_4D, packed)
    USE icosa
    IMPLICIT NONE
    TYPE(t_field),POINTER :: f_4D(:)
    REAL(rstd)            :: packed(:,:,:)
    REAL(rstd), POINTER   :: loc(:,:,:)
    INTEGER :: ind
    DO ind=1,ndomain
       IF (.NOT. assigned_domain(ind)) CYCLE
       loc = f_4D(ind)
       CALL pack_domain_4D(pack_info(ind), loc, packed)
    END DO
  END SUBROUTINE pack_4D

  SUBROUTINE unpack_4D(f_4D, packed) 
    USE icosa
    IMPLICIT NONE
    TYPE(t_field),POINTER :: f_4D(:)
    REAL(rstd)            :: packed(:,:,:)
    REAL(rstd), POINTER   :: loc(:,:,:)
    INTEGER :: ind
    DO ind=1,ndomain
       IF (.NOT. assigned_domain(ind)) CYCLE
       loc = f_4D(ind)
       CALL unpack_domain_4D(pack_info(ind), loc, packed)
    END DO
  END SUBROUTINE unpack_4D

  SUBROUTINE pack_domain_4D(info, loc, glob)
    IMPLICIT NONE
    TYPE(t_pack_info) :: info
    REAL(rstd), DIMENSION(:,:,:) :: loc, glob
    INTEGER :: jj,n,k,ij
    DO jj=1, info%nseg
       n = info%n(jj)-1
       ij = info%ij(jj)
       k = info%k(jj)
       glob(k:k+n,:,:) = loc(ij:ij+n,:,:)
    END DO
  END SUBROUTINE pack_domain_4D

  SUBROUTINE unpack_domain_4D(info, loc, glob)
    IMPLICIT NONE
    TYPE(t_pack_info) :: info
    REAL(rstd), DIMENSION(:,:,:) :: loc, glob
    INTEGER :: jj,n,k,ij
    DO jj=1, info%nseg
       n = info%n(jj)-1
       ij = info%ij(jj)
       k = info%k(jj)
       loc(ij:ij+n,:,:) = glob(k:k+n,:,:)
    END DO
  END SUBROUTINE unpack_domain_4D

END MODULE physics_interface_mod
