!> \file mo_temporal_aggregation.f90
!> \brief \copybrief mo_temporal_aggregation
!> \details \copydetails mo_temporal_aggregation

!> \brief Temporal aggregation for time series (averaging)
!> \details This module does temporal aggregation (averaging) of time series
!> \changelog
!! - Pallav Shrestha, Jun 2019
!!   - changed the output argument I/O from INOUT to OUT
!> \authors Oldrich Rakovec, Rohini Kumar, Pallav Shrestha
!> \date October 2015
!> \copyright Copyright 2005-\today, the CHS Developers, Sabine Attinger: All rights reserved.
!! FORCES is released under the LGPLv3+ license \license_note
MODULE mo_temporal_aggregation

  use mo_kind, ONLY : i4, dp
  use mo_julian, ONLY : julday, dec2date
  use mo_constants, ONLY : eps_dp

  IMPLICIT NONE

  PUBLIC :: day2mon_average    ! converts daily time series to monthly
  PUBLIC :: hour2day_average   ! converts hourly time series to daily
  PUBLIC :: day2mon_sum        ! converts daily time series to monthly sums

  ! ------------------------------------------------------------------

  !>        \brief Day-to-month average (day2mon_average)

  !>        \details converts daily time series to monthly

  !>        \param[in] "real(sp/dp) :: daily_data(:)"   array of daily time series
  !>        \param[in] "integer(i4) :: year"            year of the starting time
  !>        \param[in] "integer(i4) :: month"           month of the starting time
  !>        \param[in] "integer(i4) :: day"             day of the starting time
  !>        \param[out] "real(sp/dp) :: mon_average(:)"  array of monthly averaged values
  !>        \param[in] "real(sp/dp), optional :: misval"          missing value definition
  !>        \param[in] "logical, optional     :: rm_misval"       switch to exclude missing values

  !>        \authors Oldrich Rakovec, Rohini Kumar
  !>        \date Oct 2015

  INTERFACE day2mon_average
    MODULE PROCEDURE day2mon_average_dp
  END INTERFACE day2mon_average

  ! ------------------------------------------------------------------

  !>        \brief Hour-to-day average (hour2day_average)

  !>        \details converts hourly time series to daily

  !>        \param[in] "real(sp/dp) :: hourly_data(:)"  array of hourly time series
  !>        \param[in] "integer(i4) :: year"            year of the starting time
  !>        \param[in] "integer(i4) :: month"           month of the starting time
  !>        \param[in] "integer(i4) :: day"             day of the starting time
  !>        \param[in] "integer(i4) :: hour"            hour of the starting time
  !>        \param[inout] "real(sp/dp) :: day_average(:)"         array of daily averaged values
  !>        \param[in] "real(sp/dp), optional :: misval"          missing value definition
  !>        \param[in] "logical, optional     :: rm_misval"       switch to exclude missing values

  !>        \note Hours values should be from 0 to 23 (NOT from 1 to 24!)

  !>        \author Oldrich Rakovec, Rohini Kumar
  !>        \date Oct 2015

  INTERFACE hour2day_average
    MODULE PROCEDURE hour2day_average_dp
  END INTERFACE hour2day_average

  ! ------------------------------------------------------------------

  !>        \brief Day-to-month sum (day2mon_sum)

  !>        \details converts daily time series to monthly sums

  !>        \param[in] "real(sp/dp) :: daily_data(:)"   array of daily time series
  !>        \param[in] "integer(i4) :: year"            year of the starting time
  !>        \param[in] "integer(i4) :: month"           month of the starting time
  !>        \param[in] "integer(i4) :: day"             day of the starting time
  !>        \param[out] "real(sp/dp) :: mon_sum(:)"     array of monthly summed values
  !>        \param[in] "real(sp/dp), optional :: misval"          missing value definition
  !>        \param[in] "logical, optional     :: rm_misval"       switch to exclude missing values

  !>        \author Pallav Kumar Shrestha
  !>        \date Apr 2019

  INTERFACE day2mon_sum
    MODULE PROCEDURE day2mon_sum_dp
  END INTERFACE day2mon_sum

  ! ------------------------------------------------------------------

  PRIVATE

  ! ------------------------------------------------------------------

