! Copyright 2008-2021 Earth Sciences Department, BSC-CNS
!
! This file is part of MONARCH.
!
! MONARCH 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.
!
! MONARCH 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 MONARCH. If not, see <http://www.gnu.org/licenses/>.

!> \file
!> The abstract monarch_output_diagnostics_t object and related functions

!> \page setup_output_diagnostics Setting up a new diagnostic for output
!!
!! A set of diagnostics are output by MONARCH at user-specified time steps.
!! These diaganostics are calculated on the compute tasks at the end of each
!! model time step with a call to \c monarch_diagnostics_writer::calculate.
!! At specified intervals, the calculated data are passed to the write tasks
!! and output to a netCDF file.
!!
!! The output diagnostics are managed by a \c monarch_diagnostics_writer
!! which exists on each task (compute and write). During initialization,
!! the \c monarch_diagnostics_writer initializes a set of members that extend
!! the abstract \c monarch_output_diagnostics::monarch_output_diagnostics_t
!! type. These extending types are included in modules in the
!! `nmmb-monarch/monarch-pmc/output_diagnostics` folder. This design is used
!! so that similar diagnostics can be grouped into a single module for
!! calculation and output.
!!
!! The run-time configuration of output diagnostics is described
!! \ref config_output_diagnostics "here".
!!
!! The following steps describe how to add a new output diagnostic to the
!! \c monarch_output_diag_phys::monarch_output_diag_phys_t type. Similar
!! steps can be used to add diagnostics to any type that extends
!! \c monarch_output_diagnostics::monarch_output_diagnostics_t
!!
!!   -# TODO Finish once config development is done
!!

