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

!> Driver for emissions reactions loaded from NETCDF input data
module monarch_emission_model_netcdf_input
! MONARCH always has MPI
#define MONARCH_USE_MPI

#ifdef DO_PARTMC

  use monarch_column,                       only : monarch_column_t
  use monarch_constants,                    only : mint, mflt, mdbl
  use monarch_emission_model,               only : monarch_emission_model_t
  use monarch_util,                         only : monarch_dimensions_t,     &
                                                   string_t

  implicit none
  private

  public :: monarch_emission_model_netcdf_input_t

  ! Species type
  integer(kind=mint), parameter :: NETCDF_SPECIES_UNKNOWN = 0
  integer(kind=mint), parameter :: NETCDF_SPECIES_GAS     = 1
  integer(kind=mint), parameter :: NETCDF_SPECIES_AEROSOL = 2

  !> netcdf_input emission model driver
  type, extends (monarch_emission_model_t) :: monarch_emission_model_netcdf_input_t
    private
    !> Label for emissions input file
    character(len=:), allocatable :: file_label
    !> Emissions netcdf input file path
    character(len=:), allocatable :: file_path
    !> Flag indicating whether the input file has a time dimension
    !! TODO Right now this is set based on whether the update frequency is
    !! labelled as hourly or daily. Maybe set it based on the acutal file
    logical :: has_time_dimension = .false.
    !> Update frequency [s]
    real(kind=mdbl) :: update_frequency__s = real( 0.0, kind=mdbl )
    !> Next update time UTC [s]
    real(kind=mdbl) :: next_update_time__s = real( 0.0, kind=mdbl )
    !> Number of species to be loaded from the netcdf input file
    integer(kind=mint) :: num_netcdf_species = 0
    !> Species names to load from the netcdf file
    type(string_t), allocatable :: netcdf_species_name( : )
    !> Species index in the netcdf input file
    integer(kind=mint), allocatable :: netcdf_species_id( : )
    !> Species phase
    !! (Used to determine the conversion factor for gas/aerosol species)
    integer(kind=mint), allocatable :: netcdf_species_phase( : )
    !> PartMC-phlex emission rxn id
    integer(kind=mint), allocatable :: map_rxn_id( : )
    !> Emission model species id in the netcdf input file
    integer(kind=mint), allocatable :: map_species_id( : )
    !> Emission scaling factor. netcdf species emission rates are multiplied
    !! by this factor before being added to PartMC-phlex emission reactions.
    real(kind=mdbl), allocatable :: map_scale_factor( : )
    !> Working column object
    type(monarch_column_t), pointer :: column => null( )
    !> Emission rates [ppm s-1] or [ug m-3 s-1]
    !! Dimensions are ( West<->East idx, South<->North idx,
    !!   surface<->TOA, emission model species ) for the task memory domain
    real(kind=mdbl), allocatable :: emission_rate( :,:,:,: )
    !> Dimensions of the emissions grid in the netCDF file
    type(monarch_dimensions_t) :: emission_grid_dimensions
    !> Working arrays for passing emissions via MPI
    !! Dimensions are ( West<->East idx, South<->North idx ) for the total
    !! domain and the task memory domain
    real(kind=mflt), allocatable :: emission_total_grid( :,: ),              &
                                    emission_task_grid( :,: )
  contains
    !> Recalculate the emission rates for this model
    procedure :: calc_rates
    !> Get the current emission rate for a particular rxn and grid cell
    !! (PRIVATE)
    procedure :: get_rxn_rate
    !> Initialize the netcdf emissions input interface (PRIVATE)
    procedure :: initialize_model
    !> 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 emission model data
    procedure :: print => do_print
    !> Create a map between netcdf input species and PartMC-phlex emission rxns
    procedure, private :: create_map
    !> Load emission rate data from the netcdf input file
    procedure, private :: load_rate_data
  end type monarch_emission_model_netcdf_input_t

  !> Constructor for the monarch_emission_model_netcdf_input_t type
  interface monarch_emission_model_netcdf_input_t
    procedure :: constructor
  end interface monarch_emission_model_netcdf_input_t

