module inidat
  !----------------------------------------------------------------------- 
  ! 
  ! Purpose: Read initial dataset and spectrally truncate as appropriate.
  !
  ! Method: Initialize one or a few fields at a time, to minimize the 
  !         memory  requirements
  ! 
  ! Author: 
  ! Modified: P. Worley, to implement initialization of subsets
  !           of fields. (8/03)
  !
  !           A. Gettelman and C. Craig (Nov 2010) - put micro/macro physics 
  !           into separate routines
  ! 
  !-----------------------------------------------------------------------
  use cam_logfile, only : iulog
  use element_mod, only : element_t
  use shr_kind_mod, only: r8 => shr_kind_r8
  use spmd_utils,   only: iam, masterproc
  use cam_control_mod, only : ideal_phys, aqua_planet, pertlim
  implicit none
  private
  public read_inidat

contains



  subroutine read_inidat( ncid_ini, ncid_topo, dyn_in)
    use dyn_comp,      only: dyn_import_t
    use parallel_mod,     only: par
    use bndry_mod,     only: bndry_exchangev
    use constituents, only: cnst_name, cnst_read_iv, qmin
    use dimensions_mod,     only: nelemd, nlev, np
    use dof_mod, only           : putUniquePoints
    use edge_mod, only : edgevpack, edgevunpack, InitEdgeBuffer, FreeEdgeBuffer, EdgeBuffer_t
    use ncdio_atm, only : infld
    use shr_vmath_mod, only: shr_vmath_log
    use hycoef,           only: ps0
    use abortutils,     only: endrun
    use pio, only : file_desc_t, io_desc_t, pio_double, pio_get_local_array_size, pio_freedecomp
    use dyn_grid, only : get_horiz_grid_dim_d
    use chemistry   , only: chem_implements_cnst, chem_init_cnst
    use aerosol_intr, only: aerosol_implements_cnst, aerosol_init_cnst
    use carma_intr,   only: carma_implements_cnst, carma_init_cnst
    use tracers     , only: tracers_implements_cnst, tracers_init_cnst
    use aoa_tracers , only: aoa_tracers_implements_cnst, aoa_tracers_init_cnst
    use stratiform,   only: stratiform_implements_cnst, stratiform_init_cnst
    use microp_driver, only: microp_driver_implements_cnst, microp_driver_init_cnst
    use phys_control,  only: phys_getopts
    use co2_cycle   , only: co2_implements_cnst, co2_init_cnst
    use nctopo_util_mod, only: nctopo_util_inidat
    implicit none
    type(file_desc_t),intent(inout) :: ncid_ini, ncid_topo
    type (dyn_import_t), target, intent(out)   :: dyn_in   ! dynamics import

    type(element_t), pointer :: elem(:)
    real(r8), allocatable :: tmp(:,:)
    integer, allocatable :: gcols(:)
    integer :: tlncols, ig, ie, start, j, t, k
    character(len=40) :: fieldname
    logical :: found
    integer :: kptr, m_cnst
    type(EdgeBuffer_t) :: edge
    type(io_desc_t) :: iodesc
    integer :: lsize

    integer,parameter :: pcnst = PCNST
    integer, pointer :: gcid(:)

    integer :: rndm_seed_sz
    integer, allocatable :: rndm_seed(:)
    real(r8) :: pertval
    integer :: i
    real(r8), parameter :: D0_0 = 0.0_r8
    real(r8), parameter :: D0_5 = 0.5_r8
    real(r8), parameter :: D1_0 = 1.0_r8
    real(r8), parameter :: D2_0 = 2.0_r8
    character*16 :: subname='READ_INIDAT'

