! radiation_single_level.F90 - Derived type for single-level fields
!
! (C) Copyright 2014- ECMWF.
!
! This software is licensed under the terms of the Apache Licence Version 2.0
! which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
!
! In applying this licence, ECMWF does not waive the privileges and immunities
! granted to it by virtue of its status as an intergovernmental organisation
! nor does it submit to any jurisdiction.
!
! Author:  Robin Hogan
! Email:   r.j.hogan@ecmwf.int
!
! Modifications
!   2019-01-14  R. Hogan  Added out_of_physical_bounds routine
!   2019-01-18  R. Hogan  Added weighted albedo mapping

module radiation_single_level

  use parkind1, only : jprb

  implicit none
  public

  !---------------------------------------------------------------------
  ! Derived type to contain variables that don't vary with height;
  ! mostly they are surface properties
  type single_level_type
    ! Note that skin temperature is only defined if
    ! is_simple_surface==.true.
    real(jprb), allocatable, dimension(:) :: &
         &   cos_sza, &       ! (ncol) Cosine of solar zenith angle
         &   skin_temperature ! (ncol) Skin temperature (K)

    ! Shortwave albedo: if sw_albedo_direct is not allocated then
    ! sw_albedo will be used for both direct and diffuse solar
    ! radiation; otherwise, sw_albedo will be used for diffuse
    ! radiation and sw_albedo_direct for direct radiation.
    real(jprb), allocatable, dimension(:,:) :: &
         &   sw_albedo, &        ! (ncol,nalbedobands)
         &   sw_albedo_direct    ! (ncol,nalbedobands) 
    
    ! If is_simple_surface==.true., we provide longwave emissivity
    ! coarser spectral intervals, while if
    ! is_simple_surface==.false. it will be generated by the surface
    ! radiation scheme and may optionally be on the full spectral grid
    ! of the atmospheric model.
    real(jprb), allocatable, dimension(:,:) :: &
         &   lw_emissivity       ! (ncol,nemissbands) If
    ! is_simple_surface==.false. then we specify the emission instead
    ! of the skin temperature, and again this may be on the emissivity
    ! bands or the full spectral grid
    real(jprb), allocatable, dimension(:,:) :: &
         &   lw_emission         ! (ncol,nemissbands)

    ! Incoming solar irradiance at the Earth is specified as the total
    ! across the shortwave spectrum. Note that this needs to be
    ! adjusted for Earth-Sun distance, solar cycle etc. To get
    ! normalized fluxes out, simply set this to 1.0.
    real(jprb) :: solar_irradiance = 1366.0_jprb ! W m-2

    ! In addition to the effect of the solar cycle on total solar
    ! irradiance (which the user is expected to input via
    ! solar_irradiance), it can change the balance between different
    ! parts of the solar spectrum, e.g. more UV at solar maximum. This
    ! variable provides the scaling to be applied to the spectral
    ! amplitude of the solar cycle (assuming it has been provided),
    ! e.g. 1.0 for solar maximum, -1.0 for solar maximum and 0.0 for
    ! a mean solar spectrum.
    real(jprb) :: spectral_solar_cycle_multiplier = 0.0_jprb
    
    ! If config%use_spectral_solar_irradiance==true then this will be
    ! scaled by spectral_solar_scaling
    real(jprb), allocatable, dimension(:) :: &
         &   spectral_solar_scaling ! (n_bands_sw)
 
    ! Seed for random number generator in McICA; it is expected that
    ! the host model generates this from the model time, longitude
    ! and latitude, in order that the model is deterministic
    integer, allocatable, dimension(:) :: iseed ! (ncol)

    ! Is the underlying surface a simple single "flat" tile? If so we
    ! describe it with skin_temperature and lw_emissivity. Otherwise
    ! we describe it with lw_emission and lw_emissivity coming out of
    ! the surface radiation scheme.
    logical :: is_simple_surface = .true.

    ! If is_simple_surface==.false., do we use the full number of g
    ! points for dimensioning sw_albedo, sw_albedo_direct and
    ! lw_emission?
    !logical :: use_full_spectrum_sw = .false.
    !logical :: use_full_spectrum_lw = .true.

  contains
    procedure :: allocate   => allocate_single_level
    procedure :: deallocate => deallocate_single_level
    procedure :: init_seed_simple
    procedure :: get_albedos
    procedure :: out_of_physical_bounds

  end type single_level_type

