!
! Todd Hutchinson
! WSI
! August 17, 2005
!
! Routines in this file are shared by io_grib1 and io_grib2
!

!*****************************************************************************

SUBROUTINE get_dims(MemoryOrder, Start, End, ndim, x_start, x_end, y_start, &
     y_end, z_start, z_end)
  IMPLICIT NONE
  CHARACTER (LEN=*)    ,INTENT(IN)    :: MemoryOrder
  INTEGER              ,INTENT(OUT)   :: ndim,x_start,x_end,y_start
  INTEGER              ,INTENT(OUT)   :: y_end,z_start,z_end
  integer ,dimension(*),intent(in)    :: Start, End
  CHARACTER (LEN=1)                   :: char
  INTEGER                             :: idx
  CHARACTER (LEN=3)                   :: MemoryOrderLcl

  x_start = 1
  x_end   = 1
  y_start = 1
  y_end   = 1
  z_start = 1
  z_end   = 1

  !
  ! Note: Need to add "char == 'S'" for boundary conditions
  !

  ndim = 0

  ! Fix for out-of-bounds references
  MemoryOrderLcl = '   '
  do idx=1,len_trim(MemoryOrder)
     MemoryOrderLcl(idx:idx) = MemoryOrder(idx:idx)
  enddo
  !
  ! First, do the special boundary cases.  These do not seem to 
  !    
  if ((MemoryOrderLcl(1:3) .eq. 'XSZ') &
       .or. (MemoryOrderLcl(1:3) .eq. 'XEZ')) then
     x_start = Start(3)
     x_end = End(3)
     y_start = Start(1)
     y_end = End(1)
     z_start = Start(2)
     z_end = End(2)
     ndim = 3
  else if ((MemoryOrderLcl(1:3) .eq. 'YSZ') .or. &
       (MemoryOrderLcl(1:3) .eq. 'YEZ')) then
     x_start = Start(1)
     x_end = End(1)
     y_start = Start(3)
     y_end = End(3)
     z_start = Start(2)
     z_end = End(2)
     ndim = 3
  else if ((MemoryOrderLcl(1:2) .eq. 'YS') .or. &
       (MemoryOrderLcl(1:2) .eq. 'YE')) then
     x_start = Start(1)
     x_end = End(1)
     y_start = Start(2)
     y_end = End(2)
     ndim = 2
  else if ((MemoryOrderLcl(1:2) .eq. 'XS') .or. &
       (MemoryOrderLcl(1:2) .eq. 'XE')) then
     x_start = Start(2)
     x_end = End(2)
     y_start = Start(1)
     y_end = End(1)
     ndim = 2
  else if ((MemoryOrderLcl(1:1) .eq. 'C') .or. (MemoryOrderLcl(1:1) .eq. 'c')) then 
     ! This is for "non-decomposed" fields
     x_start = Start(1)
     x_end = End(1)