! Get microphysics option

    character(len=16) :: microp_scheme 
    call phys_getopts( microp_scheme_out = microp_scheme )
    if(iam < par%nprocs) then
       elem=> dyn_in%elem
    else
       nullify(elem)
    end if

    call get_dyn_decomp(elem, nlev, pio_double, iodesc)

    lsize = pio_get_local_array_size(iodesc)	

    tlncols = lsize/nlev

    allocate(tmp(tlncols,nlev))    

    if (iam < par%nprocs) then
      if(elem(1)%idxP%NumUniquePts <=0 .or. elem(1)%idxP%NumUniquePts > np*np) then
         write(iulog,*)  elem(1)%idxP%NumUniquePts
         call endrun('inidat')
      end if
    end if

    fieldname = 'U'
    call infld(fieldname, ncid_ini, iodesc, tlncols,'lev',  tmp, found)
    if(.not. found) then
       call endrun('Could not find U field on input datafile')
    end if
    
    start=1
    do ie=1,nelemd
       elem(ie)%state%v=0.0_r8
       call putUniquePoints(elem(ie)%idxP, nlev, tmp(start:,:), &
            elem(ie)%state%v(:,:,1,:,1))
       start=start+elem(ie)%idxP%numUniquePts
    end do

    fieldname = 'V'
    call infld(fieldname, ncid_ini, iodesc, tlncols,'lev',  tmp, found)
    if(.not. found) then
       call endrun('Could not find V field on input datafile')
    end if
    start=1
    do ie=1,nelemd
       call putUniquePoints(elem(ie)%idxP, nlev, tmp(start:,:), &
            elem(ie)%state%v(:,:,2,:,1))
       start=start+elem(ie)%idxP%numUniquePts
    end do

    fieldname = 'T'
    call infld(fieldname, ncid_ini, iodesc, tlncols ,'lev',  tmp, found)
    if(.not. found) then
       call endrun('Could not find T field on input datafile')
    end if
    start=1
    do ie=1,nelemd
       elem(ie)%state%T=0.0_r8
       call putUniquePoints(elem(ie)%idxP, nlev, tmp(start:,:), &
            elem(ie)%state%T(:,:,:,1))
       start=start+elem(ie)%idxP%numUniquePts
    end do

    if (pertlim .ne. D0_0) then
      if(masterproc) then
        write(iulog,*) trim(subname), ': Adding random perturbation bounded', &
                       'by +/- ', pertlim, ' to initial temperature field'
      end if

      call random_seed(size=rndm_seed_sz)
      allocate(rndm_seed(rndm_seed_sz))

      do ie=1,nelemd
        ! seed random number generator based on element ID
        ! (possibly include a flag to allow clock-based random seeding)
        rndm_seed = elem(ie)%GlobalId
        call random_seed(put=rndm_seed)
        do i=1,np
          do j=1,np
            do k=1,nlev
              call random_number(pertval)
              pertval = D2_0*pertlim*(D0_5 - pertval)
              elem(ie)%state%T(i,j,k,1) = elem(ie)%state%T(i,j,k,1)*(D1_0 + pertval)
            end do
          end do
        end do
      end do

      deallocate(rndm_seed)
    end if

    gcid => get_ldof(elem, 1)

    ! qmin = 1e-12,0,0

    do m_cnst=1,pcnst
       found = .false.
       if(cnst_read_iv(m_cnst)) then
          call infld(cnst_name(m_cnst), ncid_ini, iodesc, tlncols, 'lev', tmp, found)
       end if
       if(.not. found) then

          if(par%masterproc  ) write(iulog,*) 'Field ',cnst_name(m_cnst),' not found on initial dataset'

          if (( microp_scheme .eq. 'MG' ) .and. (microp_driver_implements_cnst(cnst_name(m_cnst)))) then
             call microp_driver_init_cnst(cnst_name(m_cnst),tmp , gcid)
              if(par%masterproc) write(iulog,*) '          ', cnst_name(m_cnst), ' initialized by "microp_driver_init_cnst"'
          else if (( microp_scheme .eq. 'RK' ) .and. (stratiform_implements_cnst(cnst_name(m_cnst)))) then
             call stratiform_init_cnst(cnst_name(m_cnst), tmp, gcid)
              if(par%masterproc) write(iulog,*) '          ', cnst_name(m_cnst), &
                   ' initialized by "stratiform_init_cnst"'
          else if (chem_implements_cnst(cnst_name(m_cnst))) then
             call chem_init_cnst(cnst_name(m_cnst), tmp, gcid)
              if(par%masterproc) write(iulog,*) '          ', cnst_name(m_cnst), &
                   ' initialized by "chem_init_cnst"'
          else if (tracers_implements_cnst(cnst_name(m_cnst))) then
             call tracers_init_cnst(cnst_name(m_cnst), tmp, gcid)
              if(par%masterproc) write(iulog,*) '          ', cnst_name(m_cnst), &
                   ' initialized by "tracers_init_cnst"'
          else if (aoa_tracers_implements_cnst(cnst_name(m_cnst))) then
             call aoa_tracers_init_cnst(cnst_name(m_cnst), tmp, gcid)
              if(par%masterproc) write(iulog,*) '          ', cnst_name(m_cnst), &
                   ' initialized by "aoa_tracers_init_cnst"'
          else if (aerosol_implements_cnst(cnst_name(m_cnst))) then
             call aerosol_init_cnst(cnst_name(m_cnst), tmp, gcid)
              if(par%masterproc) write(iulog,*) '          ', cnst_name(m_cnst), &
                   ' initialized by "aerosol_init_cnst"'
          else if (carma_implements_cnst(cnst_name(m_cnst))) then
             call carma_init_cnst(cnst_name(m_cnst), tmp, gcid)
              if(par%masterproc) write(iulog,*) '          ', cnst_name(m_cnst), &
                   ' initialized by "carma_init_cnst"'
          else if (co2_implements_cnst(cnst_name(m_cnst))) then
             call co2_init_cnst(cnst_name(m_cnst), tmp, gcid)
              if(par%masterproc) write(iulog,*) '          ', cnst_name(m_cnst), &
                   ' initialized by "co2_init_cnst"'
          else
              if(par%masterproc) write(iulog,*) '          ', cnst_name(m_cnst), ' set to 0.'
          end if
       end if
       do k=1,nlev
          do ig=1,tlncols
             tmp(ig,k)=max(qmin(m_cnst),tmp(ig,k))
          end do
       end do
       
       start=1
       do ie=1,nelemd
          elem(ie)%state%Q(:,:,:,m_cnst,:)=0.0_r8
          call putUniquePoints(elem(ie)%idxP, nlev, tmp(start:,:), &
               elem(ie)%state%Q(:,:,:,m_cnst,1))
          start=start+elem(ie)%idxP%numUniquePts
       end do
    end do
    deallocate(gcid)

    call pio_freedecomp(ncid_ini, iodesc)

    call get_dyn_decomp(elem, 1, pio_double, iodesc)

    fieldname = 'PS'
    call infld(fieldname, ncid_ini, iodesc, tmp(:,1), found)
    if(.not. found) then
       call endrun('Could not find PS field on input datafile')
    end if
    start=1
    tmp(:,1)=tmp(:,1)*0.01_r8

    if(minval(tmp(:,1)) < 100) then
       call endrun('Problem reading ps field')
    end if

    do ie=1,nelemd
       elem(ie)%state%ps_v=0.0_r8
       call putUniquePoints(elem(ie)%idxP, tmp(start:start+elem(ie)%idxP%numUniquePts-1,1), &
            elem(ie)%state%ps_v(:,:,1))
       start=start+elem(ie)%idxP%numUniquePts
    end do

    if ( (ideal_phys .or. aqua_planet)) then
       tmp(:,:) = 0._r8
    else    
       fieldname = 'PHIS'
       call infld(fieldname, ncid_topo, iodesc, tmp(:,1), found)
       if(.not. found) then
          call endrun('Could not find PHIS field on input datafile')
       end if
    end if
    start=1
    do ie=1,nelemd
       elem(ie)%state%phis=0.0_r8
       call putUniquePoints(elem(ie)%idxP, tmp(start:,1), &
            elem(ie)%state%phis(:,:))
       start=start+elem(ie)%idxP%numUniquePts
    end do
    
    ! once we've read all the fields we do a boundary exchange to 
    ! update the redundent columns in the dynamics
    if(iam < par%nprocs) then
       call initEdgeBuffer(edge, (3+pcnst)*nlev+2)
    end if
    do ie=1,nelemd
       kptr=0
       call edgeVpack(edge, elem(ie)%state%ps_v(:,:,1),1,kptr,elem(ie)%desc)
       kptr=kptr+1
       call edgeVpack(edge, elem(ie)%state%phis,1,kptr,elem(ie)%desc)
       kptr=kptr+1
       call edgeVpack(edge, elem(ie)%state%v(:,:,:,:,1),2*nlev,kptr,elem(ie)%desc)
       kptr=kptr+2*nlev
       call edgeVpack(edge, elem(ie)%state%T(:,:,:,1),nlev,kptr,elem(ie)%desc)
       kptr=kptr+nlev
       call edgeVpack(edge, elem(ie)%state%Q(:,:,:,:,1),nlev*pcnst,kptr,elem(ie)%desc)
    end do
    if(iam < par%nprocs) then
       call bndry_exchangeV(par,edge)
    end if
    do ie=1,nelemd
       kptr=0
       call edgeVunpack(edge, elem(ie)%state%ps_v(:,:,1),1,kptr,elem(ie)%desc)
       kptr=kptr+1
       call edgeVunpack(edge, elem(ie)%state%phis,1,kptr,elem(ie)%desc)
       kptr=kptr+1
       call edgeVunpack(edge, elem(ie)%state%v(:,:,:,:,1),2*nlev,kptr,elem(ie)%desc)
       kptr=kptr+2*nlev
       call edgeVunpack(edge, elem(ie)%state%T(:,:,:,1),nlev,kptr,elem(ie)%desc)
       kptr=kptr+nlev
       call edgeVunpack(edge, elem(ie)%state%Q(:,:,:,:,1),nlev*pcnst,kptr,elem(ie)%desc)
    end do