contains

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

  !> Constructor for the monarch_emission_model_netcdf_input_t type
  function constructor( phlex_core, model_props, diag, next_rxn_id )         &
      result( new_obj )

    use monarch_diagnostics,                only : monarch_diagnostics_t
    use monarch_util,                       only : assert, assert_msg, die_msg
    use netcdf,                             only : nf90_open, nf90_close,    &
                                                   nf90_strerror,            &
                                                   NF90_NOERR, NF90_NOWRITE
    use pmc_phlex_core,                     only : phlex_core_t
    use pmc_property,                       only : property_t

    include "netcdf.inc"

    !> New netcdf_input model driver
    type(monarch_emission_model_netcdf_input_t), pointer :: new_obj
    !> Phlex-core
    type(phlex_core_t), target, intent(inout) :: phlex_core
    !> Input model properties
    !! Should only be included in calls from primary processes
    type(property_t), intent(in), optional :: model_props
    !> Diagnostics object attached to the solver internal state with model
    !! dimensions. Should only be included in calls from primary process.
    type(monarch_diagnostics_t), intent(in), optional :: diag
    !> Next reaction id. Will be advanced after setting reaction ids for
    !! species in this emissions file
    integer(kind=mint), intent(inout), optional :: next_rxn_id

    integer(kind=mint) :: i_spec, input_file_id, netcdf_status
    character(len=:), allocatable :: key, str_val

    ! Allocate space for the new driver
    allocate( new_obj )

    new_obj%phlex_core => phlex_core

    ! Only load data on the primary process
    if( .not.present( model_props ) ) return
    call assert( 389979218, present( diag ) )

    ! Get the label for this emissions input file to match to PartMC-Phlex
    ! reactions
    key = "file label"
    call assert_msg( 263089019,                                              &
                     model_props%get_string( key, new_obj%file_label ),      &
                     "Missing label for emissions file. This string is "//   &
                     "needed to match emissions species to PartMC-phlex "//  &
                     "reactions." )

    ! Get the update frequency
    key = "update frequency"
    call assert_msg( 392161642, model_props%get_string( key, str_val ),      &
                     "Missing update frequency for emission file labelled "//&
                     new_obj%file_label )
    select case( str_val )
      case( "hourly" )
        new_obj%update_frequency__s = 60.0 * 60.0
        new_obj%has_time_dimension  = .true.
      case( "daily" )
        new_obj%update_frequency__s = 60.0 * 60.0 * 24.0
        new_obj%has_time_dimension  = .false.
      case default
        call die_msg( 209021941, "Received unrecognized update frequency "// &
                      "for file labelled "//new_obj%file_label//" : "//      &
                      str_val )
    end select

    ! Get the file path for the netcdf input file
    key ="file name"
    call assert_msg( 886275420,                                              &
                     model_props%get_string( key, new_obj%file_path ),       &
                     "Missing file name for netcdf emissions input" )

    ! Open the netcdf input file to check for species names
    netcdf_status = nf90_open( trim( new_obj%file_path ), NF90_NOWRITE,      &
                               input_file_id )
    call assert_msg( 625748122, netcdf_status .eq. NF90_NOERR,               &
                     "Error opening NETCDF emissions file: '"//              &
                     trim( new_obj%file_path )//"': "//                      &
                     trim( nf90_strerror( netcdf_status ) ) )

    ! Save the starting rxn id
    new_obj%starting_rxn_id = next_rxn_id

    ! Match PartMC-phlex emissions rxn species with their netcdf_input index
    call new_obj%create_map( input_file_id )

    ! Advance the next reaction id by the number emission reactions for this
    ! model
    next_rxn_id = next_rxn_id + new_obj%n_rxns

    ! Close the netcdf file
    netcdf_status = nf90_close( input_file_id )
    call assert_msg( 466117432, netcdf_status .eq. NF90_NOERR,               &
                     "Error closing NETCDF emissions file: '"//              &
                     trim( new_obj%file_path )//"': "//                      &
                     trim( nf90_strerror( netcdf_status ) ) )

  end function constructor

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

  !> Reload the emissions from the netcdf file
  subroutine calc_rates( this, diag )

    use monarch_diagnostics,                only : monarch_diagnostics_t

    !> Emission model driver
    class(monarch_emission_model_netcdf_input_t), intent(inout) :: this
    !> Diagnostics object attached to current internal state
    type(monarch_diagnostics_t), intent(in) ::diag

    ! Determine if it is time to read the file
    if( diag%mid_time_step_UTC__s( ) .ge. this%next_update_time__s     .and. &
        diag%mid_time_step_UTC__s( ) + this%update_frequency__s .lt.         &
             60.0 * 60.0 * 24.0 ) then

      ! Load the emissions from the input file
      call this%load_rate_data( diag )

      ! Set the next update time
      if( this%next_update_time__s .eq. real( 0.0, kind=mdbl ) ) then
        this%next_update_time__s = diag%mid_time_step_UTC__s( )
      end if
      this%next_update_time__s = mod( this%next_update_time__s +             &
                                        this%update_frequency__s,            &
                                        60.0 * 60.0 * 24.0 )

    end if

  end subroutine calc_rates

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

  !> Get the current emission rate for a particular rxn and grid cell
  !! (PRIVATE)
  pure function get_rxn_rate( this, indices, rxn_id ) result( rate )

    use monarch_util,                       only : monarch_point_t

    !> Emission rate [ppm s-1] or [ug m-3 s-1]
    real(kind=mdbl) :: rate
    !> Emission model driver
    class(monarch_emission_model_netcdf_input_t), intent(in) :: this
    !> Indices for the grid cell
    type(monarch_point_t), intent(in) :: indices
    !> Internal index for the emission reaction
    !! (1...num_rxns)
    integer(kind=mint) , intent(in)  :: rxn_id

    rate = this%emission_rate( indices%WE_idx( ),                            &
                               indices%SN_idx( ),                            &
                               indices%vertical_idx( ),                      &
                               this%map_species_id( rxn_id ) )             * &
           this%map_scale_factor( rxn_id )

  end function get_rxn_rate

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

  !> Initialize the model (PRIVATE)
  subroutine initialize_model( this, diag, species_map )

    use monarch_diagnostics,                only : monarch_diagnostics_t
    use monarch_species_map,                only : monarch_species_map_t

    !> Emission model driver
    class(monarch_emission_model_netcdf_input_t), intent(inout) :: this
    !> Diagnostics object
    type(monarch_diagnostics_t), intent(in) :: diag
    !> PartMC-Phlex <-> MONARCH species map
    type(monarch_species_map_t), target, intent(in) :: species_map

    type(monarch_dimensions_t) :: task_dims, mem_dims, domain_dims

    ! Get model dimensions
    task_dims   = diag%task_integration_dimensions( )
    mem_dims    = diag%task_memory_dimensions( )
    domain_dims = diag%total_domain_dimensions( )
    if( diag%is_global_run( ) ) then
      this%emission_grid_dimensions = diag%total_real_domain_dimensions( )
    else
      this%emission_grid_dimensions = diag%total_domain_dimensions( )
    end if

    ! Allocate space for the emissions grid used in message passing
    ! (The grid always includes ghost cells, even if they are not updated from
    !  the input file data.)
    allocate( this%emission_total_grid( domain_dims%lower%WE_idx( ):         &
                                        domain_dims%upper%WE_idx( ),         &
                                        domain_dims%lower%SN_idx( ):         &
                                        domain_dims%upper%SN_idx( ) ) )
    allocate( this%emission_task_grid(  mem_dims%lower%WE_idx( ):            &
                                        mem_dims%upper%WE_idx( ),            &
                                        mem_dims%lower%SN_idx( ):            &
                                        mem_dims%upper%SN_idx( ) ) )

    ! Initialize the column object
    this%column => monarch_column_t( this%phlex_core, species_map, diag )

    ! Allocate space for emission rxn rates
    allocate( this%emission_rate( mem_dims%lower%WE_idx( ):                  &
                                  mem_dims%upper%WE_idx( ),                  &
                                  mem_dims%lower%SN_idx( ):                  &
                                  mem_dims%upper%SN_idx( ),                  &
                                  mem_dims%lower%vertical_idx( ):            &
                                  mem_dims%upper%vertical_idx( ),            &
                                  this%num_netcdf_species ) )


  end subroutine initialize_model

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

  !> Determine the number of bytes required to pack the given object
  function pack_size( this, comm )

    use monarch_mpi,             only : mnrc_mpi_pack_size_integer,          &
                                        mnrc_mpi_pack_size_integer_array,    &
                                        mnrc_mpi_pack_size_double,             &
                                        mnrc_mpi_pack_size_double_array

    !> Number of bytes need to pack the object
    integer(kind=mint) :: pack_size
    !> Emission model driver
    class(monarch_emission_model_netcdf_input_t), intent(in) :: this
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

    pack_size =                                                              &
        mnrc_mpi_pack_size_double( this%update_frequency__s       , comm ) + &
        mnrc_mpi_pack_size_integer( this%num_netcdf_species       , comm ) + &
        mnrc_mpi_pack_size_integer( this%n_rxns                   , comm ) + &
        mnrc_mpi_pack_size_integer( this%starting_rxn_id          , comm ) + &
        mnrc_mpi_pack_size_integer_array( this%netcdf_species_id  , comm ) + &
        mnrc_mpi_pack_size_integer_array( this%netcdf_species_phase,comm ) + &
        mnrc_mpi_pack_size_integer_array( this%map_rxn_id         , comm ) + &
        mnrc_mpi_pack_size_integer_array( this%map_species_id     , comm ) + &
        mnrc_mpi_pack_size_double_array( this%map_scale_factor    , comm )

  end function pack_size

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

  !> Packs the given object in the buffer, advancing position
  subroutine bin_pack( this, buffer, pos, comm )
