! 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 monarch_emissions_t object and related functions

!> Functions to calculate emissions
module monarch_emissions
! MONARCH always has MPI and JSON
#define MONARCH_USE_MPI

#ifdef DO_PARTMC

! PartMC always has JSON when used in MONARCH
#define PMC_USE_JSON

! Prefix to use in labelling PMC emissions rxns for updates
! (This can be any integer, but must be unique to emissions)
#define PMC_EMISSIONS 8675309

  use monarch_constants,                    only : mint, mflt, mdbl
  use monarch_emission_model_pointer,       only : monarch_emission_model_ptr
  use monarch_species_map,                  only : monarch_species_map_t
  use pmc_phlex_core,                       only : phlex_core_t
  use pmc_property,                         only : property_t

  implicit none
  private

  public :: monarch_emissions_t

  !> PartMC emissions
  !!
  !! This type provides functions to calculate emissions rates and update
  !! the PartMC-phlex first-order loss reactions accordingly
  type monarch_emissions_t
    private
    !> Phlex-chem core
    type(phlex_core_t), pointer :: phlex_core => null()
    !> PartMC-Phlex <-> MONARCH species map
    type(monarch_species_map_t), pointer :: species_map => null()
    !> Next reaction index for new emissions models to use
    integer(kind=mint) :: next_rxn_id = PMC_EMISSIONS
    !> Emission models
    type(monarch_emission_model_ptr), allocatable :: emission_model( : )
    !> Emission input data for each emission model
    type(property_t), pointer :: model_data => null( )
  contains
    !> Calculate the emissions
    procedure :: calc_rates
    !> Update the emissions rates for one grid cell
    procedure :: update_rxn_rates
    !> Determine the number of bytes required to pack the given object
    procedure :: pack_size
    !> Packs the given object in the buffer, advancing position
    procedure :: bin_pack
    !> Unpacks the given object from the buffer, advancing position
    procedure :: bin_unpack
    !> Print the emissions data
    procedure :: print => do_print
    !> Load emissions model properties from PartMC
    procedure, private :: load
    !> Initialize the emissions module
    procedure :: initialize
    !> Finalize the emissions object
    final :: finalize
  end type monarch_emissions_t

  !> PartMC emissions constructor
  interface monarch_emissions_t
    procedure :: constructor
  end interface monarch_emissions_t

contains

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

  !> Create a new monarch_emissions_t object
  !!
  !! Create a new monarch_emissions_t object at the beginning of the model
  !! run for each process. This constructor should be called with an already-
  !! initialized phlex_core_t object. This should be called with the
  !! initialize flag set to true on the primary process, and false on the
  !! other processes.
  function constructor( phlex_core, species_map, diag, config_file )         &
      result( new_obj )

    use monarch_diagnostics,                only : monarch_diagnostics_t
    use monarch_util,                       only : die_msg

    !> A new PartMC emissions object
    type(monarch_emissions_t), pointer :: new_obj
    !> Phlex core
    type(phlex_core_t), target, intent(inout) :: phlex_core
    !> PartMC-Phlex <-> MONARCH species map
    type(monarch_species_map_t), target, intent(in) :: species_map
    !> Diagnostics object attached to internal state with model dimensions
    !! Should only be included in calls from primary process
    type(monarch_diagnostics_t), intent(in), optional :: diag
    !> Path to the emissions configuration file
    !! Should only be included in calls from primary process
    character(len=:), allocatable, optional :: config_file

    ! Create a new emissions object
    allocate(new_obj)

    ! Attach to the phlex_core_t object and species map
    new_obj%phlex_core  => phlex_core
    new_obj%species_map => species_map

    ! Return if not loading emission model properties
    if( .not. present( config_file ) ) then
      allocate( new_obj%emission_model( 0 ) )
      return
    end if

    if( .not. present( diag ) ) then
      call die_msg( 400997639, "Internal error" )
    else

      ! Load the emission model data
      call new_obj%load( diag, config_file )

    end if

  end function constructor

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

  !> Run the emissions module for a given set of conditions for the domain
  subroutine calc_rates( this, diag )

    use monarch_diagnostics,                only : monarch_diagnostics_t

    !> Emissions object
    class(monarch_emissions_t), intent(inout) :: this
    !> Diagnostic object attached to current model state
    type(monarch_diagnostics_t), intent(in) :: diag

    integer(kind=mint) :: i_model, n_steps

    ! Allow each emission model driver to recalculate its value for this step
    do i_model = 1, size( this%emission_model )
      call this%emission_model( i_model )%val%calc_rates( diag )
    end do

  end subroutine calc_rates

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

  !> Update emissions reaction rates for a single grid cell
  subroutine update_rxn_rates( this, indices )

    use monarch_util,                       only : monarch_point_t

    !> Emissions object
    class(monarch_emissions_t), intent(inout) :: this
    !> Indices for the grid cell to update rates for
    type(monarch_point_t), intent(in) :: indices

    integer(kind=mint) :: i_model

    ! Allow each emission model driver to update reaction rates for this step
    do i_model = 1, size( this%emission_model )
      call this%emission_model( i_model )%val%update_rxn_rates( indices )
    end do

  end subroutine update_rxn_rates

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

  !> Determine the size of a binary required to pack the PMC emissions
  !! module
  integer(kind=mint) function pack_size( this, comm )

    use monarch_mpi,                        only : mnrc_mpi_pack_size_integer

    !> Emissions object
    class(monarch_emissions_t), intent(in) :: this
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

    integer(kind=mint) :: i_model

    pack_size = mnrc_mpi_pack_size_integer( size( this%emission_model ), comm)
    do i_model = 1, size( this%emission_model )
      pack_size = pack_size + this%emission_model( i_model )%pack_size( comm )
    end do

  end function pack_size

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

  !> Pack the given object to the buffer, advancing position
  subroutine bin_pack( this, buffer, pos, comm )
