!|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

module ecosys_diagnostics_operators_mod

  !BOP
  ! !MODULE: ecosys_diagnostics_operators_mod

  ! !DESCRIPTION:
  !  This module is used to read the marbl_diagnostics_operators file
  !  and provide information to ecosys_tavg
  !
  !  Written by: Michael Levy, NCAR, Dec 2017


  ! !REVISION HISTORY:
  !  SVN:$Id$

  ! !USES:
  use kinds_mod, only : int_kind
  use kinds_mod, only : char_len
  use tavg,      only : tavg_method_unknown
  use exit_mod,  only : sigAbort, exit_POP
  use io_tools,  only : document

  implicit none
  private

  !-----------------------------------------------------------------------
  !  Variables used to help define / accumulate tavg_fields in ecosys_tavg
  !-----------------------------------------------------------------------

  integer(int_kind)              :: max_marbl_diags_stream_cnt
  integer(int_kind), allocatable :: marbl_diags_stream_cnt_surface(:)    ! dim = number of surface diags returned from MARBL
  integer(int_kind), allocatable :: marbl_diags_stream_cnt_interior(:)   ! dim = number of interior diags returned from MARBL
  integer(int_kind), allocatable :: marbl_diags_operators_surface(:,:)   ! first dim matches above, second dim is max_marbl_diags_stream_cnt
  integer(int_kind), allocatable :: marbl_diags_operators_interior(:,:)  ! first dim matches above, second dim is max_marbl_diags_stream_cnt

  public :: ecosys_diagnostics_operators_init
  public :: max_marbl_diags_stream_cnt
  public :: marbl_diags_stream_cnt_surface
  public :: marbl_diags_stream_cnt_interior

