! $Id$
!
! Earth System Modeling Framework
! Copyright 2002-2003, University Corporation for Atmospheric Research,
! Massachusetts Institute of Technology, Geophysical Fluid Dynamics
! Laboratory, University of Michigan, National Centers for Environmental
! Prediction, Los Alamos National Laboratory, Argonne National Laboratory,
! NASA Goddard Space Flight Center.
! Licensed under the GPL.
!
!==============================================================================
!
!     ESMF Calendar Module
      module ESMF_CalendarMod
!
!==============================================================================
!
! This file contains the Calendar class definition and all Calendar class
! methods.
!
!------------------------------------------------------------------------------
! INCLUDES
#include <ESMF_TimeMgr.inc>

!==============================================================================
!BOPI
! !MODULE: ESMF_CalendarMod
!
! !DESCRIPTION:
! Part of Time Manager F90 API wrapper of C++ implemenation
!
! Defines F90 wrapper entry points for corresponding
! C++ class { \tt ESMC\_Calendar} implementation
!
! See {\tt ../include/ESMC\_Calendar.h} for complete description
!
!------------------------------------------------------------------------------
! !USES:
      ! inherit from ESMF base class
      use ESMF_BaseMod

      ! inherit from base time class
      use ESMF_BaseTimeMod

      implicit none
!
!------------------------------------------------------------------------------
! !PRIVATE TYPES:
      private
!------------------------------------------------------------------------------

      INTEGER, PARAMETER :: mday(MONTHS_PER_YEAR)   &
                          = (/31,28,31,30,31,30,31,31,30,31,30,31/)
      INTEGER, PARAMETER :: mdayleap(MONTHS_PER_YEAR) &
                          = (/31,29,31,30,31,30,31,31,30,31,30,31/)
      INTEGER, DIMENSION(365) :: daym
      INTEGER, DIMENSION(366) :: daymleap
      INTEGER :: mdaycum(0:MONTHS_PER_YEAR)
      INTEGER :: mdayleapcum(0:MONTHS_PER_YEAR)
      TYPE(ESMF_BaseTime), TARGET :: monthbdys(0:MONTHS_PER_YEAR)
      TYPE(ESMF_BaseTime), TARGET :: monthbdysleap(0:MONTHS_PER_YEAR)
      TYPE(ESMF_BaseTime), TARGET :: monthedys(0:MONTHS_PER_YEAR)
      TYPE(ESMF_BaseTime), TARGET :: monthedysleap(0:MONTHS_PER_YEAR)


!------------------------------------------------------------------------------
!     ! ESMF_CalKind_Flag
!
!     ! F90 "enum" type to match C++ ESMC_CalKind_Flag enum

      type ESMF_CalKind_Flag
        integer :: caltype
      end type

      type(ESMF_CalKind_Flag), parameter :: &
                               ESMF_CALKIND_GREGORIAN =  ESMF_CalKind_Flag(1), &
                               ESMF_CALKIND_NOLEAP =     ESMF_CalKind_Flag(2)

!      type(ESMF_CalKind_Flag), parameter :: &
!                               ESMF_CALKIND_GREGORIAN =  ESMF_CalKind_Flag(1), &
!                               ESMF_CALKIND_JULIAN =     ESMF_CalKind_Flag(2), &
!                           ! like Gregorian, except Feb always has 28 days
!                               ESMF_CALKIND_NOLEAP =     ESMF_CalKind_Flag(3), & 
!                           ! 12 months, 30 days each
!                               ESMF_CALKIND_360DAY =     ESMF_CalKind_Flag(4), & 
!                           ! user defined
!                               ESMF_CALKIND_GENERIC =    ESMF_CalKind_Flag(5), &
!                           ! track base time seconds only
!                               ESMF_CALKIND_NOCALENDAR = ESMF_CalKind_Flag(6)