CONTAINS

  SUBROUTINE day2mon_average_dp(daily_data, yearS, monthS, dayS, mon_avg, misval, rm_misval)

    IMPLICIT NONE

    REAL(dp), dimension(:), INTENT(IN) :: daily_data      ! array of daily data
    INTEGER(i4), INTENT(IN) :: yearS           ! year of the initial time step
    INTEGER(i4), INTENT(IN) :: monthS          ! month of the initial time step
    INTEGER(i4), INTENT(IN) :: dayS            ! day of the initial time step

    REAL(dp), dimension(:), allocatable, INTENT(OUT) :: mon_avg         ! array of the monthly averages

    REAL(dp), optional, INTENT(IN) :: misval          ! missing value definition
    logical, optional, INTENT(IN) :: rm_misval       ! switch to remove missing values

    ! local variables
    INTEGER(i4) :: ndays, tt, kk      ! number of days, indices
    INTEGER(i4) :: start_day, end_day ! size of input array, size of days
    INTEGER(i4) :: y, m
    INTEGER(i4) :: year, month, day    ! variables for date
    INTEGER(i4) :: yearE, monthE, dayE ! vatiables for End date
    REAL(dp) :: newTime

    REAL(dp), dimension(:, :), allocatable :: nCounter_m       ! counter number of days in months (w/ data)
    REAL(dp), dimension(:, :), allocatable :: nCounter_m_full  ! counter number of days in months (complete)
    REAL(dp), dimension(:, :), allocatable :: mon_sum          ! monthly sum

    INTEGER(i4) :: nmonths     ! number of days, number of months
    LOGICAL :: remove      ! switch for considering missing data
    REAL(dp) :: missing  ! switch for reading missing value or default -9999.

    if (present(misval)) then
      missing = misval
    else
      missing = -9999._dp
    end if

    if (present(rm_misval)) then
      remove = rm_misval
    else
      remove = .FALSE.
    end if

    ! get total number of days
    ndays = SIZE(daily_data)

    ! assign initial julian day
    start_day = julday(dayS, monthS, yearS)

    ! calculate last julian day
    end_day = start_day + ndays - 1_i4

    ! get year, month and day of the end date:
    call dec2date(real(end_day, dp), yy = yearE, mm = monthE, dd = dayE)

    ! get number of days with data for each month
    allocate(nCounter_m(yearS : yearE, 12))
    allocate(nCounter_m_full(yearS : yearE, 12))
    allocate(mon_sum(yearS : yearE, 12))
    nCounter_m(:, :) = 0
    nCounter_m_full(:, :) = 0
    mon_sum(:, :) = 0.0_dp

    newTime = real(start_day, dp)
    ! calculate monthly sums
    do tt = 1, (end_day - start_day + 1)
      call dec2date((newTime + tt - 1), yy = year, mm = month, dd = day)
      nCounter_m_full(year, month) = nCounter_m_full(year, month) + 1.0_dp
      if (abs(daily_data(tt) - missing) .lt. eps_dp) cycle
      mon_sum(year, month) = mon_sum(year, month) + daily_data(tt)
      nCounter_m(year, month) = nCounter_m(year, month) + 1.0_dp
    end do

    ! calculate number of months
    nmonths = 0
    do y = yearS, yearE
      do m = 1, 12
        if ((y .EQ. yearS) .AND. (m .LT. monthS)) cycle
        if ((y .EQ. yearE) .AND. (m .GT. monthE)) cycle
        nmonths = nmonths + 1
      end do
    end do

    ! calculate monthly averages
    if(allocated(mon_avg)) deallocate(mon_avg)
    allocate(mon_avg(nmonths))
    mon_avg(:) = missing
    kk = 0
    do y = yearS, yearE
      do m = 1, 12
        if ((y .EQ. yearS) .AND. (m .LT. monthS)) cycle
        if ((y .EQ. yearE) .AND. (m .GT. monthE)) cycle
        kk = kk + 1
        if ((nCounter_m(y, m) .GT. 0) .AND. &
                (abs(nCounter_m_full(y, m) - nCounter_m(y, m)) .LT. eps_dp)) then
          mon_avg(kk) = mon_sum(y, m) / real(nCounter_m(y, m), dp)
        else if ((nCounter_m(y, m) .GT. 0) .AND. remove) then
          mon_avg(kk) = mon_sum(y, m) / real(nCounter_m(y, m), dp)
        end if
      end do
    end do

    deallocate(nCounter_m_full)
    deallocate(nCounter_m)
    deallocate(mon_sum)

  END SUBROUTINE day2mon_average_dp

  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  SUBROUTINE hour2day_average_dp(hourly_data, yearS, monthS, dayS, hourS, day_avg, misval, rm_misval)

    IMPLICIT NONE

    REAL(dp), dimension(:), INTENT(IN) :: hourly_data     ! array of hourly data
    INTEGER(i4), INTENT(IN) :: yearS           ! year of the initial time step
    INTEGER(i4), INTENT(IN) :: monthS          ! month of the initial time step
    INTEGER(i4), INTENT(IN) :: dayS            ! day of the initial time step
    INTEGER(i4), INTENT(IN) :: hourS           ! hour of the initial time step

    REAL(dp), dimension(:), allocatable, INTENT(INOUT) :: day_avg         ! array of the daily averages

    REAL(dp), optional, INTENT(IN) :: misval          ! missing value definition
    logical, optional, INTENT(IN) :: rm_misval       ! switch to remove missing values

    ! local variables
    INTEGER(i4) :: nhours, ndays_dummy, tt, dd, kk
    REAL(dp) :: start_day, end_day   ! assign julian values
    INTEGER(i4) :: yearE, monthE, dayE, hourE, hourEd ! End dates, incl. Dummy

    REAL(dp), dimension(:), allocatable :: nCounter_h       ! counter number of hours in day (w/ data)
    REAL(dp), dimension(:), allocatable :: nCounter_h_full  ! counter number of hours in day (complete)
    REAL(dp), dimension(:), allocatable :: day_sum          ! daily sum

    LOGICAL :: remove   ! switch for considering missing data
    REAL(dp) :: missing  ! switch for reading missing value or default -9999.

    if (present(misval)) then
      missing = misval
    else
      missing = -9999._dp
    end if

    if (present(rm_misval)) then
      remove = rm_misval
    else
      remove = .FALSE.
    end if

    ! get total number of hours
    nhours = SIZE(hourly_data)
    ! assign initial julian day
    start_day = julday(dayS, monthS, yearS) - 0.5_dp + real(hourS, dp) / 24._dp

    ! calculate last julian day
    end_day = start_day + real(nhours - 1._dp, dp) / 24._dp

    ! get year, month and day of the end date
    call dec2date(end_day, yy = yearE, mm = monthE, dd = dayE, hh = hourE)

    ! get largerst possible number of calendar days
    ndays_dummy = ceiling(real(nhours, dp) / 24._dp + 2._dp)

    allocate(day_sum(ndays_dummy))
    allocate(nCounter_h(ndays_dummy))
    allocate(nCounter_h_full(ndays_dummy))
    day_sum(:) = 0.0_dp
    nCounter_h(:) = 0
    nCounter_h_full(:) = 0

    ! calculate daily sums
    dd = 1
    do tt = 1, nhours
      call dec2date(start_day + real(tt - 1, dp) / 24._dp, hh = hourEd)
      nCounter_h_full(dd) = nCounter_h_full(dd) + 1
      if (abs(hourly_data(tt) - missing) .lt. eps_dp) then
        day_sum(dd) = day_sum(dd)
      else
        day_sum(dd) = day_sum(dd) + hourly_data(tt)
        nCounter_h(dd) = nCounter_h(dd) + 1
      end if
      if ((hourEd .EQ. 23) .AND. (tt .LT. nhours)) dd = dd + 1
    end do

    ! dd is the total number of calendar days, between hourS and hourE
    allocate(day_avg(dd))
    day_avg(:) = missing

    ! calculate daily average
    do kk = 1, dd
      if ((nCounter_h(kk) .GT. 0) .AND. &
              (abs(nCounter_h_full(kk) - nCounter_h(kk)) .LT. eps_dp)) then
        day_avg(kk) = day_sum(kk) / real(nCounter_h(kk), dp)
      else if ((nCounter_h(kk) .GT. 0) .AND. remove) then
        day_avg(kk) = day_sum(kk) / real(nCounter_h(kk), dp)
      end if
    end do

    deallocate(nCounter_h_full)
    deallocate(nCounter_h)
    deallocate(day_sum)

  END SUBROUTINE hour2day_average_dp

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

  SUBROUTINE day2mon_sum_dp(daily_data, yearS, monthS, dayS, mon_sum, misval, rm_misval)

    IMPLICIT NONE

    REAL(dp), dimension(:), INTENT(IN) :: daily_data      ! array of daily data
    INTEGER(i4), INTENT(IN) :: yearS           ! year of the initial time step
    INTEGER(i4), INTENT(IN) :: monthS          ! month of the initial time step
    INTEGER(i4), INTENT(IN) :: dayS            ! day of the initial time step

    REAL(dp), dimension(:), allocatable, INTENT(OUT) :: mon_sum         ! array of the monthly sums

    REAL(dp), optional, INTENT(IN) :: misval          ! missing value definition
    logical, optional, INTENT(IN) :: rm_misval       ! switch to remove missing values

    ! local variables
    INTEGER(i4) :: ndays, tt, kk      ! number of days, indices
    INTEGER(i4) :: start_day, end_day ! size of input array, size of days
    INTEGER(i4) :: y, m
    INTEGER(i4) :: year, month, day    ! variables for date
    INTEGER(i4) :: yearE, monthE, dayE ! vatiables for End date
    REAL(dp) :: newTime

    REAL(dp), dimension(:, :), allocatable :: nCounter_m       ! counter number of days in months (w/ data)
    REAL(dp), dimension(:, :), allocatable :: nCounter_m_full  ! counter number of days in months (complete)
    REAL(dp), dimension(:, :), allocatable :: mon_sum_temp_2d  ! monthly sum temporary variable
    REAL(dp), dimension(:),    allocatable :: mon_sum_temp_1d  ! monthly sum temporary variable

    INTEGER(i4) :: nmonths     ! number of days, number of months
    LOGICAL :: remove      ! switch for considering missing data
    REAL(dp) :: missing  ! switch for reading missing value or default -9999.

    if (present(misval)) then
      missing = misval
    else
      missing = -9999._dp
    end if

    if (present(rm_misval)) then
      remove = rm_misval
    else
      remove = .FALSE.
    end if

    ! get total number of days
    ndays = SIZE(daily_data)

    ! assign initial julian day
    start_day = julday(dayS, monthS, yearS)

    ! calculate last julian day
    end_day = start_day + ndays - 1_i4

    ! get year, month and day of the end date:
    call dec2date(real(end_day, dp), yy = yearE, mm = monthE, dd = dayE)

    ! get number of days with data for each month
    allocate(nCounter_m(yearS : yearE, 12))
    allocate(nCounter_m_full(yearS : yearE, 12))
    allocate(mon_sum_temp_2d(yearS : yearE, 12))
    nCounter_m(:, :) = 0
    nCounter_m_full(:, :) = 0
    mon_sum_temp_2d(:, :) = 0.0_dp

    newTime = real(start_day, dp)
    ! calculate monthly sums
    do tt = 1, (end_day - start_day + 1)
      call dec2date((newTime + tt - 1), yy = year, mm = month, dd = day)
      nCounter_m_full(year, month) = nCounter_m_full(year, month) + 1.0_dp
      if (abs(daily_data(tt) - missing) .lt. eps_dp) cycle
      mon_sum_temp_2d(year, month) = mon_sum_temp_2d(year, month) + daily_data(tt)
      nCounter_m(year, month) = nCounter_m(year, month) + 1.0_dp
    end do

    ! calculate number of months
    nmonths = 0
    do y = yearS, yearE
      do m = 1, 12
        if ((y .EQ. yearS) .AND. (m .LT. monthS)) cycle
        if ((y .EQ. yearE) .AND. (m .GT. monthE)) cycle
        nmonths = nmonths + 1
      end do
    end do


    ! store monthly sums
    allocate(mon_sum_temp_1d(nmonths))
    mon_sum_temp_1d(:) = missing
    kk = 0
    do y = yearS, yearE
      do m = 1, 12
        if ((y .EQ. yearS) .AND. (m .LT. monthS)) cycle
        if ((y .EQ. yearE) .AND. (m .GT. monthE)) cycle
        kk = kk + 1
        if ((nCounter_m(y, m) .GT. 0) .AND. &
                (abs(nCounter_m_full(y, m) - nCounter_m(y, m)) .LT. eps_dp)) then
          mon_sum_temp_1d(kk) = mon_sum_temp_2d(y, m)
        else if ((nCounter_m(y, m) .GT. 0) .AND. remove) then
          mon_sum_temp_1d(kk) = mon_sum_temp_2d(y, m)
        end if
      end do
    end do

    if(allocated(mon_sum)) deallocate(mon_sum)
    allocate(mon_sum(nmonths))
    mon_sum = mon_sum_temp_1d

    deallocate(nCounter_m_full)
    deallocate(nCounter_m)
    deallocate(mon_sum_temp_2d)
    deallocate(mon_sum_temp_1d)

  END SUBROUTINE day2mon_sum_dp

END MODULE mo_temporal_aggregation