!     y_start = Start(2)
!     y_end = End(2)
!     z_start = Start(3)
!     z_end = End(3)
     ndim = 3
  else
     do idx=1,len_trim(MemoryOrderLcl)
        char = MemoryOrderLcl(idx:idx)
        if ((char == 'X') .or. (char == 'x')) then
           x_start = Start(idx)
           x_end   = End(idx)
           ndim = ndim + 1
        else if ((char == 'Y') .or. (char == 'y')) then
           y_start = Start(idx)
           y_end   = End(idx)
           ndim = ndim + 1
        else if ((char == 'Z') .or. (char == 'z')) then
           z_start = Start(idx)
           z_end   = End(idx)
           ndim = ndim + 1
        else if (char == '0') then
           ! Do nothing, this indicates field is a scalar.
           ndim = 0
        else
           call wrf_message('Invalid Dimension in get_dims: '//char)
        endif
     enddo
  endif

END SUBROUTINE get_dims

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

SUBROUTINE geth_idts (ndate, odate, idts)

  IMPLICIT NONE

  !  From 2 input mdates ('YYYY-MM-DD HH:MM:SS.ffff'), 
  !  compute the time difference.

  !  on entry     -  ndate  -  the new hdate.
  !                  odate  -  the old hdate.

  !  on exit      -  idts    -  the change in time in seconds.

  CHARACTER (LEN=*) , INTENT(INOUT) :: ndate, odate
  REAL              , INTENT(OUT)   :: idts

  !  Local Variables

  !  yrnew    -  indicates the year associated with "ndate"
  !  yrold    -  indicates the year associated with "odate"
  !  monew    -  indicates the month associated with "ndate"
  !  moold    -  indicates the month associated with "odate"
  !  dynew    -  indicates the day associated with "ndate"
  !  dyold    -  indicates the day associated with "odate"
  !  hrnew    -  indicates the hour associated with "ndate"
  !  hrold    -  indicates the hour associated with "odate"
  !  minew    -  indicates the minute associated with "ndate"
  !  miold    -  indicates the minute associated with "odate"
  !  scnew    -  indicates the second associated with "ndate"
  !  scold    -  indicates the second associated with "odate"
  !  i        -  loop counter
  !  mday     -  a list assigning the number of days in each month

  CHARACTER (LEN=24) :: tdate
  INTEGER :: olen, nlen
  INTEGER :: yrnew, monew, dynew, hrnew, minew, scnew
  INTEGER :: yrold, moold, dyold, hrold, miold, scold
  INTEGER :: mday(12), i, newdys, olddys
  LOGICAL :: npass, opass
  INTEGER :: isign
  CHARACTER (LEN=300) :: wrf_err_message
  INTEGER :: ndfeb

  IF (odate.GT.ndate) THEN
     isign = -1
     tdate=ndate
     ndate=odate
     odate=tdate
  ELSE
     isign = 1
  END IF

  !  Assign the number of days in a months

  mday( 1) = 31
  mday( 2) = 28
  mday( 3) = 31
  mday( 4) = 30
  mday( 5) = 31
  mday( 6) = 30
  mday( 7) = 31
  mday( 8) = 31
  mday( 9) = 30
  mday(10) = 31
  mday(11) = 30
  mday(12) = 31

  !  Break down old hdate into parts

  hrold = 0
  miold = 0
  scold = 0
  olen = LEN(odate)

  READ(odate(1:4),  '(I4)') yrold
  READ(odate(6:7),  '(I2)') moold
  READ(odate(9:10), '(I2)') dyold
  IF (olen.GE.13) THEN
     READ(odate(12:13),'(I2)') hrold
     IF (olen.GE.16) THEN
        READ(odate(15:16),'(I2)') miold
        IF (olen.GE.19) THEN
           READ(odate(18:19),'(I2)') scold
        END IF
     END IF
  END IF

  !  Break down new hdate into parts

  hrnew = 0
  minew = 0
  scnew = 0
  nlen = LEN(ndate)

  READ(ndate(1:4),  '(I4)') yrnew
  READ(ndate(6:7),  '(I2)') monew
  READ(ndate(9:10), '(I2)') dynew
  IF (nlen.GE.13) THEN
     READ(ndate(12:13),'(I2)') hrnew
     IF (nlen.GE.16) THEN
        READ(ndate(15:16),'(I2)') minew
        IF (nlen.GE.19) THEN
           READ(ndate(18:19),'(I2)') scnew
        END IF
     END IF
  END IF

  !  Check that the dates make sense.

  npass = .true.
  opass = .true.

  !  Check that the month of NDATE makes sense.

  IF ((monew.GT.12).or.(monew.LT.1)) THEN
     PRINT*, 'GETH_IDTS:  Month of NDATE = ', monew
     npass = .false.
  END IF

  !  Check that the month of ODATE makes sense.

  IF ((moold.GT.12).or.(moold.LT.1)) THEN
     PRINT*, 'GETH_IDTS:  Month of ODATE = ', moold
     opass = .false.
  END IF

  !  Check that the day of NDATE makes sense.

  IF (monew.ne.2) THEN
     ! ...... For all months but February
     IF ((dynew.GT.mday(monew)).or.(dynew.LT.1)) THEN
        PRINT*, 'GETH_IDTS:  Day of NDATE = ', dynew
        npass = .false.
     END IF
  ELSE IF (monew.eq.2) THEN
     ! ...... For February
     IF ((dynew.GT.ndfeb(yrnew)).OR.(dynew.LT.1)) THEN
        PRINT*, 'GETH_IDTS:  Day of NDATE = ', dynew
        npass = .false.
     END IF
  END IF

  !  Check that the day of ODATE makes sense.

  IF (moold.ne.2) THEN
     ! ...... For all months but February
     IF ((dyold.GT.mday(moold)).or.(dyold.LT.1)) THEN
        PRINT*, 'GETH_IDTS:  Day of ODATE = ', dyold
        opass = .false.
     END IF
  ELSE IF (moold.eq.2) THEN
     ! ....... For February
     IF ((dyold.GT.ndfeb(yrold)).or.(dyold.LT.1)) THEN
        PRINT*, 'GETH_IDTS:  Day of ODATE = ', dyold
        opass = .false.
     END IF
  END IF

  !  Check that the hour of NDATE makes sense.

  IF ((hrnew.GT.23).or.(hrnew.LT.0)) THEN
     PRINT*, 'GETH_IDTS:  Hour of NDATE = ', hrnew
     npass = .false.
  END IF

  !  Check that the hour of ODATE makes sense.

  IF ((hrold.GT.23).or.(hrold.LT.0)) THEN
     PRINT*, 'GETH_IDTS:  Hour of ODATE = ', hrold
     opass = .false.
  END IF

  !  Check that the minute of NDATE makes sense.

  IF ((minew.GT.59).or.(minew.LT.0)) THEN
     PRINT*, 'GETH_IDTS:  Minute of NDATE = ', minew
     npass = .false.
  END IF

  !  Check that the minute of ODATE makes sense.

  IF ((miold.GT.59).or.(miold.LT.0)) THEN
     PRINT*, 'GETH_IDTS:  Minute of ODATE = ', miold
     opass = .false.
  END IF

  !  Check that the second of NDATE makes sense.

  IF ((scnew.GT.59).or.(scnew.LT.0)) THEN
     PRINT*, 'GETH_IDTS:  SECOND of NDATE = ', scnew
     npass = .false.
  END IF

  !  Check that the second of ODATE makes sense.

  IF ((scold.GT.59).or.(scold.LT.0)) THEN
     PRINT*, 'GETH_IDTS:  Second of ODATE = ', scold
     opass = .false.
  END IF

  IF (.not. npass) THEN
     WRITE( wrf_err_message , * ) &
          'module_date_time: geth_idts: Bad NDATE: ', ndate(1:nlen)
     CALL wrf_error_fatal ( TRIM ( wrf_err_message ) )
  END IF

  IF (.not. opass) THEN
     WRITE( wrf_err_message , * ) &
          'module_date_time: geth_idts: Bad ODATE: ', odate(1:olen)
     CALL wrf_error_fatal ( TRIM ( wrf_err_message ) )
  END IF

  !  Date Checks are completed.  Continue.

  !  Compute number of days from 1 January ODATE, 00:00:00 until ndate
  !  Compute number of hours from 1 January ODATE, 00:00:00 until ndate
  !  Compute number of minutes from 1 January ODATE, 00:00:00 until ndate

  newdys = 0
  DO i = yrold, yrnew - 1
     newdys = newdys + (365 + (ndfeb(i)-28))
  END DO

  IF (monew .GT. 1) THEN
     mday(2) = ndfeb(yrnew)
     DO i = 1, monew - 1
        newdys = newdys + mday(i)
     END DO
     mday(2) = 28
  END IF

  newdys = newdys + dynew-1

  !  Compute number of hours from 1 January ODATE, 00:00:00 until odate
  !  Compute number of minutes from 1 January ODATE, 00:00:00 until odate

  olddys = 0

  IF (moold .GT. 1) THEN
     mday(2) = ndfeb(yrold)
     DO i = 1, moold - 1
        olddys = olddys + mday(i)
     END DO
     mday(2) = 28
  END IF

  olddys = olddys + dyold-1

  !  Determine the time difference in seconds

  idts = (newdys - olddys) * 86400
  idts = idts + (hrnew - hrold) * 3600
  idts = idts + (minew - miold) * 60
  idts = idts + (scnew - scold)

  IF (isign .eq. -1) THEN
     tdate=ndate
     ndate=odate
     odate=tdate
     idts = idts * isign
  END IF

END SUBROUTINE geth_idts

!*****************************************************************************

SUBROUTINE get_vert_stag(VarName,Stagger,vert_stag)
  
  character (LEN=*) :: VarName
  character (LEN=*) :: Stagger
  logical           :: vert_stag

  if ((index(Stagger,'Z') > 0) .or. (VarName .eq. 'DNW') &
       .or.(VarName .eq. 'RDNW')) then
     vert_stag = .true.
  else
     vert_stag = .false.
  endif
end SUBROUTINE

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

FUNCTION ndfeb ( year ) RESULT (num_days)
  
  ! Compute the number of days in February for the given year
  
  IMPLICIT NONE
  
  INTEGER :: year
  INTEGER :: num_days
  
  num_days = 28 ! By default, February has 28 days ...
  IF (MOD(year,4).eq.0) THEN  
     num_days = 29  ! But every four years, it has 29 days ...
     IF (MOD(year,100).eq.0) THEN
        num_days = 28  ! Except every 100 years, when it has 28 days ...
        IF (MOD(year,400).eq.0) THEN
           num_days = 29  ! Except every 400 years, when it has 29 days.
        END IF
     END IF
  END IF
  
END FUNCTION ndfeb

!*****************************************************************************

SUBROUTINE get_dimvals(MemoryOrder,x,y,z,dims)

  IMPLICIT NONE
  CHARACTER (LEN=*)    ,INTENT(IN)    :: MemoryOrder
  INTEGER              ,INTENT(IN)    :: x,y,z
  INTEGER, DIMENSION(*),INTENT(OUT)   :: dims
  INTEGER                             :: idx
  CHARACTER (LEN=1) :: char
  CHARACTER (LEN=3) :: MemoryOrderLcl

  dims(1) = 1
  dims(2) = 1
  dims(3) = 1

  ! Fix for out-of-bounds references
  MemoryOrderLcl = '   '
  do idx=1,len_trim(MemoryOrder)
     MemoryOrderLcl(idx:idx) = MemoryOrder(idx:idx)
  enddo

  !
  ! Note: Need to add "char == 'S'" for boundary conditions
  !

  if ((MemoryOrderLcl(1:3) .eq. 'XSZ') &
       .or. (MemoryOrderLcl(1:3) .eq. 'XEZ')) then
     dims(1) = y
     dims(2) = z
     dims(3) = x
  else if ((MemoryOrderLcl(1:3) .eq. 'YSZ') .or. &
       (MemoryOrderLcl(1:3) .eq. 'YEZ')) then
     dims(1) = x
     dims(2) = z
     dims(3) = y
  else if ((MemoryOrderLcl(1:2) .eq. 'YS') .or. &
       (MemoryOrderLcl(1:2) .eq. 'YE')) then
     dims(1) = x
     dims(2) = y
     dims(3) = z
  else if ((MemoryOrderLcl(1:2) .eq. 'XS') .or. &
       (MemoryOrderLcl(1:2) .eq. 'XE')) then
     dims(1) = y
     dims(2) = x
     dims(3) = z
  else if ((MemoryOrderLcl(1:1) .eq. 'C') .or. &
       (MemoryOrderLcl(1:1) .eq. 'c')) then
     ! Non-decomposed field
     dims(1) = x
     dims(2) = y
     dims(3) = z
  else 
     do idx=1,len_trim(MemoryOrderLcl)
        char = MemoryOrderLcl(idx:idx)
        if ((char == 'X') .or. (char == 'x')) then
           dims(idx) = x
        else if ((char == 'Y') .or. (char == 'y')) then
           dims(idx) = y
        else if ((char == 'Z') .or. (char == 'z')) then
           dims(idx) = z
        else if (char == '0') then
           ! This is a scalar, do nothing.
        else
           call wrf_message ('Invalid Dimension in get_dimvals: '//char)
        endif
     enddo
  endif

END SUBROUTINE get_dimvals

!*****************************************************************************

SUBROUTINE get_soil_layers(VarName,soil_layers)
  
  character (LEN=*) :: VarName
  logical           :: soil_layers

  if ((VarName .eq. 'ZS') .or. (VarName .eq. 'DZS') &
       .or.(VarName .eq. 'TSLB') .or. (VarName .eq. 'SMOIS') &
       .or. (VarName .eq. 'SH2O') .or. (VarName .eq. 'KEEPFR3DFLAG') &
       .or. (VarName .eq. 'SMFR3D')) then
     soil_layers = .true.
  else
     soil_layers = .false.
  endif
end SUBROUTINE

!*****************************************************************************

SUBROUTINE Transpose(MemoryOrder, di, FieldType, Field, &
     Start1, End1, Start2, End2, Start3, End3, data, zidx, numrows, numcols)

  IMPLICIT NONE

#include "wrf_io_flags.h"

  CHARACTER (LEN=*),INTENT(IN)    :: MemoryOrder
  INTEGER          ,INTENT(IN)    :: Start1,End1,Start2,End2,Start3,End3
  INTEGER          ,INTENT(IN)    :: di
  integer          ,intent(inout) :: &
       Field(di,Start1:End1,Start2:End2,Start3:End3)
  INTEGER          ,intent(in)    :: FieldType
  real             ,intent(in)    :: data(*)
  INTEGER          ,INTENT(IN)    :: zidx, numcols, numrows
  INTEGER, DIMENSION(3)           :: dims
  INTEGER                         :: col, row
  LOGICAL                         :: logicaltype
  CHARACTER (LEN=1000)            :: msg
     
  if ((FieldType == WRF_REAL) .or. (FieldType == WRF_DOUBLE)) then
     do col=1,numcols
        do row=1,numrows
           call get_dimvals(MemoryOrder,col,row,zidx,dims)
           Field(1:di,dims(1),dims(2),dims(3)) = &
                TRANSFER(data((row-1)*numcols+col),Field,1)
        enddo
     enddo
  else if (FieldType == WRF_INTEGER) then
     do col=1,numcols
        do row=1,numrows
           call get_dimvals(MemoryOrder,col,row,zidx,dims)
           Field(1:di,dims(1),dims(2),dims(3)) = data((row-1)*numcols+col)
        enddo
     enddo
  else
     write (msg,*)'Reading of type ',FieldType,'from grib data not supported'
     call wrf_message(msg)
  endif

!
! This following seciton is for the logical type.  This caused some problems
!   on certain platforms.
! 
!  else if (FieldType == WRF_LOGICAL) then
!     do col=1,numcols
!        do row=1,numrows
!           call get_dimvals(MemoryOrder,col,row,zidx,dims)
!           Field(1:di,dims(1),dims(2),dims(3)) = &
!                TRANSFER(data((row-1)*numcols+col),logicaltype,1)
!        enddo
!     enddo
  
  
end SUBROUTINE

!*****************************************************************************

SUBROUTINE Transpose1D(MemoryOrder, di, FieldType, Field, &
     Start1, End1, Start2, End2, Start3, End3, data, nelems)

  IMPLICIT NONE

#include "wrf_io_flags.h"

  CHARACTER (LEN=*),INTENT(IN)    :: MemoryOrder
  INTEGER          ,INTENT(IN)    :: Start1,End1,Start2,End2,Start3,End3
  INTEGER          ,INTENT(IN)    :: di
  integer          ,intent(inout) :: &
       Field(di,Start1:End1,Start2:End2,Start3:End3)
  INTEGER          ,intent(in)    :: FieldType
  real             ,intent(in)    :: data(*)
  LOGICAL                         :: logicaltype
  CHARACTER (LEN=1000)            :: msg
  integer                         :: elemnum,nelems

  if ((FieldType == WRF_REAL) .or. (FieldType == WRF_DOUBLE)) then
     do elemnum=1,nelems
        Field(1:di,elemnum,1,1) = TRANSFER(data(elemnum),Field,1)
     enddo
  else if (FieldType == WRF_INTEGER) then
     do elemnum=1,nelems
        Field(1:di,elemnum,1,1) = TRANSFER(data(elemnum),Field,1)
     enddo
  else
     write (msg,*)'Reading of type ',FieldType,'from grib1 data not supported'
     call wrf_message(msg)
  endif

!
! This following seciton is for the logical type.  This caused some problems
!   on certain platforms.
! 
!  else if (FieldType == WRF_LOGICAL) then
!     do col=1,numcols
!        do row=1,numrows
!           call get_dimvals(MemoryOrder,col,row,zidx,dims)
!           Field(1:di,dims(1),dims(2),dims(3)) = &
!                TRANSFER(data((row-1)*numcols+col),logicaltype,1)
!        enddo
!     enddo
  
  
end SUBROUTINE Transpose1D

!*****************************************************************************
!
! Takes a starting date (startTime) in WRF format (yyyy-mm-dd_hh:mm:ss), 
!   adds an input number of seconds to the time, and outputs a new date 
!   (endTime) in WRF format.
!
!*****************************************************************************

subroutine advance_wrf_time(startTime, addsecs, endTime)
  implicit none

  integer          , intent(in)  :: addsecs
  character (len=*), intent(in)  :: startTime
  character (len=*), intent(out) :: endTime
  integer :: syear,smonth,sday,shour,smin,ssec
  integer :: days_in_month(12)

  read(startTime,'(I4.4,1X,I2.2,1X,I2.2,1X,I2.2,1X,I2.2,1X,I2.2)') &
       syear,smonth,sday,shour,smin,ssec
  
  ssec = ssec + addsecs

  do while (ssec .ge. 60) 
     smin = smin + 1
     ssec = ssec - 60
  enddo

  do while (smin .ge. 60)
     shour = shour + 1
     smin = smin - 60
  enddo

  do while (shour .ge. 24)
     sday = sday + 1
     shour = shour - 24
  enddo


  days_in_month(1) = 31
  if (((mod(syear,4) .eq. 0) .and. (mod(syear,100) .ne. 0)) &
       .or. (mod(syear,400) .eq. 0)) then
     days_in_month(2) = 29
  else
     days_in_month(2) = 28
  endif
  days_in_month(3) = 31
  days_in_month(4) = 30
  days_in_month(5) = 31
  days_in_month(6) = 30
  days_in_month(7) = 31
  days_in_month(8) = 31
  days_in_month(9) = 30
  days_in_month(10) = 31
  days_in_month(11) = 30
  days_in_month(12) = 31


  do while (sday .gt. days_in_month(smonth))
     sday = sday - days_in_month(smonth)
     smonth = smonth + 1
     if (smonth .gt. 12) then
        smonth = 1
        syear = syear + 1
     endif
  enddo
  

  write(endTime,'(I4.4,A,I2.2,A,I2.2,A,I2.2,A,I2.2,A,I2.2)') &
       syear,'-',smonth,'-',sday,'_',shour,':',smin,':',ssec

  return

end subroutine