!------------------------------------------------------------------------------
!     ! ESMF_Calendar
!
!     ! F90 class type to match C++ Calendar class in size only;
!     !  all dereferencing within class is performed by C++ implementation
!
!------------------------------------------------------------------------------
!
!     ! ESMF_DaysPerYear
!
      type ESMF_DaysPerYear
        integer :: D = 0    ! whole days per year
        integer :: Dn = 0   ! fractional days per year numerator
        integer :: Dd = 1   ! fractional days per year denominator
      end type              ! e.g. for Venus, D=0, Dn=926, Dd=1000
!
!------------------------------------------------------------------------------
!     ! ESMF_Calendar
!
!
      type ESMF_Calendar
        type(ESMF_CalKind_Flag) :: Type
        logical :: Set = .false.
        integer, dimension(MONTHS_PER_YEAR) :: DaysPerMonth = 0
        integer :: SecondsPerDay = 0
        integer :: SecondsPerYear = 0
        type(ESMF_DaysPerYear) :: DaysPerYear
      end type
!------------------------------------------------------------------------------
! !PUBLIC DATA: added by Juanxiong He, in order to breakthe cycle call between 
! ESMF_Stubs and ESMF_Time
   TYPE(ESMF_Calendar), public, save, pointer :: defaultCal   ! Default Calendar
   TYPE(ESMF_Calendar), public, save, pointer :: gregorianCal ! gregorian Calendar
   TYPE(ESMF_Calendar), public, save, pointer :: noleapCal    ! noleap Calendar

!
!------------------------------------------------------------------------------
! !PUBLIC TYPES:
      public initdaym
!      public mday
!      public mdayleap
!      public monthbdys
!      public monthbdysleap
!      public monthedys
!      public monthedysleap
!      public daym
!      public daymleap
!      public mdaycum
!      public mdayleapcum
      public ndaysinmonth
      public nsecondsinmonth
      public ndaysinyear
      public nsecondsinyear
      public nmonthinyearsec
      public ndayinyearsec
      public nsecondsinyearmonth
      public isleap
      public ESMF_CalKind_Flag
      public ESMF_CALKIND_GREGORIAN, ESMF_CALKIND_NOLEAP
!             ESMF_CALKIND_360DAY, ESMF_CALKIND_NOCALENDAR
!      public ESMF_CAL_JULIAN
!      public ESMF_CAL_GENERIC
      public ESMF_Calendar
      public ESMF_DaysPerYear

!------------------------------------------------------------------------------
!
! !PUBLIC MEMBER FUNCTIONS:
      public ESMF_CalendarCreate

! Required inherited and overridden ESMF_Base class methods

      public ESMF_CalendarInitialized ! Only in this implementation, intended
                                      ! to be private within ESMF methods
!EOPI

!------------------------------------------------------------------------------
! The following line turns the CVS identifier string into a printable variable.
      character(*), parameter, private :: version = &
      '$Id$'

!==============================================================================

      contains


!==============================================================================
!BOP
! !IROUTINE: ESMF_CalendarCreate - Create a new ESMF Calendar of built-in type

! !INTERFACE:
      ! Private name; call using ESMF_CalendarCreate()
      function ESMF_CalendarCreate(name, calkindflag, rc)

! !RETURN VALUE:
      type(ESMF_Calendar) :: ESMF_CalendarCreate

! !ARGUMENTS:
      character (len=*),       intent(in),  optional :: name
      type(ESMF_CalKind_Flag), intent(in)            :: calkindflag
      integer,                 intent(out), optional :: rc

