module dynVarTimeInterpMod

#include "shr_assert.h" 

  !---------------------------------------------------------------------------
  ! !DESCRIPTION:
  ! Contains a derived type and associated methods that extend the base class,
  ! dyn_var_type. The type defined here is for variables that SHOULD be interpolated in
  ! time. For variables of this type, the data have the value given on year Y of the file
  ! at midnight on Jan. 1 at the start of year Y. The value then gets linearly
  ! interpolated over the year, so that by Dec. 31 of year Y, the value is close to the
  ! file's value at year Y+1. Before the start of the time series, the data are fixed at
  ! their value from the first year in the file; after the end of the time series, the
  ! data are fixed at their value from the last year in the file. If the last year on the
  ! file is X, then the data are fixed at this last value (and thus do not vary)
  ! throughout year X.
  
  ! !USES:
  use shr_kind_mod   , only : r8 => shr_kind_r8
  use dynVarMod      , only : dyn_var_type

  implicit none
  private
  save

  ! !PUBLIC TYPES:
  public :: dyn_var_time_interp_type

  type, extends(dyn_var_type) :: dyn_var_time_interp_type
     private
     ! Note that data are stored as 1-d, then converted to the appropriate dimensionality
     ! as needed
     real(r8), allocatable :: data_at_tlower(:)  ! data at time time_index_lower
     real(r8), allocatable :: data_at_tupper(:)  ! data at time time_index_upper

     integer :: time_index_lower  ! current time_index_lower curresponding to data_at_tlower
     integer :: time_index_upper  ! current time_index_upper curresponding to data_at_tupper
   contains
     generic :: get_current_data => &      ! Get the current value of the data
          get_current_data_1d, get_current_data_2d, get_current_data_3d
     procedure :: get_current_data_1d  ! Get the current value of the data, for a 1-d variable
     procedure :: get_current_data_2d  ! Get the current value of the data, for a 2-d variable
     procedure :: get_current_data_3d  ! Get the current value of the data, for a 3-d variable
     procedure :: read_data_if_needed  ! Read the next time slice of data, if necessary
  end type dyn_var_time_interp_type

  interface dyn_var_time_interp_type
     module procedure constructor  ! initialize a new dyn_var_time_interp object
  end interface dyn_var_time_interp_type