#ifdef MONARCH_USE_MPI
    use monarch_mpi,                        only : mnrc_mpi_pack_integer
    use monarch_util,                       only : assert
#endif

    !> Emissions object
    class(monarch_emissions_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

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position, i_model

    prev_position = pos
    call mnrc_mpi_pack_integer( buffer, pos, size( this%emission_model ),comm)
    do i_model = 1, size( this%emission_model )
      call this%emission_model( i_model )%bin_pack( buffer, pos, comm )
    end do
    call assert( 600945212, &
         pos - prev_position <= this%pack_size( comm ) )
#endif

  end subroutine bin_pack

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

  !> Unpack the given object to the buffer, advancing position
  subroutine bin_unpack( this, buffer, pos, comm, diag )

    use monarch_diagnostics,                only : monarch_diagnostics_t
#ifdef MONARCH_USE_MPI
    use monarch_mpi,                        only : mnrc_mpi_unpack_integer
    use monarch_util,                       only : assert
#endif

    !> Emissions object
    class(monarch_emissions_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
    !> Diagnostics object
    type(monarch_diagnostics_t), intent(in) :: diag

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position, i_model, n_models

    prev_position = pos
    call mnrc_mpi_unpack_integer( buffer, pos, n_models, comm )
    if( allocated( this%emission_model ) ) deallocate( this%emission_model )
    allocate( this%emission_model( n_models ) )
    do i_model = 1, n_models
      call this%emission_model( i_model )%bin_unpack( buffer, pos, comm,     &
                                                      this%phlex_core )
    end do
    call assert( 509518923, &
         pos - prev_position <= this%pack_size( comm ) )
#endif

  end subroutine bin_unpack

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

  !> Print the emissions data
  subroutine do_print( this, file_unit )

    !> Emissions object
    class(monarch_emissions_t), intent(in) :: this
    !> File unit to write to
    integer(kind=mint), intent(in) :: file_unit

    integer(kind=mint) :: i_model
    character(len=*), parameter :: fmt_blank_line = "('')"

    write(file_unit,fmt_blank_line)
    write(file_unit,*) "************************"
    write(file_unit,*) "** PMC Emissions Data **"
    write(file_unit,*) "************************"
    write(file_unit,fmt_blank_line)
    write(file_unit,*) "Number of emissions models:",                        &
                       size( this%emission_model )

    do i_model = 1, size( this%emission_model )
      call this%emission_model( i_model )%val%print( file_unit )
    end do

    if( associated( this%model_data ) ) then
      write(file_unit,fmt_blank_line)
      write(file_unit,*) "*** Emission model input data***"
      call this%model_data%print( )
    end if

    write(file_unit,fmt_blank_line)
    write(file_unit,*) "****************************"
    write(file_unit,*) "** End PMC Emissions Data **"
    write(file_unit,*) "****************************"
    write(file_unit,fmt_blank_line)

  end subroutine do_print

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

  !> Finalize the emissions object
  subroutine finalize( this )

    !> Emissions object
    type(monarch_emissions_t), intent(inout) :: this

    ! The PartMC <-> MONARCH interface owns the phlex_core object
    if( associated( this%phlex_core ) )                                      &
        this%phlex_core => null( )
    if( allocated( this%emission_model ) )                                   &
        deallocate( this%emission_model )

  end subroutine finalize

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

  !> Load emission model properties
  !!
  !! Specific emission model drivers are loaded with properties from input
  !! data and mapped to PartMC-phlex emission reactions
  subroutine load( this, diag, config_file )

#ifdef PMC_USE_JSON
    use json_module
#endif
    use monarch_diagnostics,                only : monarch_diagnostics_t
    use monarch_util,                       only : assert, assert_msg, die_msg
    use pmc_property,                       only : property_t

    !> Emissions object
    class(monarch_emissions_t), intent(inout) :: this
    !> Diagnostics object attached to internal state with model dimensions
    type(monarch_diagnostics_t), intent(in) :: diag
    !> Emissions configuration file
    character(len=:), allocatable :: config_file

#ifdef PMC_USE_JSON
    type(json_core), pointer :: json
    type(json_file) :: j_file
    type(json_value), pointer :: j_obj, j_next, j_child
    character(len=:), allocatable :: owner_name, model_type
    integer(kind=mint) :: i_model
    logical :: found
    type(property_t), pointer :: model_props

    ! Verify the phlex core is initialized
    call assert_msg(582709698, this%phlex_core%is_initialized( ),            &
                    "Trying to load species properties for "//               &
                    "monarch_emissions_t with an "//                         &
                    "uninitialized phlex_core_t object")

    ! Get a new json core
    allocate( json )

    ! Initialize the json objects
    j_obj  => null( )
    j_next => null( )

    ! Initialize the json file
    call j_file%initialize( )
    call j_file%get_core( json )
    call assert_msg( 148350246, allocated( config_file ),                    &
              "Received non-allocated string for emissions file path" )
    call assert_msg( 427466722, trim( config_file ) .ne. "",                 &
              "Received empty string for emissions file path" )
    inquire( file=config_file, exist=found )
    call assert_msg( 308430831, found, "Cannot find file: "//                &
              config_file )
    call j_file%load_file( filename = config_file )

    ! Find the emission models
    call j_file%get( 'emissions-data( 1 ) ', j_obj )

    ! Load the emission model data
    this%model_data => property_t( )
    do while( associated( j_obj ) )
      owner_name = "Emissions interface"
      call this%model_data%load( json, j_obj, .true., owner_name, .true. )
      j_next => j_obj
      call json%get_next( j_next, j_obj )
    end do

    ! Allocate space for the emission models and load their data
    allocate( this%emission_model( this%model_data%size( ) ) )

    ! Create each model driver
    call this%model_data%iter_reset( )
    do i_model = 1, size( this%emission_model )

      ! get the model type name
      call assert( 821119797, this%model_data%get_key( model_type ) )

      ! get the model properties
      call assert( 324536472,                                                &
                   this%model_data%get_property_t( val = model_props ) )

      ! create the appropriate driver
      call this%emission_model( i_model )%new_by_name( model_type,           &
                                                       this%phlex_core,      &
                                                       model_props,          &
                                                       diag,                 &
                                                       this%next_rxn_id )

      ! advance to the next emissions model's data
      call this%model_data%iter_next( )

    end do
#else
    call die_msg( 639661605, "No JSON support for reading emissions "//      &
                  "configuration file" )
#endif

  end subroutine load

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

  !> Initialize the emissions module
  subroutine initialize( this, diag )

    use monarch_diagnostics,                only : monarch_diagnostics_t
    use monarch_util,                       only : assert_msg
    use pmc_rxn_factory,                    only : rxn_factory_t

    !> Emissions object
    class(monarch_emissions_t), intent(inout) :: this
    !> Diagnostics object attached to current solver internal state
    type(monarch_diagnostics_t), intent(in) :: diag

    type(rxn_factory_t) :: rxn_factory
    integer(kind=mint) :: i_model

    ! Verify the phlex core is initialized
    call assert_msg(743466917, this%phlex_core%is_initialized(),             &
                    "Trying to initialize monarch_emissions_t with an "//    &
                    "uninitialized phlex_core_t object.")

    do i_model = 1, size( this%emission_model )
      call this%emission_model( i_model )%val%initialize( diag,              &
                                                          this%species_map )
    end do

  end subroutine initialize

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

#endif // DO_PARTMC
end module monarch_emissions
