! SUMMA - Structure for Unifying Multiple Modeling Alternatives
! Copyright (C) 2014-2020 NCAR/RAL; University of Saskatchewan; University of Washington
!
! This file is part of SUMMA
!
! For more information see: http://www.ral.ucar.edu/projects/summa
!
! This program is free software: you can redistribute it and/or modify
! it under the terms of the GNU General Public License as published by
! the Free Software Foundation, either version 3 of the License, or
! (at your option) any later version.
!
! This program is distributed in the hope that it will be useful,
! but WITHOUT ANY WARRANTY; without even the implied warranty of
! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
! GNU General Public License for more details.
!
! You should have received a copy of the GNU General Public License
! along with this program.  If not, see <http://www.gnu.org/licenses/>.

module paramCheck_module
! define numerical recipes data type
USE nrtype
! define look-up values for the choice of method to combine and sub-divide snow layers
USE mDecisions_module,only:&
 sameRulesAllLayers, & ! SNTHERM option: same combination/sub-dividion rules applied to all layers
 rulesDependLayerIndex ! CLM option: combination/sub-dividion rules depend on layer index
implicit none
private
public::paramCheck
contains


 ! ************************************************************************************************
 ! public subroutine paramCheck: check consistency of model parameters
 ! ************************************************************************************************
 subroutine paramCheck(mpar_data,err,message)
 ! model decisions
 USE globalData,only:model_decisions  ! model decision structure
 USE var_lookup,only:iLookDECISIONS   ! named variables for elements of the decision structure
 ! SUMMA look-up variables
 USE data_types,only:var_dlength      ! data vector with variable length dimension (dp): x%var(:)%dat(:)
 USE var_lookup,only:iLookPARAM       ! named variables for elements of the data structures
 implicit none
 ! define input
 type(var_dlength),intent(in)    :: mpar_data            ! model parameters
 ! define output
 integer(i4b),intent(out)        :: err                  ! error code
 character(*),intent(out)        :: message              ! error message
 ! local variables
 integer(i4b)                    :: iLayer               ! index of model layers
 real(dp),dimension(5)           :: zminLayer            ! minimum layer depth in each layer (m)
 real(dp),dimension(4)           :: zmaxLayer_lower      ! lower value of maximum layer depth
 real(dp),dimension(4)           :: zmaxLayer_upper      ! upper value of maximum layer depth
 ! Start procedure here
 err=0; message="paramCheck/"

 ! *****
 ! * check that the snow layer bounds are OK...
 ! ********************************************

 ! select option for combination/sub-division of snow layers
 select case(model_decisions(iLookDECISIONS%snowLayers)%iDecision)
  ! SNTHERM option
  case(sameRulesAllLayers)
   if(mpar_data%var(iLookPARAM%zmax)%dat(1)/mpar_data%var(iLookPARAM%zmin)%dat(1) < 2.5_dp)then
    message=trim(message)//'zmax must be at least 2.5 times larger than zmin: this avoids merging layers that have just been divided'
    err=20; return
   end if
  ! CLM option
  case(rulesDependLayerIndex)
   ! (build vectors of min/max)
   zminLayer       = (/mpar_data%var(iLookPARAM%zminLayer1)%dat(1),&
                       mpar_data%var(iLookPARAM%zminLayer2)%dat(1),&
                       mpar_data%var(iLookPARAM%zminLayer3)%dat(1),&
                       mpar_data%var(iLookPARAM%zminLayer4)%dat(1),&
                       mpar_data%var(iLookPARAM%zminLayer5)%dat(1)/)
   zmaxLayer_lower = (/mpar_data%var(iLookPARAM%zmaxLayer1_lower)%dat(1),&
                       mpar_data%var(iLookPARAM%zmaxLayer2_lower)%dat(1),&
                       mpar_data%var(iLookPARAM%zmaxLayer3_lower)%dat(1),&
                       mpar_data%var(iLookPARAM%zmaxLayer4_lower)%dat(1)/)
   zmaxLayer_upper = (/mpar_data%var(iLookPARAM%zmaxLayer1_upper)%dat(1),&
                       mpar_data%var(iLookPARAM%zmaxLayer2_upper)%dat(1),&
                       mpar_data%var(iLookPARAM%zmaxLayer3_upper)%dat(1),&
                       mpar_data%var(iLookPARAM%zmaxLayer4_upper)%dat(1)/)
   ! (check consistency)
   do iLayer=1,4  ! NOTE: the lower layer does not have a maximum value
    ! ensure that we have higher maximum thresholds for sub-division when fewer number of layers
    if(zmaxLayer_lower(iLayer) < zmaxLayer_upper(iLayer))then
     write(message,'(a,2(i0,a))') trim(message)//'expect the maximum threshold for sub-division in the case where there is only ', &
                                  iLayer,' layer(s) is greater than the maximum threshold for sub-division in the case where there are > ',&
                                  iLayer,' layer(s)'
     err=20; return
    end if
    ! ensure that the maximum thickness is 3 times greater than the minimum thickness
    if(zmaxLayer_upper(iLayer)/zminLayer(iLayer) < 2.5_dp .or. zmaxLayer_upper(iLayer)/zminLayer(iLayer+1) < 2.5_dp)then
     write(*,'(a,1x,3(f20.10,1x))') 'zmaxLayer_upper(iLayer), zminLayer(iLayer), zminLayer(iLayer+1) = ', &
                                     zmaxLayer_upper(iLayer), zminLayer(iLayer), zminLayer(iLayer+1)
     write(message,'(a,3(i0,a))') trim(message)//'zmaxLayer_upper for layer ',iLayer,' must be 2.5 times larger than zminLayer for layers ',&
                                  iLayer,' and ',iLayer+1,': this avoids merging layers that have just been divided'
     err=20; return
    end if
   end do  ! loop through layers
  case default; err=20; message=trim(message)//'unable to identify option to combine/sub-divide snow layers'; return
 end select ! (option to combine/sub-divide snow layers)

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

 ! *****
 ! * check parameter dependencies...
 ! *********************************

 ! associations
 associate(&
 ! canopy geometry
 heightCanopyTop        => mpar_data%var(iLookPARAM%heightCanopyTop)%dat(1),   & ! intent(in): [dp] height at the top of the vegetation canopy (m)
 heightCanopyBottom     => mpar_data%var(iLookPARAM%heightCanopyBottom)%dat(1),& ! intent(in): [dp] height at the bottom of the vegetation canopy (m)
 ! transpiration
 critSoilWilting        => mpar_data%var(iLookPARAM%critSoilWilting)%dat(1),   & ! intent(in): [dp] critical vol. liq. water content when plants are wilting (-)
 critSoilTranspire      => mpar_data%var(iLookPARAM%critSoilTranspire)%dat(1), & ! intent(in): [dp] critical vol. liq. water content when transpiration is limited (-)
 ! soil properties
 fieldCapacity          => mpar_data%var(iLookPARAM%fieldCapacity)%dat(1),     & ! intent(in): [dp]    field capacity (-)
 theta_sat              => mpar_data%var(iLookPARAM%theta_sat)%dat,            & ! intent(in): [dp(:)] soil porosity (-)
 theta_res              => mpar_data%var(iLookPARAM%theta_res)%dat             & ! intent(in): [dp(:)] soil residual volumetric water content (-)
 ) ! associations to parameters

 ! check canopy geometry
 if(heightCanopyTop < heightCanopyBottom)then
  write(message,'(a,i0,a)') trim(message)//'height of canopy top is less than the height of the canopy bottom'
  err=20; return
 endif

 ! check that the maximum transpiration limit is within bounds
 if( any(critSoilTranspire > theta_sat) .or. any(critSoilTranspire < theta_res) )then
  print*, 'theta_res         = ', theta_res
  print*, 'theta_sat         = ', theta_sat
  print*, 'critSoilTranspire = ', critSoilTranspire
  message=trim(message)//'critSoilTranspire parameter is out of range '// &
                         '[NOTE: if overwriting Noah-MP soil table values in paramTrial, must overwrite all soil parameters]'
  err=20; return
 end if

 ! check that the soil wilting point is within bounds
 if( any(critSoilWilting > theta_sat) .or. any(critSoilWilting < theta_res) )then
  print*, 'theta_res       = ', theta_res
  print*, 'theta_sat       = ', theta_sat
  print*, 'critSoilWilting = ', critSoilWilting
  message=trim(message)//'critSoilWilting parameter is out of range '// &
                         '[NOTE: if overwriting Noah-MP soil table values in paramTrial, must overwrite all soil parameters]'
  err=20; return
 end if

 ! check that the field capacity is within bounds
 if( any(fieldCapacity > theta_sat) .or. any(fieldCapacity < theta_res) )then
  print*, 'theta_res     = ', theta_res
  print*, 'theta_sat     = ', theta_sat
  print*, 'fieldCapacity = ', fieldCapacity
  message=trim(message)//'fieldCapacity parameter is out of range '// &
                         '[NOTE: if overwriting Noah-MP soil table values in paramTrial, must overwrite all soil parameters]'
  err=20; return
 end if

 ! check transpiration
 if(critSoilTranspire < critSoilWilting)then
  write(message,'(a,i0,a)') trim(message)//'critical point for transpiration is less than the wilting point'
  err=20; return
 endif

 ! check porosity
 if( any(theta_sat < theta_res) )then
  write(message,'(a,i0,a)') trim(message)//'porosity is less than the residual liquid water content'
  err=20; return
 endif

 ! end associations to parameter dependencies
 end associate

 end subroutine paramCheck


end module paramCheck_module