! !DESCRIPTION:
!     Creates and sets a {\tt calendar} to the given built-in
!     {\tt ESMF\_CalKind_Flag}. 
!
!     This is a private method; invoke via the public overloaded entry point
!     {\tt ESMF\_CalendarCreate()}.
!
!     The arguments are:
!     \begin{description}
!     \item[{[name]}]
!          The name for the newly created calendar.  If not specified, a
!          default unique name will be generated: "CalendarNNN" where NNN
!          is a unique sequence number from 001 to 999.
!     \item[calkindflag]
!          The built-in {\tt ESMF\_CalKind_Flag}.  Valid values are:
!            {\tt ESMF\_CAL\_360DAY}, {\tt ESMF\_CAL\_GREGORIAN},
!            {\tt ESMF\_CAL\_JULIANDAY}, {\tt ESMF\_CAL\_NOCALENDAR}, and
!            {\tt ESMF\_CAL\_NOLEAP}.
!          See the "Time Manager Reference" document for a description of
!          each calendar type.
!     \item[{[rc]}]
!          Return code; equals {\tt ESMF\_SUCCESS} if there are no errors.
!     \end{description}
!    
!EOP
! !REQUIREMENTS:
!     TMGn.n.n
      type(ESMF_DaysPerYear) :: dayspy

      if ( present(rc) ) rc = ESMF_FAILURE
! Calendar is hard-coded.  Use ESMF library if more flexibility is needed.  
!      write(6,*) 'tcx ESMF_CalendarCreate ',calkindflag%caltype, ESMF_CALKIND_NOLEAP%caltype, ESMF_CALKIND_GREGORIAN%caltype
      if ( calkindflag%caltype  == ESMF_CALKIND_NOLEAP%caltype ) then
!         write(6,*) 'tcx ESMF_CalendarCreate: initialize noleap calendar '
         ESMF_CalendarCreate%Type = ESMF_CALKIND_NOLEAP
      elseif ( calkindflag%caltype  == ESMF_CALKIND_GREGORIAN%caltype ) then
!         write(6,*) 'tcx ESMF_CalendarCreate: initialize gregorian calendar '
         ESMF_CalendarCreate%Type = ESMF_CALKIND_GREGORIAN
      else
!         write(6,*) 'tcx ESMF_CalendarCreate: ERROR initialize invalid calendar'
         call wrf_error_fatal( "Error:: ESMF_CalendarCreate invalid calendar")
      endif

!$$$ This is a bug on some systems -- need initial value set by compiler at 
!$$$ startup.  
      ESMF_CalendarCreate%Set = .true.
      ESMF_CalendarCreate%SecondsPerDay = SECONDS_PER_DAY
! DaysPerYear and SecondsPerYear are incorrect for Gregorian calendars...  
      dayspy%D = size(daym)
      dayspy%Dn = 0
      dayspy%Dd = 1
      ESMF_CalendarCreate%DaysPerYear = dayspy
      ESMF_CalendarCreate%SecondsPerYear = ESMF_CalendarCreate%SecondsPerDay &
                                       * dayspy%D
      ESMF_CalendarCreate%DaysPerMonth(:) = mday(:)

      if ( present(rc) ) rc = ESMF_SUCCESS

      end function ESMF_CalendarCreate


!==============================================================================
!BOP
! !IROUTINE: ESMF_CalendarInitialized - check if calendar was created

! !INTERFACE:
      function ESMF_CalendarInitialized(calendar)

! !RETURN VALUE:
      logical ESMF_CalendarInitialized

! !ARGUMENTS:
      type(ESMF_Calendar), intent(in)            :: calendar

! !DESCRIPTION:
!EOP
! !REQUIREMENTS:
!     TMGn.n.n
        ESMF_CalendarInitialized = calendar%set
        if ( calendar%SecondsPerDay == 0 ) &
              ESMF_CalendarInitialized = .false.

     end function ESMF_CalendarInitialized

!==============================================================================

SUBROUTINE initdaym 
  IMPLICIT NONE
  INTEGER i,j,m

  m = 1
  mdaycum(0) = 0
!$$$ push this down into ESMF_BaseTime constructor
  monthbdys(0)%S  = 0
  monthbdys(0)%Sn = 0
  monthbdys(0)%Sd = 0
  DO i = 1,MONTHS_PER_YEAR
    DO j = 1,mday(i)
      daym(m) = i
      m = m + 1
    ENDDO
    mdaycum(i) = mdaycum(i-1) + mday(i)
