!------------------------------------------------------------------------------
!
! MODULE: Grib1
!
! DESCRIPTION:
!> Read data input and write icing output in Grib 1
!
! REVISION HISTORY:
! Feb 2015 - generated
!
!------------------------------------------------------------------------------

module Grib1
  use Kinds

  IMPLICIT NONE

  public 

contains

  !------------------------------------------------------------------------------
  ! DESCRIPTION:
  !> Read in all model fields
  !
  !> @param[in]  filename  - input model file name
  !> @param[in]  cmodel    - GFS/NAM/RAP
  !> @param[in]  pds7        - model vertical levels
  !> @param[out] fields    - storing 3D model fields
  !> @param[out] modelData - storing 2D model fields
  !> @param[out] kpds      - kpds(200)
  !> @param[out] kgds      - kgds(200)
  !> @param[out] iret      - status (0 is successful)
  !
  ! Notes:
  !   make sure pressure in Pascal for GFS & NAM; RAP pressure is in Pascal
  !------------------------------------------------------------------------------

  subroutine readModelGB1(filename, cmodel, pds7, fields, modelData, kpds, kgds, iret)
    implicit none
    character(len=*), intent(in) :: filename
    character(len=*), intent(in) :: cmodel ! RAP/NAM/GFS
    integer,          intent(in) :: pds7(:)! For pressure level, in hPa
    type(model_fields3D_t), intent(out) :: fields
    type(model_inputs_t),   intent(out) :: modelData
    integer, intent(out) :: kpds(:)
    integer, intent(out) :: kgds(:)
    integer, intent(out) :: iret
 
    integer :: iunit, jret
    integer :: igrid       ! grid number
    integer :: nx, ny, nxy, nz, k

    type :: pds5_t
       !~~~~~~~~~~~~~~~~~~~~~3D~~~~~~~~~~~~~~~~~~~~~~!
       ! RAP NAM GFS
       integer :: p   ! pressure
       integer :: h   ! height
       integer :: pvv ! pressureVerticalVelocity
       integer :: cwm ! cloudWaterMixingRatio
       integer :: t   ! temperature
       integer :: wvm ! simply derived from specificHumidity
       ! RAP NAM
       integer :: rwm ! rainWaterMixingRatio
       integer :: snm ! snowMixingRatio
       ! RAP
       integer :: gpm ! graupelMixingRatio
       integer :: icm ! iceMixingRatio = CICE,  conventional difference
       ! NAM & GFS
       integer :: rh  ! relativeHumidity
       !~~~~~~~~~~~~~~~~~~~~~2D~~~~~~~~~~~~~~~~~~~~~~!
       ! topography: the same as height
    end type pds5_t
    type(pds5_t) :: pds5
    integer :: pds6

    character(len=*), parameter :: myself = 'Model::readModelGB1(), '


    iret = -1
    iunit = 11

    nz = size(pds7)

    !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
    ! open GRIB 1 file for reading
    !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
    call BAOPENR(iunit, filename, iret)
    if (iret /= 0)   go to 10

    call getHeaderGB1(iunit, kgds, kpds)
    nx = kgds(2)
    ny = kgds(3)
    nxy = nx * ny
    print *, "model kgds=", kgds(1:22)

    ! return if not a proper grid number
    igrid = kpds(3)
    if( cmodel == 'RAP' .and. (igrid /= 252 .and. igrid /= 130)) then
       iret = -1
       write(*,*) "For RAP model, CIP only supports grid 252 and 130"
       return
    else if(cmodel == 'NAM' .and. (igrid /= 180 .and. igrid /= 221) )then
       iret = -1
       write(*,*) "For NAM model, CIP only supports grid 180 and 221"
       return
    end if

    !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
    ! read in 2D fields of modelData
    !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
    ! topography
    allocate(modelData% top(nx, ny))
    pds5%h = 7
    call readGB1(iunit,nxy,pds5%h, 1, 0, modelData%top,iret)
    if(iret /= 0)  then
       call BACLOSE(iunit, jret)
       return
    end if

    !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
    ! read in 3D fields
    !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
    ! preset by GFS/NAM, allocated fields%p at different time from other fields.
    allocate(fields%p(nx, ny, nz))

    select case(cmodel)
    case("GFS")
       ! pds5:        p  h  vv  cwm  t  spfh rwm  snm  gpm  icm  rh
       pds5 = pds5_t(-1, 7, 39, 153, 11, 51,  -1,  -1,  -1,  -1, 52)
       ! pds6: 3D on pressure leve
       pds6 = 100
       ! for pressure field, make sure it's in pascal
       do k = 1, nz
          fields%p(:, :, k) = pds7(k) * 100.0 ! from millibar to pascal
       end do
    case("RAP")
       ! pds5:       p  h  vv  cwm   t  spfh rwm  snm  gpm  cice  rh
       pds5 = pds5_t(1, 7, 39, 153,  11, 51, 170, 171, 179,  58,  -1)
       ! pds6: 3D on Sigma Level
       pds6 = 109
    case("NAM")
       ! pds5:        p  h  vv  cwm  t   spfh rwm  snm  gpm  icm  rh
       pds5 = pds5_t(-1, 7, 39, 153, 11,  51, 170, 171,  -1,  58, 52)
       ! pds6: 3D on pressure level
       pds6 = 100
       ! for pressure field, make sure it's in pascal
       do k = 1, nz
          fields%p(:, :, k) = pds7(k) * 100.0 ! from millibar to pascal
       end do
       ! gpm is set to zero for NAM
       allocate(fields%gpm(nx, ny, nz))
       fields%gpm = 0.0
    case default
       write(*,*) myself, "error -- Model ", cmodel, " is not supported."
       return
    end select

    if(pds5%h > 0) allocate(fields%h(nx, ny, nz))
    if(pds5%pvv > 0) allocate(fields%pvv(nx, ny, nz))
    if(pds5%cwm > 0) allocate(fields%cwm(nx, ny, nz))
    if(pds5%t > 0) allocate(fields%t(nx, ny, nz))
    if(pds5%wvm > 0) allocate(fields%wvm(nx, ny, nz))
    if(pds5%rwm > 0) allocate(fields%rwm(nx, ny, nz))
    if(pds5%snm > 0) allocate(fields%snm(nx, ny, nz))
    if(pds5%gpm > 0) allocate(fields%gpm(nx, ny, nz))
    if(pds5%icm > 0) allocate(fields%icm(nx, ny, nz))
    if(pds5%rh > 0) allocate(fields%rh(nx, ny, nz))

    do k = 1, nz
       if(pds5%p > 0) then
          call readGB1(iunit,nxy,pds5%p,  pds6,pds7(k),fields%p(:,:,k),  iret)
          if(iret /= 0)  go to 10 ! close iunit and return
       end if
       if(pds5%h > 0) then
          call readGB1(iunit,nxy,pds5%h,  pds6,pds7(k),fields%h(:,:,k),  iret)
          if(iret /= 0)  go to 10
       end if
       if(pds5%pvv > 0) then
          call readGB1(iunit,nxy,pds5%pvv,pds6,pds7(k),fields%pvv(:,:,k),iret)
          if(iret /= 0)  go to 10
       end if
       if(pds5%cwm > 0) then
          call readGB1(iunit,nxy,pds5%cwm,pds6,pds7(k),fields%cwm(:,:,k),iret)
          if(iret /= 0)  go to 10
       end if
       if(pds5%t > 0) then
          call readGB1(iunit,nxy,pds5%t,  pds6,pds7(k),fields%t(:,:,k),  iret)
          if(iret /= 0)  go to 10
       end if
       ! specific humidity for GFS, to be converted to water vapor mixing ratio
       if(pds5%wvm > 0) then
          call readGB1(iunit,nxy,pds5%wvm,pds6,pds7(k),fields%wvm(:,:,k),iret)
          if(iret /= 0)  go to 10
       end if
       if(pds5%rwm > 0) then
          call readGB1(iunit,nxy,pds5%rwm,pds6,pds7(k),fields%rwm(:,:,k),iret)
          if(iret /= 0)  go to 10
       end if
       if(pds5%snm > 0) then
          call readGB1(iunit,nxy,pds5%snm,pds6,pds7(k),fields%snm(:,:,k),iret)
          if(iret /= 0)  go to 10
       end if
       ! was set to zero for NAM
       if(pds5%gpm > 0) then
          call readGB1(iunit,nxy,pds5%gpm,pds6,pds7(k),fields%gpm(:,:,k),iret)
          if(iret /= 0)  go to 10
       end if
       ! ICMR and CICE are the same for RAP
       if(pds5%icm > 0) then
          call readGB1(iunit,nxy,pds5%icm,pds6,pds7(k),fields%icm(:,:,k),iret)
          if(iret /= 0)  go to 10
       end if
       if(pds5%rh > 0) then
          call readGB1(iunit,nxy,pds5%rh, pds6,pds7(k),fields%rh(:,:,k), iret)
          if(iret /= 0)  go to 10
       end if
    end do

    !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
    ! close GRIB 1 file
    !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