#ifdef MONARCH_USE_MPI
    use monarch_mpi,             only : mnrc_mpi_pack_integer,               &
                                        mnrc_mpi_pack_integer_array,         &
                                        mnrc_mpi_pack_double,                &
                                        mnrc_mpi_pack_double_array
    use monarch_util,            only : assert
#endif

    !> Emission model driver
    class(monarch_emission_model_netcdf_input_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 :: prev_position

    prev_position = pos
    call mnrc_mpi_pack_double(        buffer, pos, this%update_frequency__s, &
                                                                        comm )
    call mnrc_mpi_pack_integer(       buffer, pos, this%num_netcdf_species,  &
                                                                        comm )
    call mnrc_mpi_pack_integer(       buffer, pos, this%n_rxns        , comm )
    call mnrc_mpi_pack_integer(       buffer, pos, this%starting_rxn_id,     &
                                                                        comm )
    call mnrc_mpi_pack_integer_array( buffer, pos, this%netcdf_species_id,   &
                                                                        comm )
    call mnrc_mpi_pack_integer_array( buffer, pos, this%netcdf_species_phase,&
                                                                        comm )
    call mnrc_mpi_pack_integer_array( buffer, pos, this%map_rxn_id    , comm )
    call mnrc_mpi_pack_integer_array( buffer, pos, this%map_species_id, comm )
    call mnrc_mpi_pack_double_array(  buffer, pos, this%map_scale_factor,    &
                                                                        comm )
    call assert( 306436591, &
         pos - prev_position <= this%pack_size( comm ) )
#endif

  end subroutine bin_pack

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

  !> Unpacks the given object from the buffer, advancing position
  subroutine bin_unpack( this, buffer, pos, comm )
#ifdef MONARCH_USE_MPI
    use monarch_mpi,             only : mnrc_mpi_unpack_integer,             &
                                        mnrc_mpi_unpack_integer_array,       &
                                        mnrc_mpi_unpack_double,              &
                                        mnrc_mpi_unpack_double_array
    use monarch_util,            only : assert
#endif

    !> Emission model driver
    class(monarch_emission_model_netcdf_input_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

#ifdef MONARCH_USE_MPI
    integer :: prev_position

    prev_position = pos
    call mnrc_mpi_unpack_double(        buffer, pos,                         &
                                              this%update_frequency__s, comm )
    call mnrc_mpi_unpack_integer(       buffer, pos, this%num_netcdf_species,&
                                                                        comm )
    call mnrc_mpi_unpack_integer(       buffer, pos, this%n_rxns      , comm )
    call mnrc_mpi_unpack_integer(       buffer, pos, this%starting_rxn_id,   &
                                                                        comm )
    call mnrc_mpi_unpack_integer_array( buffer, pos, this%netcdf_species_id, &
                                                                        comm )
    call mnrc_mpi_unpack_integer_array( buffer, pos,                         &
                                              this%netcdf_species_phase, comm)
    call mnrc_mpi_unpack_integer_array( buffer, pos, this%map_rxn_id  , comm )
    call mnrc_mpi_unpack_integer_array( buffer, pos, this%map_species_id,    &
                                                                        comm )
    call mnrc_mpi_unpack_double_array(  buffer, pos, this%map_scale_factor,  &
                                                                        comm )
    call assert( 983705434, &
         pos - prev_position <= this%pack_size( comm ) )
#endif

  end subroutine bin_unpack

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

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

    use monarch_util,                       only : to_string

    !> Emission model driver
    class(monarch_emission_model_netcdf_input_t), intent(in) :: this
    !> File unit to write to
    integer(kind=mint), intent(in) :: file_unit

    integer(kind=mint) :: i_map
    character(len=*), parameter :: fmt_blank_line = "('')"
    character(len=*), parameter :: fmt1 = "(' | ',A15,' | ',A20,' | ',"//    &
                                          "A15,' | ',A10,' | ',A14,' |')"
    character(len=*), parameter :: fmt2 = "(' | ',I15,' | ',A20,' | ',"//    &
                                          "I15,' | ',A10' | ',1x,ES13.3,' |')"

    character(len=*), parameter, dimension( 0:2 ) :: phase_name =            &
                                   (/"UNKNOWN   ","GAS       ","AEROSOL   "/)

    write(file_unit,fmt_blank_line)
    write(file_unit,*) "*************************************"
    write(file_unit,*) "** Emission Driver for NETCDF Input**"
    write(file_unit,*) "*************************************"
    write(file_unit,fmt_blank_line)
    write(file_unit,*) "Input label:", this%file_label
    write(file_unit,*) "File path:  ", this%file_path
    write(file_unit,*) "Number of netcdf species to input data for:",        &
                       this%num_netcdf_species
    write(file_unit,*) "Number of PartMC-Phlex emission reactions updated:", &
                       this%n_rxns
    write(file_unit,*) "PartMC-phlex <-> NETCDF species map"
    if( allocated( this%netcdf_species_id ) ) then
      write(file_unit,fmt1) "Rxn id", "Species Name", "NETCDF id", "Phase",  &
                    "Scaling factor"
      do i_map = 1, size( this%map_rxn_id )
        write(file_unit,fmt2) this%map_rxn_id( i_map ),                      &
            this%netcdf_species_name( this%map_species_id( i_map ) )%string, &
            this%netcdf_species_id( this%map_species_id( i_map ) ),          &
            phase_name( this%netcdf_species_phase(                           &
                             this%map_species_id( i_map ) ) ),               &
            this%map_scale_factor( i_map )
      end do
    end if
    write(file_unit,fmt_blank_line)
    write(file_unit,*) "******************************************"
    write(file_unit,*) "** End Emission Driver for NETCDF Input **"
    write(file_unit,*) "******************************************"
    write(file_unit,fmt_blank_line)

  end subroutine do_print

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

  !> Create a map between netcdf species and PartMC-phlex emission reactions
  subroutine create_map( this, input_file_id )

    use monarch_constants,                  only : const
    use monarch_util,                       only : assert, assert_msg
    use netcdf,                             only : nf90_inq_varid,           &
                                                   nf90_strerror, NF90_NOERR
    use pmc_mechanism_data,                 only : mechanism_data_t
    use pmc_property,                       only : property_t
    use pmc_rxn_data,                       only : rxn_data_t
    use pmc_rxn_emission,                   only : rxn_emission_t

    include "netcdf.inc"

    !> Emission model driver
    class(monarch_emission_model_netcdf_input_t), intent(inout) :: this
    !> Id for already openned netcdf emissions file
    integer(kind=mint), intent(in) :: input_file_id

    integer(kind=mint) :: i_rxn, j_rxn, n_rxn, i_emis_spec, n_emis_spec,     &
                          i_map, i_emis_rxn, netcdf_status
    logical :: add_spec
    type(mechanism_data_t), pointer :: mechanism
    class(rxn_data_t), pointer :: rxn
    type(property_t), pointer :: netcdf_prop_set
    character(len=:), allocatable :: netcdf_key, scale_key, species_key,     &
                                     file_key, spec_name, file_label
    real(kind=mdbl) :: scale_factor
    type(string_t), allocatable :: rxn_spec_name( : )
    integer(kind=mint), allocatable :: rxn_spec_id( : )
    logical, allocatable :: rxn_spec_found( : )
    real(kind=mflt), allocatable :: rxn_spec_scale_factor( : )

    ! FIXME This assumes MONARCH only uses one PMC mechanism at a time
    mechanism => this%phlex_core%mechanism( 1 )%val

    ! key names for netcdf-input emission reactions
    netcdf_key  = "netcdf input"
    file_key    = "file label"
    species_key = "species name"
    scale_key   = "scaling factor"

    ! Set up arrays to hold emission species names and netcdf ids
    allocate( rxn_spec_name(         mechanism%size( ) ) )
    allocate( rxn_spec_id(           mechanism%size( ) ) )
    allocate( rxn_spec_found(        mechanism%size( ) ) )
    allocate( rxn_spec_scale_factor( mechanism%size( ) ) )
    rxn_spec_found( : ) = .false.

    ! Count the number of emission reactions and total species--reaction
    ! contributions
    n_rxn      = 0
    do i_rxn = 1, mechanism%size( )
      rxn => mechanism%get_rxn( i_rxn )
      select type( rxn_emission => rxn )
        class is( rxn_emission_t )
          if( rxn%property_set%get_property_t( netcdf_key,                   &
                                               netcdf_prop_set ) ) then
            call assert_msg( 414282598,                                      &
                             netcdf_prop_set%get_string( file_key,           &
                                                           file_label ),     &
                             "Missing file label in netcdf emissions "//     &
                             "reaction." )
            if( file_label .eq. this%file_label ) then
              call assert_msg( 395583199,                                    &
                               netcdf_prop_set%get_string( species_key,      &
                                                             spec_name ),    &
                               "Missing species name in netcdf emissions "// &
                               "reaction." )
              n_rxn = n_rxn + 1
              netcdf_status = nf90_inq_varid( input_file_id, spec_name,      &
                                              rxn_spec_id( i_rxn ) )
              call assert_msg( 649735025, netcdf_status .eq. NF90_NOERR,     &
                               "Error trying to find species "//spec_name//  &
                               " in netcdf input file "//file_label//": "//  &
                               trim( nf90_strerror( netcdf_status ) ) )
              rxn_spec_name(  i_rxn )%string = spec_name
              rxn_spec_found( i_rxn )        = .true.
              if( netcdf_prop_set%get_real( scale_key, scale_factor ) ) then
                rxn_spec_scale_factor( i_rxn ) = scale_factor
              else
                rxn_spec_scale_factor( i_rxn ) = 1.0
              end if
            end if
          end if
      end select
    end do

    ! Set the number of reactions updated by this model
    this%n_rxns = n_rxn

    ! Count the number of unique emissions species
    n_emis_spec = 0
    do i_rxn = 1, mechanism%size( )
      if( .not. rxn_spec_found( i_rxn ) ) cycle
      n_emis_spec = n_emis_spec + 1
      do j_rxn = i_rxn + 1, mechanism%size( )
        if( .not. rxn_spec_found( j_rxn ) ) cycle
        if( rxn_spec_id( j_rxn ) .eq. rxn_spec_id( i_rxn ) ) then
          n_emis_spec = n_emis_spec - 1
          exit
        end if
      end do
    end do
    this%num_netcdf_species = n_emis_spec

    ! Allocate space for the PartMC-Phlex <-> netcdf map
    allocate( this%netcdf_species_name(  n_emis_spec ) )
    allocate( this%netcdf_species_id(    n_emis_spec ) )
    allocate( this%netcdf_species_phase( n_emis_spec ) )
    allocate( this%map_rxn_id(           n_rxn       ) )
    allocate( this%map_species_id(       n_rxn       ) )
    allocate( this%map_scale_factor(     n_rxn       ) )
    this%map_species_id( : )   = 0

    ! FIXME Update once aerosol species are included
    this%netcdf_species_phase( : ) = NETCDF_SPECIES_GAS

    ! Set the names and netcdf ids of the unique emission species
    i_emis_spec = 0
    do i_rxn = 1, mechanism%size( )
      if( .not. rxn_spec_found( i_rxn ) ) cycle
      i_emis_spec = i_emis_spec + 1
      add_spec = .true.
      do j_rxn = i_rxn + 1, mechanism%size( )
        if( .not. rxn_spec_found( j_rxn ) ) cycle
        if( rxn_spec_id( j_rxn ) .eq. rxn_spec_id( i_rxn ) ) then
          i_emis_spec = i_emis_spec - 1
          add_spec = .false.
          exit
        end if
      end do
      if( add_spec ) then
        this%netcdf_species_name( i_emis_spec )%string =                     &
            rxn_spec_name( i_rxn )%string
        this%netcdf_species_id( i_emis_spec ) = rxn_spec_id( i_rxn )
      end if
    end do

    ! Make sure nothing strange happened
    call assert( 547066301, i_emis_spec .eq. n_emis_spec )

    ! Create the species map
    i_map      = 0
    i_emis_rxn = this%starting_rxn_id
    do i_rxn = 1, mechanism%size( )
      rxn => mechanism%get_rxn( i_rxn )
      select type( rxn_emission => rxn )
        class is( rxn_emission_t )
          if( rxn_spec_found( i_rxn ) ) then
            i_map = i_map + 1
            call rxn_emission%set_rxn_id( i_emis_rxn )
            this%map_rxn_id(       i_map ) = i_emis_rxn
            this%map_scale_factor( i_map ) = rxn_spec_scale_factor( i_rxn )
            do i_emis_spec = 1, n_emis_spec
              if( this%netcdf_species_id( i_emis_spec ) .eq.                 &
                  rxn_spec_id( i_rxn ) ) then
                this%map_species_id( i_map ) = i_emis_spec
                exit
              end if
            end do
            call assert( 941146074, this%map_species_id( i_map ) .gt. 0 )
            i_emis_rxn = i_emis_rxn + 1
          end if
      end select
    end do

    ! Clean up
    deallocate( rxn_spec_name         )
    deallocate( rxn_spec_id           )
    deallocate( rxn_spec_found        )
    deallocate( rxn_spec_scale_factor )

    ! Make sure nothing strange happened
    call assert( 694512639, i_emis_rxn .eq.                                  &
                            this%n_rxns + this%starting_rxn_id )
    call assert( 524355735, i_map      .eq. this%n_rxns  )

  end subroutine create_map

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

  !> Load emissions data from the netcdf file
  subroutine load_rate_data( this, diag )

    use module_DM_PARALLEL,                 only : dstrb
    use monarch_constants,                  only : const
    use monarch_diagnostics,                only : monarch_diagnostics_t
    use monarch_util,                       only : monarch_point_t, die_msg, &
                                                   assert_msg, to_string
    use netcdf,                             only : nf90_open, nf90_get_var,  &
                                                   nf90_close, NF90_NOWRITE, &
                                                   nf90_strerror, NF90_NOERR

   include "netcdf.inc"

    !> Emission model driver
    class(monarch_emission_model_netcdf_input_t), intent(inout) :: this
    !> Diagnostics object attached to current internal state
    type(monarch_diagnostics_t), intent(in) ::diag

    integer(kind=mint) :: i_spec, i_hour, i_WE, i_SN, i_layer
    integer(kind=mint) :: emis_WE_start, emis_WE_end
    integer(kind=mint) :: emis_SN_start, emis_SN_end
    integer(kind=mint) :: netcdf_status, input_file_id

    real(kind=mdbl) :: gas_conv, aero_conv

    type(monarch_dimensions_t) :: mem_dims
    type(monarch_point_t) :: cell

#ifdef MONARCH_USE_MPI
    ! Do I/O on the primary process
    if( diag%mpi_process_id( ) .eq. 0 ) then
#endif
      ! Open the file
      netcdf_status = nf90_open( this%file_path, NF90_NOWRITE, input_file_id )
      call assert_msg( 877266603, netcdf_status .eq. NF90_NOERR,             &
                       "Error opening emissions file "//this%file_path//     &
                       ": "//trim( nf90_strerror( netcdf_status ) ) )
#ifdef MONARCH_USE_MPI
    endif
#endif

    ! Get the emission grid dimensions
    emis_WE_start = this%emission_grid_dimensions%lower%WE_idx( )
    emis_WE_end   = this%emission_grid_dimensions%upper%WE_idx( )
    emis_SN_start = this%emission_grid_dimensions%lower%SN_idx( )
    emis_SN_end   = this%emission_grid_dimensions%upper%SN_idx( )

    ! Emission files are assumed to start with the first hour of chemistry
    ! beginning at index 1
    i_hour = floor( diag%time_since_chemistry_start__s( ) / 3600.0 ) + 1

    call assert_msg( 101556042, i_hour .ge. 1,                               &
                     "Invalid emission hour index: "//to_string( i_hour ) )

    ! Loop through the vertical layers
    do i_layer = this%emission_grid_dimensions%lower%vertical_idx( ),        &
                 this%emission_grid_dimensions%upper%vertical_idx( )

      ! Update the emissions for each species
      do i_spec = 1, this%num_netcdf_species

        ! Read data from the file
#ifdef MONARCH_USE_MPI
        if( diag%mpi_process_id( ) .eq. 0 ) then
#endif
        ! Use dimensions based on whether the input file has a time dimension
        if( this%has_time_dimension ) then
          netcdf_status =                                                    &
            nf90_get_var( input_file_id,                                     & !<-- NETCDF file
                          this%netcdf_species_id( i_spec ),                  & !<-- NETCDF Species id
                          this%emission_total_grid(                          & !<-- working array for emissions for this
                                              emis_WE_start : emis_WE_end,   & !    layer and species
                                              emis_SN_start : emis_SN_end ), &
                          start = (/ 1, 1, i_layer, i_hour /),               & !<-- Starting indices to read in the file
                          count = (/ emis_WE_end - emis_WE_start + 1,        & !<-- Number of points to read in each
                                     emis_SN_end - emis_SN_start + 1,        & !    dimension
                                     1, 1 /) )
        else
          netcdf_status =                                                    &
            nf90_get_var( input_file_id,                                     & !<-- NETCDF file
                          this%netcdf_species_id( i_spec ),                  & !<-- NETCDF Species id
                          this%emission_total_grid(                          & !<-- working array for emissions for this
                                              emis_WE_start : emis_WE_end,   & !    layer and species
                                              emis_SN_start : emis_SN_end ), &
                          start = (/ 1, 1, i_layer /),                       & !<-- Starting indices to read in the file
                          count = (/ emis_WE_end - emis_WE_start + 1,        & !<-- Number of points to read in each
                                     emis_SN_end - emis_SN_start + 1,        & !    dimension
                                     1 /) )

        end if
        call assert_msg( 208933560, netcdf_status .eq. NF90_NOERR,           &
                         "Error reading emissions from file "//              &
                         this%file_path//" for species "//                   &
                         this%netcdf_species_name( i_spec )%string//": "//   &
                         trim( nf90_strerror( netcdf_status ) ) )
#ifdef MONARCH_USE_MPI
        end if
        call dstrb( this%emission_total_grid,                                &
                    this%emission_task_grid,                                 &
                    1,1,1,1,1,                                               &
                    diag%mpi_process_id( ),                                  &
                    diag%mpi_computation_comm( ) )
        this%emission_rate( :, :, i_layer, i_spec ) =                        &
            this%emission_task_grid( :, : )
#else
        this%emission_rate( :, :, i_layer, i_spec ) =                        &
            this%emission_total_grid( :, : )
#endif
      end do
    end do

#ifdef MONARCH_USE_MPI
    ! Do I/O on the primary process
    if( diag%mpi_process_id( ) .eq. 0 ) then
#endif
      ! Close the file
      netcdf_status = nf90_close( input_file_id )
      call assert_msg( 877266603, netcdf_status .eq. NF90_NOERR,             &
                       "Error closing emissions file "//this%file_path//     &
                       ": "//trim( nf90_strerror( netcdf_status ) ) )
#ifdef MONARCH_USE_MPI
    endif
#endif

    ! Convert emission rates to units used in PartMC-Phlex
    mem_dims  = diag%task_memory_dimensions( )
    cell      = diag%new_point( )

    do i_WE = mem_dims%lower%WE_idx( ), mem_dims%upper%WE_idx( )
      call cell%set_WE_idx( i_WE )
      do i_SN = mem_dims%lower%SN_idx( ), mem_dims%upper%SN_idx( )
        call cell%set_SN_idx( i_SN )
        call this%column%update( diag, cell )
        do i_layer = mem_dims%lower%vertical_idx( ),                         &
                     mem_dims%upper%vertical_idx( )
          call cell%set_vertical_idx( i_layer )

          gas_conv  = const%molec_weight_dry_air__kgmol /                    &
                      diag%air_density__kgm3( cell )    /                    &
                      this%column%layer_thickness__m( i_layer ) * 1.0e6

          aero_conv = 1.0 / this%column%layer_thickness__m( i_layer )

          do i_spec = 1, this%num_netcdf_species
            select case( this%netcdf_species_phase( i_spec ) )
              case( NETCDF_SPECIES_GAS )
                this%emission_rate( i_WE, i_SN, i_layer, i_spec ) =          &
                    this%emission_rate( i_WE, i_SN, i_layer, i_spec )      * &
                    gas_conv
              case( NETCDF_SPECIES_AEROSOL )
                this%emission_rate( i_WE, i_SN, i_layer, i_spec ) =          &
                    this%emission_rate( i_WE, i_SN, i_layer, i_spec )      * &
                    aero_conv
              case default
                call die_msg( 759569990, "Internal error" )
            end select
          end do
        end do
      end do
    end do

  end subroutine load_rate_data

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#endif
end module monarch_emission_model_netcdf_input