contains
  
  !---------------------------------------------------------------------
  ! Allocate the arrays of a single-level type
  subroutine allocate_single_level(this, ncol, nalbedobands, nemisbands, &
       &                           use_sw_albedo_direct, is_simple_surface)

    use yomhook, only : lhook, dr_hook, jphook
    USE radiation_config, ONLY : config_type

    type(config_type) :: config
    class(single_level_type), intent(inout) :: this
    integer,                  intent(in)    :: ncol, nalbedobands, nemisbands
    logical,        optional, intent(in)    :: use_sw_albedo_direct
    logical,        optional, intent(in)    :: is_simple_surface

    real(jphook) :: hook_handle

    if (lhook) call dr_hook('radiation_single_level:allocate',0,hook_handle)

    call this%deallocate()

    if (present(is_simple_surface)) then
      this%is_simple_surface = is_simple_surface
    else
      this%is_simple_surface = .true.
    end if

    allocate(this%cos_sza(ncol))

    if (this%is_simple_surface) then
      allocate(this%skin_temperature(ncol))
    else
      allocate(this%lw_emission(ncol, nemisbands))
    end if
    allocate(this%lw_emissivity(ncol, nemisbands))

    allocate(this%sw_albedo(ncol, nalbedobands))

    allocate(this%spectral_solar_scaling(config%n_bands_sw))

    if (present(use_sw_albedo_direct)) then
      if (use_sw_albedo_direct) then
        allocate(this%sw_albedo_direct(ncol, nalbedobands))
      end if
    end if

    allocate(this%iseed(ncol))

    if (lhook) call dr_hook('radiation_single_level:allocate',1,hook_handle)

  end subroutine allocate_single_level


  !---------------------------------------------------------------------
  ! Deallocate the arrays of a single-level type
  subroutine deallocate_single_level(this)

    use yomhook, only : lhook, dr_hook, jphook

    class(single_level_type), intent(inout) :: this

    real(jphook) :: hook_handle

    if (lhook) call dr_hook('radiation_single_level:deallocate',0,hook_handle)

    if (allocated(this%cos_sza)) then
      deallocate(this%cos_sza)
    end if
    if (allocated(this%skin_temperature)) then
      deallocate(this%skin_temperature)
    end if
    if (allocated(this%sw_albedo)) then
      deallocate(this%sw_albedo)
    end if
    if (allocated(this%sw_albedo_direct)) then
      deallocate(this%sw_albedo_direct)
    end if
    if (allocated(this%lw_emissivity)) then
      deallocate(this%lw_emissivity)
    end if
    if (allocated(this%lw_emission)) then
      deallocate(this%lw_emission)
    end if
    if (allocated(this%spectral_solar_scaling)) then
      deallocate(this%spectral_solar_scaling)
    end if
    if (allocated(this%iseed)) then
      deallocate(this%iseed)
    end if

    if (lhook) call dr_hook('radiation_single_level:deallocate',1,hook_handle)

  end subroutine deallocate_single_level


  !---------------------------------------------------------------------
  ! Unimaginative initialization of random-number seeds
  subroutine init_seed_simple(this, istartcol, iendcol)
    class(single_level_type), intent(inout) :: this
    integer, intent(in)                     :: istartcol, iendcol

    integer :: jcol

    if (.not. allocated(this%iseed)) then
      allocate(this%iseed(istartcol:iendcol))
    end if

    do jcol = istartcol,iendcol
      this%iseed(jcol) = jcol
    end do
  end subroutine init_seed_simple


  !---------------------------------------------------------------------
  ! Extract the shortwave and longwave surface albedos in each g-point
  subroutine get_albedos(this, istartcol, iendcol, config, &
       &                 sw_albedo_direct, sw_albedo_diffuse, lw_albedo)

    use radiation_config, only : config_type
    use radiation_io,     only : nulerr, radiation_abort
    use yomhook,          only : lhook, dr_hook, jphook

    class(single_level_type), intent(in) :: this
    type(config_type),        intent(in) :: config
    integer,                  intent(in) :: istartcol, iendcol

    ! The longwave albedo of the surface in each longwave g-point;
    ! note that these are weighted averages of the values from
    ! individual tiles
    real(jprb), intent(out), optional &
         &  :: lw_albedo(config%n_g_lw, istartcol:iendcol)

    ! Direct and diffuse shortwave surface albedo in each shortwave
    ! g-point; note that these are weighted averages of the values
    ! from individual tiles
    real(jprb), intent(out), dimension(config%n_g_sw, istartcol:iendcol) &
         &  :: sw_albedo_direct, sw_albedo_diffuse

    ! Temporary storage of albedo in ecRad bands
    real(jprb) :: sw_albedo_band(istartcol:iendcol, config%n_bands_sw)
    real(jprb) :: lw_albedo_band(istartcol:iendcol, config%n_bands_lw)

    ! Number of albedo bands
    integer :: nalbedoband

    ! Loop indices for ecRad bands and albedo bands
    integer :: jband, jalbedoband, jcol

    real(jphook) :: hook_handle

    if (lhook) call dr_hook('radiation_single_level:get_albedos',0,hook_handle)

    if (config%do_sw) then
      ! Albedos/emissivities are stored in single_level in their own
      ! spectral intervals and with column as the first dimension
      if (config%use_canopy_full_spectrum_sw) then
        ! Albedos provided in each g point
        if (size(this%sw_albedo,2) /= config%n_g_sw) then
          write(nulerr,'(a,i0,a)') '*** Error: single_level%sw_albedo does not have the expected ', &
               &  config%n_g_sw, ' spectral intervals'
          call radiation_abort()
        end if
        sw_albedo_diffuse = transpose(this%sw_albedo(istartcol:iendcol,:))
        if (allocated(this%sw_albedo_direct)) then
          sw_albedo_direct = transpose(this%sw_albedo_direct(istartcol:iendcol,:))
        end if
      else if (.not. config%do_nearest_spectral_sw_albedo) then
        ! Albedos averaged accurately to ecRad spectral bands
        nalbedoband = size(config%sw_albedo_weights,1)
        if (size(this%sw_albedo,2) /= nalbedoband) then
          write(nulerr,'(a,i0,a)') '*** Error: single_level%sw_albedo does not have the expected ', &
               &  nalbedoband, ' bands'
          call radiation_abort()
        end if

        sw_albedo_band = 0.0_jprb
        do jband = 1,config%n_bands_sw
          do jalbedoband = 1,nalbedoband
            if (config%sw_albedo_weights(jalbedoband,jband) /= 0.0_jprb) then
              do jcol = istartcol,iendcol
                sw_albedo_band(jcol,jband) &
                    &  = sw_albedo_band(jcol,jband) & 
                    &  + config%sw_albedo_weights(jalbedoband,jband) &
                    &    * this%sw_albedo(jcol, jalbedoband)
              end do
            end if
          end do
        end do

        sw_albedo_diffuse = transpose(sw_albedo_band(istartcol:iendcol, &
             &                              config%i_band_from_reordered_g_sw))
        if (allocated(this%sw_albedo_direct)) then
          sw_albedo_band = 0.0_jprb
          do jband = 1,config%n_bands_sw
            do jalbedoband = 1,nalbedoband
              if (config%sw_albedo_weights(jalbedoband,jband) /= 0.0_jprb) then
                sw_albedo_band(istartcol:iendcol,jband) &
                     &  = sw_albedo_band(istartcol:iendcol,jband) & 
                     &  + config%sw_albedo_weights(jalbedoband,jband) &
                     &    * this%sw_albedo_direct(istartcol:iendcol, jalbedoband)
              end if
            end do
          end do
          sw_albedo_direct = transpose(sw_albedo_band(istartcol:iendcol, &
               &                             config%i_band_from_reordered_g_sw))
        else
          sw_albedo_direct = sw_albedo_diffuse
        end if
      else
        ! Albedos mapped less accurately to ecRad spectral bands
        if (maxval(config%i_albedo_from_band_sw) > size(this%sw_albedo,2)) then
          write(nulerr,'(a,i0,a)') '*** Error: single_level%sw_albedo has fewer than required ', &
               &  maxval(config%i_albedo_from_band_sw), ' bands'
          call radiation_abort()
        end if      
        sw_albedo_diffuse = transpose(this%sw_albedo(istartcol:iendcol, &
             &  config%i_albedo_from_band_sw(config%i_band_from_reordered_g_sw)))
        if (allocated(this%sw_albedo_direct)) then
          sw_albedo_direct = transpose(this%sw_albedo_direct(istartcol:iendcol, &
               &  config%i_albedo_from_band_sw(config%i_band_from_reordered_g_sw)))
        else
          sw_albedo_direct = sw_albedo_diffuse
        end if
      end if
    end if

    if (config%do_lw .and. present(lw_albedo)) then
      if (config%use_canopy_full_spectrum_lw) then
        if (config%n_g_lw /= size(this%lw_emissivity,2)) then
          write(nulerr,'(a,i0,a)') '*** Error: single_level%lw_emissivity does not have the expected ', &
               &  config%n_g_lw, ' spectral intervals'
          call radiation_abort()
        end if
        lw_albedo = 1.0_jprb - transpose(this%lw_emissivity(istartcol:iendcol,:))
      else if (.not. config%do_nearest_spectral_lw_emiss) then
        ! Albedos averaged accurately to ecRad spectral bands
        nalbedoband = size(config%lw_emiss_weights,1)
        if (nalbedoband /= size(this%lw_emissivity,2)) then
          write(nulerr,'(a,i0,a)') '*** Error: single_level%lw_emissivity does not have the expected ', &
               &  nalbedoband, ' bands'
          call radiation_abort()
        end if
        lw_albedo_band = 0.0_jprb
        do jband = 1,config%n_bands_lw
          do jalbedoband = 1,nalbedoband
            if (config%lw_emiss_weights(jalbedoband,jband) /= 0.0_jprb) then
              do jcol = istartcol,iendcol
                lw_albedo_band(jcol,jband) &
                    &  = lw_albedo_band(jcol,jband) & 
                    &  + config%lw_emiss_weights(jalbedoband,jband) &
                    &    * (1.0_jprb-this%lw_emissivity(jcol, jalbedoband))
              end do
            end if
          end do
        end do

        lw_albedo = transpose(lw_albedo_band(istartcol:iendcol, &
             &                config%i_band_from_reordered_g_lw))
      else
        if (maxval(config%i_emiss_from_band_lw) > size(this%lw_emissivity,2)) then
          write(nulerr,'(a,i0,a)') '*** Error: single_level%lw_emissivity has fewer than required ', &
               &  maxval(config%i_emiss_from_band_lw), ' bands'
          call radiation_abort()
        end if
        lw_albedo = 1.0_jprb - transpose(this%lw_emissivity(istartcol:iendcol, &
             &  config%i_emiss_from_band_lw(config%i_band_from_reordered_g_lw)))
      end if
    end if

    if (lhook) call dr_hook('radiation_single_level:get_albedos',1,hook_handle)

  end subroutine get_albedos


  !---------------------------------------------------------------------
  ! Return .true. if the contents of a single_level structure are out
  ! of a physically sensible range, optionally only considering only
  ! columns between istartcol and iendcol
  function out_of_physical_bounds(this, istartcol, iendcol, do_fix) result(is_bad)

    use yomhook,          only : lhook, dr_hook, jphook
    use radiation_check,  only : out_of_bounds_1d, out_of_bounds_2d

    class(single_level_type), intent(inout) :: this
    integer,         optional,intent(in) :: istartcol, iendcol
    logical,         optional,intent(in) :: do_fix
    logical                              :: is_bad

    logical    :: do_fix_local

    real(jphook) :: hook_handle

    if (lhook) call dr_hook('radiation_single_level:out_of_physical_bounds',0,hook_handle)

    if (present(do_fix)) then
      do_fix_local = do_fix
    else
      do_fix_local = .false.
    end if

    is_bad =    out_of_bounds_1d(this%cos_sza, "cos_sza", -1.0_jprb, 1.0_jprb, &
         &                       do_fix_local, i1=istartcol, i2=iendcol) &
         & .or. out_of_bounds_1d(this%skin_temperature, "skin_temperature", 173.0_jprb, 373.0_jprb, &
         &                       do_fix_local, i1=istartcol, i2=iendcol) &
         & .or. out_of_bounds_2d(this%sw_albedo, "sw_albedo", 0.0_jprb, 1.0_jprb, &
         &                       do_fix_local, i1=istartcol, i2=iendcol) &
         & .or. out_of_bounds_2d(this%sw_albedo_direct, "sw_albedo", 0.0_jprb, 1.0_jprb, &
         &                       do_fix_local, i1=istartcol, i2=iendcol) &
         & .or. out_of_bounds_2d(this%lw_emissivity, "lw_emissivity", 0.0_jprb, 1.0_jprb, &
         &                       do_fix_local, i1=istartcol, i2=iendcol)

    if (lhook) call dr_hook('radiation_single_level:out_of_physical_bounds',1,hook_handle)

  end function out_of_physical_bounds

end module radiation_single_level