!$$$ push this down into ESMF_BaseTime constructor
    monthbdys(i)%S  = SECONDS_PER_DAY * INT( mdaycum(i), ESMF_KIND_I8 )
    monthbdys(i)%Sn = 0
    monthbdys(i)%Sd = 0
  ENDDO
  ! End of month seconds, day before the beginning of next month
  DO i = 0,MONTHS_PER_YEAR
    j = i + 1
    if ( i == MONTHS_PER_YEAR ) j = 0
    monthedys(i)   = monthbdys(j)
    monthedys(i)%S = monthedys(i)%S - SECONDS_PER_DAY
  ENDDO

  m = 1
  mdayleapcum(0) = 0
!$$$ push this down into ESMF_BaseTime constructor
  monthbdysleap(0)%S  = 0
  monthbdysleap(0)%Sn = 0
  monthbdysleap(0)%Sd = 0
  DO i = 1,MONTHS_PER_YEAR
    DO j = 1,mdayleap(i)
      daymleap(m) = i
      m = m + 1
    ENDDO
    mdayleapcum(i) = mdayleapcum(i-1) + mdayleap(i)
!$$$ push this down into ESMF_BaseTime constructor
    monthbdysleap(i)%S  = SECONDS_PER_DAY * INT( mdayleapcum(i), ESMF_KIND_I8 )
    monthbdysleap(i)%Sn = 0
    monthbdysleap(i)%Sd = 0
  ENDDO
  ! End of month seconds, day before the beginning of next month
  DO i = 0,MONTHS_PER_YEAR
    j = i + 1
    if ( i == MONTHS_PER_YEAR ) j = 0
    monthedysleap(i)   = monthbdysleap(j)
    monthedysleap(i)%S = monthedysleap(i)%S - SECONDS_PER_DAY
  ENDDO

END SUBROUTINE initdaym

!==============================================================================

integer(esmf_kind_i8) FUNCTION nsecondsinyear ( year, calkindflag )
  ! Compute the number of seconds in the given year
  IMPLICIT NONE
  INTEGER, INTENT(IN) :: year
  type(ESMF_CalKind_Flag),intent(in) :: calkindflag

  nsecondsinyear = SECONDS_PER_DAY * INT( ndaysinyear(year, calkindflag) , ESMF_KIND_I8 )

END FUNCTION nsecondsinyear

!==============================================================================

integer function ndaysinmonth( year,month,calkindflag)
  ! Compute number of days in month for year, month, cal
  IMPLICIT NONE
  INTEGER, INTENT(in) :: year,month
  type(ESMF_CalKind_Flag),intent(in) :: calkindflag
  ! locals

  IF ( ( MONTH < 1 ) .OR. ( MONTH > MONTHS_PER_YEAR ) ) THEN
    CALL wrf_error_fatal( 'ERROR ndaysinmonth:  MONTH out of range' )
  ENDIF

  IF ( isleap(year,calkindflag) ) THEN
    ndaysinmonth = mdayleap(month)
  ELSE
    ndaysinmonth = mday(month)
  ENDIF

END function ndaysinmonth
!==============================================================================

integer(esmf_kind_i8) function nsecondsinmonth( year,month,calkindflag)
  ! Compute number of days in month for year, month, cal
  IMPLICIT NONE
  INTEGER, INTENT(in) :: year,month
  type(ESMF_CalKind_Flag),intent(in) :: calkindflag
  ! locals

  nsecondsinmonth = ndaysinmonth(year,month,calkindflag)*SECONDS_PER_DAY

END function nsecondsinmonth

!==============================================================================