!$omp parallel do private(ie, t, m_cnst)
    do ie=1,nelemd
       do t=2,3
          elem(ie)%state%ps_v(:,:,t)=elem(ie)%state%ps_v(:,:,1)
          elem(ie)%state%v(:,:,:,:,t)=elem(ie)%state%v(:,:,:,:,1)
          elem(ie)%state%T(:,:,:,t)=elem(ie)%state%T(:,:,:,1)
          do m_cnst=1,pcnst
             elem(ie)%state%Q(:,:,:,m_cnst,t)=elem(ie)%state%Q(:,:,:,m_cnst,1)
          end do
       end do
       call shr_vmath_log(elem(ie)%state%ps_v,elem(ie)%state%lnps,size(elem(ie)%state%lnps))
    end do

    if(iam < par%nprocs) then
       call FreeEdgeBuffer(edge)
    end if

    !
    ! This subroutine is used to create nc_topo files, if requested
    ! 

    call nctopo_util_inidat(ncid_topo,iodesc,elem)

    call pio_freedecomp(ncid_topo, iodesc)
    deallocate(tmp)

  end subroutine read_inidat



  subroutine get_dyn_decomp(elem, nlev, datatype, iodesc)
    use pio, only : io_desc_t, pio_initdecomp
    use cam_pio_utils, only : pio_subsystem
    use dyn_grid, only : get_horiz_grid_dim_d

    type(element_t), pointer :: elem(:)
    integer, intent(in) :: nlev, datatype
    type(io_desc_t), intent(out) :: iodesc
    integer, pointer :: ldof(:)
    integer :: dimlens(2), dimcnt

    dimcnt=1
    call get_horiz_grid_dim_d(dimlens(1)) 
    if(nlev>1) then
       dimlens(2) = nlev
       dimcnt=dimcnt+1
    end if

    ldof => get_ldof(elem, nlev)
    call pio_initdecomp(pio_subsystem, datatype, dimlens(1:dimcnt), ldof, iodesc)

    deallocate(ldof)

  end subroutine get_dyn_decomp


  function get_ldof(elem, nlev) result(ldof)
    use dimensions_mod,     only: nelemd
    use dyn_grid, only : get_horiz_grid_dim_d
    use abortutils,     only: endrun

    type(element_t), pointer :: elem(:)
    integer, intent(in) :: nlev
    integer, pointer :: ldof(:)

    integer :: lcnt, ie, j, k, ig, numpts, offset, hdim


    call get_horiz_grid_dim_d(hdim)

    lcnt = 0
    do ie=1,nelemd
       lcnt = lcnt+nlev*elem(ie)%idxP%NumUniquePts
    end do
    allocate(ldof(lcnt))
    ig=1
    ldof(:) = 0
    do k=1,nlev
       do ie=1,nelemd
          numpts = elem(ie)%idxP%NumUniquePts
          offset = elem(ie)%idxP%UniquePtOffset
          do j=1,numpts
             ldof(ig)=offset+(j-1)+(k-1)*hdim
             ig=ig+1
          end do
       end do
    end do


  end function get_ldof





end module inidat