contains

  ! ======================================================================
  ! Constructors
  ! ======================================================================

  !-----------------------------------------------------------------------
  type(dyn_var_time_interp_type) function constructor( &
       dyn_file, varname, dim1name, conversion_factor, &
       do_check_sums_equal_1, data_shape)
    
    ! !DESCRIPTION:
    ! Creates an object of type dyn_var_time_interp_type. This also reads the first
    ! set of data.
    
    ! Assumes that dyn_file has already been initialized.
    
    ! !USES:
    use dynFileMod     , only : dyn_file_type
    use dynTimeInfoMod , only : time_info_type
    
    ! !ARGUMENTS:
    type(dyn_file_type), target, intent(in) :: dyn_file ! file containing this variable
    character(len=*), intent(in) :: varname             ! variable name on file
    character(len=*), intent(in) :: dim1name            ! dim1name on file
    real(r8), intent(in) :: conversion_factor           ! data are DIVIDED by conversion_factor immediately after reading them

     ! Only relevant for 2-d variables: should we check to make sure that all sums equal 1?
    logical, intent(in) :: do_check_sums_equal_1
    
    ! Shape of data; max number of dimensions is given by dyn_var_max_dims in dynVarMod.
    ! First dimension is the spatial dimension.
    integer, intent(in) :: data_shape(:)
    !-----------------------------------------------------------------------

    ! Set metadata common to all dyn_var_type objects
    call constructor%set_metadata( &
         dyn_file=dyn_file, &
         varname=varname, &
         dim1name=dim1name, &
         conversion_factor=conversion_factor, &
         do_check_sums_equal_1=do_check_sums_equal_1, &
         data_shape=data_shape)

    ! Allocate space for data
    allocate(constructor%data_at_tlower(product(data_shape)))
    allocate(constructor%data_at_tupper(product(data_shape)))

    ! Read first set of data
    constructor%time_index_lower = dyn_file%time_info%get_time_index_lower()
    constructor%time_index_upper = dyn_file%time_info%get_time_index_upper()
    call constructor%read_variable(constructor%time_index_lower, constructor%data_at_tlower)
    call constructor%read_variable(constructor%time_index_upper, constructor%data_at_tupper)
  end function constructor

  ! ======================================================================
  ! Public methods
  ! ======================================================================

  ! The following specific procedures are NOT actually public, but they can be accessed
  ! via the generic type-bound procedure, get_current_data

  ! DIMS 1,2,3
  !-----------------------------------------------------------------------
  subroutine get_current_data_{DIMS}d(this, cur_data)
    
    ! !DESCRIPTION:
    ! Get the current, interpolated value of the data, in cur_data. cur_data should have
    ! the same dimensionality as the underlying data, as given by the data_shape argument
    ! that was passed to the constructor.
     
    ! If necessary, new data are read from the file.
    
    ! Should be called once per time step, AFTER calling set_current_year on the
    ! underlying dyn_file variable
    
    ! !USES:
    use elm_time_manager, only : get_curr_yearfrac
    
    ! !ARGUMENTS:
    class(dyn_var_time_interp_type) , intent(inout) :: this             ! this object
    real(r8)                        , intent(out)   :: cur_data{DIMSTR} ! current value of data
    
    ! !LOCAL VARIABLES:
    integer               :: ndims          ! ndims of data in 'this'
    real(r8)              :: wt1            ! weight of time1 (the left-hand time point)
    real(r8), allocatable :: cur_data_1d(:) ! 1-d version of data at the current time
    character(len=*), parameter :: subname = 'get_current_data_{DIMS}d'
    !-----------------------------------------------------------------------

    ! Do some error checking
    ndims = size(this%get_data_shape())
    SHR_ASSERT({DIMS} == ndims, subname//' ERROR: # dims of output argument must match ndims')
    SHR_ASSERT_ALL((shape(cur_data) == this%get_data_shape()), subname//' ERROR: shape of cur_data must match shape of data')

    ! Get current data, using a temporal weighting of the data at time 1 and the data at
    ! time 2
    call this%read_data_if_needed()
    allocate(cur_data_1d(size(this%data_at_tlower)))
    wt1 = 1.0_r8 - get_curr_yearfrac()
    cur_data_1d(:) = this%data_at_tupper(:) + wt1*(this%data_at_tlower(:) - this%data_at_tupper(:))
    cur_data = reshape(cur_data_1d, shape(cur_data))
    deallocate(cur_data_1d)

  end subroutine get_current_data_{DIMS}d

  ! ======================================================================
  ! Private methods
  ! ======================================================================

  !-----------------------------------------------------------------------
  subroutine read_data_if_needed(this)
    
    ! !DESCRIPTION:
    ! Determine if new data need to be read from the file; if so, read them.
     
    ! We need to read new data (or at least copy them) if the current time on dyn_file
    ! disagrees with the time for which we currently have stored data, for either time time_index_lower
    ! or time time_index_upper
    
    ! !USES:
    use dynFileMod     , only : dyn_file_type
    use dynTimeInfoMod , only : time_info_type
    
    ! !ARGUMENTS:
    class(dyn_var_time_interp_type), intent(inout) :: this   ! this object
    
    ! !LOCAL VARIABLES:
    type(dyn_file_type), pointer :: dyn_file ! the dyn_file of this object
    integer :: time_index_lower_cur                       ! current value of time_index_lower on dyn_fileb
    integer :: time_index_upper_cur                       ! current value of time_index_upper on dyn_fileb
    
    character(len=*), parameter :: subname = 'read_data_if_needed'
    !-----------------------------------------------------------------------

    dyn_file => this%get_dyn_file()
    time_index_lower_cur = dyn_file%time_info%get_time_index_lower()
    time_index_upper_cur = dyn_file%time_info%get_time_index_upper()

    ! If time_index_lower time has changed, get a new set of data for time time_index_lower
    if (time_index_lower_cur /= this%time_index_lower) then

       ! The typical case is that we have moved forward by a single time; thus we can
       ! avoid an extra read by simply setting the new data at time_index_lower equal to the old data
       ! at time_index_upper
       if (time_index_lower_cur == this%time_index_upper) then
          this%data_at_tlower(:) = this%data_at_tupper(:)

       ! Otherwise, handle the general (but atypical) case where we have not moved
       ! forward by a single time
       else
          call this%read_variable(time_index_lower_cur, this%data_at_tlower)
       end if

       this%time_index_lower = time_index_lower_cur
    end if

    ! If time_index_upper time has changed, read a new set of data for time time_index_upper
    if (time_index_upper_cur /= this%time_index_upper) then
       call this%read_variable(time_index_upper_cur, this%data_at_tupper)
       this%time_index_upper = time_index_upper_cur
    end if

  end subroutine read_data_if_needed


end module dynVarTimeInterpMod