integer function nmonthinyearsec(year,basetime,calkindflag)
  ! Compute month for year, basetime, cal
  IMPLICIT NONE
  INTEGER, INTENT(in) :: year
  type(ESMF_BaseTime), intent(in) :: basetime
  type(ESMF_CalKind_Flag),intent(in) :: calkindflag
  ! locals
  TYPE(ESMF_BaseTime), pointer :: MMbdys(:)
  integer :: mm,i

  IF ( isleap(year,calkindflag) ) THEN
    MMbdys => monthbdysleap
  ELSE
    MMbdys => monthbdys
  ENDIF
  MM = -1
  DO i = 1,MONTHS_PER_YEAR
    IF ( ( basetime >= MMbdys(i-1) ) .AND. ( basetime < MMbdys(i) ) ) THEN
      MM = i
      EXIT
    ENDIF
  ENDDO
  IF ( MM == -1 ) THEN
    CALL wrf_error_fatal( 'nmonthinyearsec:  could not extract month of year from time' )
  ENDIF
  nmonthinyearsec = mm

END function nmonthinyearsec

!==============================================================================
integer function ndayinyearsec(year, basetime, calkindflag)
  ! Compute day of year for year, basetime, cal
  IMPLICIT NONE
  INTEGER, INTENT(in) :: year
  type(ESMF_BaseTime), intent(in) :: basetime
  type(ESMF_CalKind_Flag),intent(in) :: calkindflag
  ! locals
  TYPE(ESMF_BaseTime), pointer :: MMbdys(:)
  TYPE(ESMF_BaseTime) :: tmpbasetime
  integer :: mm

  mm = nmonthinyearsec(year, basetime, calkindflag)

  IF ( isleap(year,calkindflag) ) THEN
    MMbdys => monthbdysleap
  ELSE
    MMbdys => monthbdys
  ENDIF
  tmpbasetime = basetime - MMbdys(mm-1)
  ndayinyearsec = ( tmpbasetime%S / SECONDS_PER_DAY ) + 1

end function ndayinyearsec
!==============================================================================
integer(esmf_kind_i8) function nsecondsinyearmonth(year, month, calkindflag)
  ! Compute number of seconds from start of year for year, month, cal
  IMPLICIT NONE
  INTEGER, INTENT(in) :: year
  INTEGER, INTENT(in) :: month
  type(ESMF_CalKind_Flag),intent(in) :: calkindflag

  ! locals
  TYPE(ESMF_BaseTime), pointer :: MMbdys(:)

  IF ( ( MONTH < 1 ) .OR. ( MONTH > MONTHS_PER_YEAR ) ) THEN
    CALL wrf_error_fatal( 'ERROR nsecondsinyearmonth():  MONTH out of range' )
  ENDIF

  IF ( isleap(year, calkindflag) ) THEN
    MMbdys => monthbdysleap
  ELSE
    MMbdys => monthbdys
  ENDIF

  nsecondsinyearmonth = MMbdys(month-1)%s

end function nsecondsinyearmonth
!==============================================================================

integer FUNCTION ndaysinyear ( year,calkindflag )
  ! Compute the number of days in the given year
  IMPLICIT NONE
  INTEGER, INTENT(IN) :: year
  type(ESMF_CalKind_Flag),intent(in) :: calkindflag

  IF ( isleap( year,calkindflag ) ) THEN
    ndaysinyear = 366
  ELSE
    ndaysinyear = 365
  ENDIF
END FUNCTION ndaysinyear

!==============================================================================

logical FUNCTION isleap ( year, calkindflag )
  ! Compute the number of days in February for the given year
  IMPLICIT NONE
  INTEGER,intent(in)  :: year
  type(ESMF_CalKind_Flag) :: calkindflag
  ! local
  INTEGER :: lyear

  lyear = abs(year)  ! make sure it handles negative years

  isleap = .false. ! By default, February has 28 days ...

  if (calkindflag%caltype == ESMF_CALKIND_GREGORIAN%caltype) then
     IF (MOD(lyear,4).eq.0) THEN
        isleap = .true.  ! But every four years, it has 29 days ...
        IF (MOD(lyear,100).eq.0) THEN
           isleap = .false.  ! Except every 100 years, when it has 28 days ...
           IF (MOD(lyear,400).eq.0) THEN
              isleap = .true.  ! Except every 400 years, when it has 29 days.
           END IF
        END IF
     END IF
  endif

END FUNCTION isleap

!==============================================================================
end module ESMF_CalendarMod