contains

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

  subroutine ecosys_diagnostics_operators_init(marbl_diag_file, surface_diags, interior_diags)
    ! Initializes module variables based on contents of marbl_diagnostics_operators file
    ! (Indexing in marbl_diags_surface_* and marbl_diags_interior_* should match the
    ! indexing in surface_diags and interior_diags, respectively)
    use io_types,                     only : nml_in
    use communicate,                  only : my_task, master_task
    use broadcast,                    only : broadcast_scalar, broadcast_array
    use marbl_interface_public_types, only : marbl_diagnostics_type
    use shr_string_mod,               only : shr_string_countChar
    use utils_mod,                    only : utils_split

    character(len=*),             intent(in) :: marbl_diag_file  ! marbl_diagnostics_operators, generated by MARBL_diags_to_tavg.py
    type(marbl_diagnostics_type), intent(in) :: surface_diags
    type(marbl_diagnostics_type), intent(in) :: interior_diags

    ! Local variables
    character(len=*), parameter :: subname = 'ecosys_diagnostics_operators_mod:ecosys_diagnostics_operators_init'
    character(len=char_len) :: single_line, diag_name, diag_ops, doc_msg, err_msg
    character(len=char_len), dimension(2) :: split_diag_ops
    integer :: io_err
    integer :: surface_diag_ind, interior_diag_ind

    ! Determine max_marbl_diags_stream_cnt
    max_marbl_diags_stream_cnt = 0
    if (my_task .eq. master_task) then
      open(unit=nml_in, file=marbl_diag_file, iostat=io_err)
      if (io_err .ne. 0) then
        call exit_POP(sigAbort, "Error opening marbl_diagnostics_operators file")
      end if
      ! Read file line by line
      do
        read(nml_in, "(A)", iostat=io_err) single_line
        if (io_err .ne. 0) exit

        ! For each line, determine number of streams required
        ! (i.e. how many operators are acting on a given diagnostic)
        ! i. skip blank / comment lines
        single_line = adjustl(single_line)
        if ((len_trim(single_line) .eq. 0) .or. (single_line(1:1) .eq. '#')) cycle
        ! ii. number of streams = number of commas plus one
        !     note that transfer() returns array of characters rather than string
        !     (converts 'len' property to 'dimension')
        max_marbl_diags_stream_cnt = max(shr_string_countChar(single_line, ',')+1, max_marbl_diags_stream_cnt)
      end do
      ! Abort if iostat did not return "End of File" status code
      if (.not. is_iostat_end(io_err)) then
         ! NOTE(bja, 2015-01) assuming that eof is the only proper exit
         ! code from the read.
         call document(subname, 'ERROR reading MARBL namelist file into buffer.')
         call exit_POP(sigAbort, 'Stopping in ' // subname)
      endif
      call document(subname, "max_marbl_diags_stream_cnt", max_marbl_diags_stream_cnt)

      ! Rewind back to beginning of file
      rewind(nml_in, iostat=io_err)
      if (io_err .ne. 0) then
        call exit_POP(sigAbort, "Error rewinding marbl_diagnostics_operators file")
      end if
    end if
    call broadcast_scalar(max_marbl_diags_stream_cnt, master_task)

    ! Set up surface diag variables
    allocate(marbl_diags_stream_cnt_surface(size(surface_diags%diags)))
    allocate(marbl_diags_operators_surface(size(surface_diags%diags), max_marbl_diags_stream_cnt))

    ! Set up interior diag variables
    allocate(marbl_diags_stream_cnt_interior(size(interior_diags%diags)))
    allocate(marbl_diags_operators_interior(size(interior_diags%diags), max_marbl_diags_stream_cnt))

    ! Intialize module variables / arrays
    marbl_diags_stream_cnt_surface = 0
    marbl_diags_operators_surface = tavg_method_unknown
    marbl_diags_stream_cnt_interior = 0
    marbl_diags_operators_interior = tavg_method_unknown

    ! Read file
    do
      ! (1) Read next line on master, iostat value out
      !     (Exit loop if read is not successful; either read error or end of file)
      if (my_task .eq. master_task) read(nml_in, "(A)", iostat=io_err) single_line
      call broadcast_scalar(io_err, master_task)
      if (io_err .ne. 0) exit

      ! (2) Broadcast line just read in on master_task to all tasks
      call broadcast_scalar(single_line, master_task)

      ! (3) Skip empty lines and lines beginning with '#'
      single_line = adjustl(single_line)
      if ((len_trim(single_line) .eq. 0) .or. (single_line(1:1) .eq. '#')) cycle

      ! (4) process non-empty lines
      ! (a) get the diagnostic name and (all) operators
      call utils_split(single_line, ':', split_diag_ops)
      diag_name = split_diag_ops(1)
      diag_ops = split_diag_ops(2)

      ! (b) If diag_name is not a valid diagnostic name, abort
      surface_diag_ind = get_diag_ind(diag_name, surface_diags)
      if (surface_diag_ind .eq. 0) then
        interior_diag_ind = get_diag_ind(diag_name, interior_diags)
        if (interior_diag_ind .eq. 0) then
          write(err_msg, "(3A)") "Can not find ", trim(diag_name), " in list of diagnostics from MARBL"
          call exit_POP(sigAbort, err_msg)
        end if
      else
        interior_diag_ind = 0
      end if

      ! (c) Save operators associated with this diagnostics
      !     - parse_diag_ops() will abort if the operator count exceeds max_marbl_diags_stream_cnt
      if (surface_diag_ind .ne. 0) then
        call parse_diag_ops(diag_ops, marbl_diags_operators_surface(surface_diag_ind,:), &
                            marbl_diags_stream_cnt_surface(surface_diag_ind))
        write(doc_msg, "(2A,I0,A)") trim(diag_name), ' will be written to ',  &
                                    marbl_diags_stream_cnt_surface(surface_diag_ind), ' streams.'
      else
        call parse_diag_ops(diag_ops, marbl_diags_operators_interior(interior_diag_ind,:), &
                            marbl_diags_stream_cnt_interior(interior_diag_ind))
        write(doc_msg, "(2A,I0,A)") trim(diag_name), ' will be written to ',  &
                                    marbl_diags_stream_cnt_interior(interior_diag_ind), ' streams.'
      end if
      call document(subname, doc_msg)

    end do
    ! Abort if iostat did not return "End of File" status code
    if (.not. is_iostat_end(io_err)) then
       ! NOTE(bja, 2015-01) assuming that eof is the only proper exit
       ! code from the read.
       call document(subname, 'ERROR reading MARBL namelist file into buffer.')
       call exit_POP(sigAbort, 'Stopping in ' // subname)
    endif

    ! Close the file on master task
    if (my_task .eq. master_task) close(nml_in)

  end subroutine ecosys_diagnostics_operators_init

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

  function get_diag_ind(diag_name, marbl_diags)
    ! Return index of diags such that diags%short_name == diag_name
    ! (Return 0 if no match is found)
    use marbl_interface_public_types, only : marbl_diagnostics_type

    character(len=*), intent(in) :: diag_name
    type(marbl_diagnostics_type), intent(in) :: marbl_diags
    integer :: get_diag_ind

    integer :: n

    get_diag_ind = 0
    do n=1,size(marbl_diags%diags)
      if (trim(diag_name) .eq. trim(marbl_diags%diags(n)%short_name)) then
        get_diag_ind = n
        return
      end if
    end do

  end function get_diag_ind

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

  subroutine parse_diag_ops(diag_ops, diag_ops_array, num_ops)
    ! Split "operator1, operator2, ..., operatorN" into the N elements of a string array

    use utils_mod, only : utils_split

    character(len=*),  intent(in)  :: diag_ops
    integer(int_kind), intent(out) :: diag_ops_array(:)
    integer(int_kind), intent(out) :: num_ops

    character(len=char_len) :: err_msg
    character(len=char_len), dimension(max_marbl_diags_stream_cnt) :: split_ops

    call utils_split(diag_ops, ',', split_ops)

    do num_ops=1,max_marbl_diags_stream_cnt
      if (len_trim(split_ops(num_ops)) .eq. 0) exit
      diag_ops_array(num_ops) = get_tavg_method(split_ops(num_ops))
    end do
    num_ops = num_ops - 1

  end subroutine parse_diag_ops

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

  function get_tavg_method(diag_ops)
    ! Convert a string (such as 'average' to tavg_method integer)

    use tavg, only : tavg_method_avg
    use tavg, only : tavg_method_min
    use tavg, only : tavg_method_max
    use tavg, only : tavg_method_constant

    character(len=*), intent(in) :: diag_ops
    integer(int_kind) :: get_tavg_method

    select case(trim(diag_ops))
      case ('average')
        get_tavg_method = tavg_method_avg
      case ('minimum')
        get_tavg_method = tavg_method_min
      case ('maximum')
        get_tavg_method = tavg_method_max
      case ('instantaneous')
        get_tavg_method = tavg_method_constant
      case DEFAULT
        get_tavg_method = tavg_method_unknown
    end select
  end function get_tavg_method

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

end module ecosys_diagnostics_operators_mod