10  call BACLOSE(iunit, jret)
    return
  end subroutine readModelGB1


  !------------------------------------------------------------------------------
  ! DESCRIPTION:
  !> Almost the same function as getgbh, but as a simpler version
  !
  !> @param[in]  iunit		    - opened file handler
  !> @param[out] kgds		    - kgds(200)
  !> @param[out] kpds_out, optional - kpds_out(200)
  !
  !------------------------------------------------------------------------------

  subroutine getHeaderGB1(iunit, kgds, kpds_out)
    IMPLICIT NONE
    integer, intent(in) :: iunit
    integer, intent(out) :: kgds(:)
    integer, intent(out), optional :: kpds_out(:)

    integer :: kg, kf, k, iret
    integer, dimension(200) :: jpds, jgds, kpds

    jpds(:) = -1
    jgds(:) = -1

    call getgbh(iunit, 0, 0, jpds, jgds, kg, kf, k, kpds, kgds, iret)

    if(present(kpds_out)) kpds_out = kpds

    return
  end subroutine getHeaderGB1


  !------------------------------------------------------------------------------
  ! DESCRIPTION:
  !> use GETBG from W3LIB library to read 2D data in GRIB1
  !
  !> @param[in]  iunit      - opened file handler
  !> @param[in]  nxy       - model 2D dimension
  !> @param[in]  pds5      - jpds(5)
  !> @param[in]  pds6      - jpds(6)
  !> @param[in]  pds7      - jpds(7)
  !> @param[inout] data    - storing data read in
  !> @param[out]   iret    - status (0 is successful)
  !
  ! Notes:
  !  for some field, jpds(14), jpds(15) have different specification
  !------------------------------------------------------------------------------

  subroutine readGB1(iunit, nxy, pds5, pds6, pds7, data, iret, pds14, pds15)
    IMPLICIT NONE
    integer, intent(in) :: iunit
    integer, intent(in) :: nxy
    integer, intent(in) :: pds5, pds6, pds7
    real,    intent(inout) :: data(:,:)
    integer, intent(out) :: iret
    integer, intent(in), optional :: pds14, pds15

    ! More arguments for GETGB
    integer, dimension(200) :: jpds, jgds, kpds, kgds
    integer :: kf, k
    logical :: bitmap(nxy)

    character(len=*), parameter :: myself = 'Model::readGB1() '

    jpds(:) = -1
    jgds(:) = -1

    jpds(5) = pds5
    jpds(6) = pds6
    jpds(7) = pds7
    if (present(pds14)) jpds(14) = pds14
    if (present(pds15)) jpds(15) = pds15

    bitmap = .false.

    call GETGB(iunit, 0, nxy, 0, jpds, jgds, kf, k, kpds, kgds, bitmap, data, iret)

    if (iret /= 0) then
      write(*, *) myself, "can not read field", jpds(5:7), " iret=", iret
      return
    end if

    return
  end subroutine readGB1


  !------------------------------------------------------------------------------
  ! DESCRIPTION:
  !> write out grib 1
  !
  !> @param[in]  filename  - output file name
  !> @param[in]  kkpds     - PDS
  !> @param[in]  kgds      - GDS
  !> @param[in]  nz        - model vertical levels
  !> @param[in]  icingData - type of icing_output_t, including all icing products
  !> @param[out] iret      - status (0 is successful)
  !
  !------------------------------------------------------------------------------
  subroutine writeIcingGB1(filename, kkpds, kgds, icingData, iret)
    IMPLICIT NONE
    character(*), intent(in) :: filename
    integer, intent(in) :: kkpds(:)
    integer, intent(in) :: kgds(:)
    type(icing_output_t), intent(in) :: icingData
    integer, intent(out) :: iret

    integer :: kpds(200) ! modified PDS
    integer :: nxy
    integer :: iunit, z
 
    integer :: nz
    integer, allocatable :: levels(:)

    logical, allocatable :: bitmap(:)

    character(len=*), parameter :: myself = 'Model::writeIcingGB1() '


    write(*,*) "Writing icing to file: ", trim(filename)

    iret = -1
    iunit = 30

    nz = size(icingData%levels)
    allocate(levels(nz))

    nxy = kgds(2) * kgds(3)
    allocate(bitmap(nxy))

    kpds(:) = kkpds(:)

    ! check output vertical levels: flight level or pressure level
    if( icingData%ctype == 'FLT') then
       kpds(6)  = 103 ! altitude above MSL
       kpds(22) = 2   ! UNITS DECIMAL SCALE FACTOR
       ! set the flight levels in meters
       levels(:) = icingData%levels(:) 
    else if( icingData%ctype == 'PRS') then
       kpds(6) = 100 ! pressure level
       kpds(22) = 2  ! UNITS DECIMAL SCALE FACTOR
       ! set the pressure levels in hPa
       levels(:) = int(icingData%levels(:) / 100)
    else
       write(*,*)  myself, icingData%ctype, " level is not supported."
       return
    end if

    ! open file to write
    call BAOPENWT(iunit, trim(filename), iret)
    if (iret /= 0) then
       write(*, *) myself, 'open file error: ', trim(filename), ' iret=', iret
       return
    end if

    ! write out
    do z = 1, nz
       kpds(7) = levels(z)
       ! probability
       kpds(5)  = 168
       kpds(19) = 140 ! grib table version
       call PUTGB(iunit, nxy, kpds, kgds, bitmap, icingData%probability(:, :, z), iret)
       ! SLD
       kpds(5)  = 236
       kpds(19) = 2
       call PUTGB(iunit, nxy, kpds, kgds, bitmap, icingData%sld(:, :, z), iret)
       ! severity
       kpds(5)  = 175
       kpds(19) = 129
       call PUTGB(iunit, nxy, kpds, kgds, bitmap, icingData%severity(:, :, z), iret)
    end do

    deallocate(levels)
    deallocate(bitmap)

    call BACLOSE(iunit, iret)
    return
  end subroutine writeIcingGB1

end module Grib1