!> \page config_output_diagnostics Configuring output diagnostics at runtime
!!
!! [Issue 87] (https://earth.bsc.es/gitlab/ac/nmmb-monarch/issues/87) in
!! MONARCH proposes to use \c json files to configure the output of
!! diagnostics. This page will be updated once development is finished.

!> Handles output of diagnostics
module monarch_output_diagnostics

  implicit none
  private

  public :: monarch_output_diagnostics_t

  !> Output diagnostics
  !!
  !! Extending types should represent sets of related diagnostics that can be
  !! optionally included at run-time based on a set of input data
  type, abstract :: monarch_output_diagnostics_t
  contains
    !> Register space in the model state for the diagnostics
    procedure(register), deferred :: register
    !> Update output diagnostics after all MONARCH ids have been initialized
    procedure(update), deferred :: update
    !> Perform column-level calculations
    procedure(calculate_column), deferred ::calculate_column
    !> Preform grid cell-level calculations
    procedure(calculate_grid_cell), deferred :: calculate_grid_cell
    !> Output the diagnostics
    procedure(output), deferred :: output
    !> Determine the number of bytes required to pack the given object
    procedure(pack_size), deferred :: pack_size
    !> Pack the given object to the buffer, advancing position
    procedure(bin_pack), deferred :: bin_pack
    !> Unpack the given object from the buffer, advancing position
    procedure(bin_unpack), deferred :: bin_unpack
    !> Print out the configuration of the output diagnostic
    procedure(do_print), deferred :: print
  end type monarch_output_diagnostics_t

abstract interface
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Register space in the model state for the diagnostics
  subroutine register( this, diag, state_changer )
    use monarch_diagnostics,                only : monarch_diagnostics_t
    use monarch_state_changer,              only : monarch_state_changer_t
    import :: monarch_output_diagnostics_t

    !> Output diagnostics
    class(monarch_output_diagnostics_t), intent(inout) :: this
    !> Diagnostics object
    type(monarch_diagnostics_t), intent(in) :: diag
    !> Model state changer
    type(monarch_state_changer_t), intent(inout) :: state_changer

  end subroutine register

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

  !> Update output diagnostics after all MONARCH indices have been initialized
  subroutine update( this, diag )
    use monarch_diagnostics,                only : monarch_diagnostics_t
    import :: monarch_output_diagnostics_t

    !> Output diagnostics
    class(monarch_output_diagnostics_t), intent(inout) :: this
    !> Diagnostics object
    type(monarch_diagnostics_t), intent(in) :: diag

  end subroutine update

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

  !> Perform column-level calculations
  subroutine calculate_column( this, column, state_changer )
    use monarch_column,                     only : monarch_column_t
    use monarch_state_changer,              only : monarch_state_changer_t
    import :: monarch_output_diagnostics_t

    !> Output diagnostics
    class(monarch_output_diagnostics_t), intent(inout) :: this
    !> Column state
    type(monarch_column_t), intent(inout) :: column
    !> Model state changer
    type(monarch_state_changer_t), intent(inout) :: state_changer

  end subroutine calculate_column

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

  !> Perform grid cell-level calculations
  subroutine calculate_grid_cell( this, cell, diag, column, state_changer )
    use monarch_column,                     only : monarch_column_t
    use monarch_diagnostics,                only : monarch_diagnostics_t
    use monarch_util,                       only : monarch_point_t
    use monarch_state_changer,              only : monarch_state_changer_t
    import :: monarch_output_diagnostics_t

    !> Output diagnostics
    class(monarch_output_diagnostics_t), intent(inout) :: this
    !> Grid cell indices
    type(monarch_point_t), intent(in) :: cell
    !> Diagnostics object
    type(monarch_diagnostics_t), intent(in) :: diag
    !> Column state
    type(monarch_column_t), intent(in) :: column
    !> Model state changer
    type(monarch_state_changer_t), intent(inout) :: state_changer

  end subroutine calculate_grid_cell

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

  !> Output the diagnostics
  subroutine output( this, write_int_state, array_name, first_output )
    use module_WRITE_INTERNAL_STATE,        only : write_internal_state
    import :: monarch_output_diagnostics_t

    !> Output diagnostics
    class(monarch_output_diagnostics_t), intent(inout) :: this
    !> Write internal state
    type(write_internal_state), intent(in) :: write_int_state
    !> Name of array that is available for output
    character(len=*), intent(in) :: array_name
    !> Flag indicating whether the state arrays need to be defined in the
    !! NetCDF output file
    logical, intent(in) :: first_output

  end subroutine output

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

  !> Determine the number of bytes required to pack the given object
  integer(kind=mint) function pack_size( this, comm )
    use monarch_constants,                  only : mint
    import :: monarch_output_diagnostics_t

    !> Output diagnostics
    class(monarch_output_diagnostics_t), intent(in) :: this
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

  end function pack_size

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

  !> Pack the given object to the buffer, advancing position
  subroutine bin_pack( this, buffer, pos, comm )
    use monarch_constants,                  only : mint
    import :: monarch_output_diagnostics_t

    !> Output diagnostics
    class(monarch_output_diagnostics_t), intent(in) :: this
    !> Memory buffer
    character, intent(inout) :: buffer( : )
    !> Current buffer position
    integer(kind=mint), intent(inout) :: pos
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

  end subroutine bin_pack

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

  !> Unpack the given object from the buffer, advancing position
  subroutine bin_unpack( this, buffer, pos, comm, aero_rep )
    use monarch_aerosol_representation,     only :                           &
                                            monarch_aerosol_representation_t
    use monarch_constants,                  only : mint
    import :: monarch_output_diagnostics_t

    !> Output diagnostics
    class(monarch_output_diagnostics_t), intent(inout) :: this
    !> Memory buffer
    character, intent(inout) :: buffer( : )
    !> Current buffer position
    integer(kind=mint), intent(inout) :: pos
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm
    !> Aerosol representation
    class(monarch_aerosol_representation_t), target, intent(in) :: aero_rep

  end subroutine bin_unpack

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

  !> Print out the configuration of the output diagnostic
  subroutine do_print( this, file_unit )
    use monarch_constants,                  only : mint
    import :: monarch_output_diagnostics_t

    !> Output diagnostics
    class(monarch_output_diagnostics_t), intent(in) :: this
    !> File unit
    integer(kind=mint), intent(in) :: file_unit

  end subroutine do_print

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
end interface

end module monarch_output_diagnostics
