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

!> Handles output of aerosol optical properties
module monarch_output_diag_aero_optics
! MONARCH always has MPI
#define MONARCH_USE_MPI

  use monarch_aerosol_representation_pointer, only :                         &
                                         monarch_aerosol_group_collection_ptr
  use monarch_constants,                      only : mint, mflt, mdbl
  use monarch_state_changer,                  only : monarch_state_array_ptr
  use monarch_output_diagnostics,             only :                         &
                                         monarch_output_diagnostics_t
  use monarch_util,                           only : string_t

  implicit none
  private

  public :: monarch_output_diag_aero_optics_t

  !> Output aerosol optical properties
  type, extends(monarch_output_diagnostics_t) ::                             &
      monarch_output_diag_aero_optics_t
  private
    !> Aerosol groups to output optical properties for
    class(monarch_aerosol_group_collection_ptr), allocatable ::              &
        aero_groups( : )
    !> Aerosol groups to output aerosol loading for
    class(monarch_aerosol_group_collection_ptr), allocatable ::              &
        aero_groups_loading( : )
    !> Names for optical property aerosol groups
    type(string_t), allocatable :: aero_optics_names( : )
    !> Names for loading aerosol groups
    type(string_t), allocatable :: aero_loading_names( : )
    !> Working array for extinction calculations [m-1]
    real(kind=mdbl), allocatable :: working_extinct__m( : )
    !> Set of pointers to state arrays holding the diagnostics
    type(monarch_state_array_ptr), allocatable :: array_ptr( : )
    !> Satellite nadir overpass times - long-based local time [s]
    real(kind=mdbl), allocatable :: satellite_overpass_times__s( : )
    !> Satellite labels
    type(string_t), allocatable :: satellite_labels( : )
    !> Labels for satellites and aerosol groups combined
    type(string_t), allocatable :: satellite_group_labels( : )
    !> Next satellite array flush date
    !! This flag is used to retain only AOD from the current UTC date in the
    !! satellite-overpass AOD array
    integer(kind=mint) :: next_satellite_flush_date = -1
  contains
    !> Register space in the model state for the diagnostics
    procedure :: register
    !> Update output diagnostic once all MONARCH ids have been initialized
    procedure :: update
    !> Perform column-level calculations
    procedure :: calculate_column
    !> Perform grid cell-level calculations
    procedure :: calculate_grid_cell
    !> Output the diagnostics
    procedure :: output
    !> Determine the number of bytes required to pack the given object
    procedure :: pack_size
    !> Pack the given object to the buffer, advancing position
    procedure :: bin_pack
    !> Unpack the given object from the buffer, advancing position
    procedure :: bin_unpack
    !> Print out the configuration of the output diagnostic
    procedure :: print => do_print
  end type monarch_output_diag_aero_optics_t

  !> Constructor for the aerosol optical property diagnostics
  interface monarch_output_diag_aero_optics_t
    procedure :: constructor
  end interface monarch_output_diag_aero_optics_t

contains
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Constructor for the aerosol optical diagnostics
  function constructor( diag, aero_rep ) result( new_obj )

    use monarch_aerosol_representation,     only :                           &
                                            monarch_aerosol_representation_t
    use monarch_aero_rep_fixed,             only : monarch_aero_rep_fixed_t, &
                                                   monarch_aero_groups_fixed_t
    use monarch_diagnostics,                only : monarch_diagnostics_t
    use monarch_util,                       only : die_msg, to_string,       &
                                                   monarch_dimensions_t,     &
                                                   assert

    !> New output diagnostics object
    type(monarch_output_diag_aero_optics_t), pointer :: new_obj
    !> Diagnostics object
    !! (Do not include when unpacking object from an mpi buffer)
    type(monarch_diagnostics_t), optional, intent(in) :: diag
    !> Aerosol representation
    !! (Do not include when unpacking object from an mpi buffer)
    class(monarch_aerosol_representation_t), optional, intent(in) :: aero_rep

    type(monarch_aero_groups_fixed_t), pointer :: aero_group
    type(monarch_dimensions_t) :: task_dims
    integer(kind=mint) :: i_group, n_groups, n_groups_loading, i_bin, i_name
    integer(kind=mint) :: i_sat, n_sat, i_satgroup
    type(string_t), allocatable :: group_names( : )

    allocate( new_obj )

    ! Return an empty opbject for unpacking from an mpi buffer
    if( .not. present( diag ) ) return

    task_dims = diag%task_integration_dimensions( )
    allocate( new_obj%working_extinct__m( task_dims%upper%vertical_idx( ) ) )

    select type( aero_rep )
      type is( monarch_aero_rep_fixed_t )

        ! Get the set of aerosol types (dust, sea salt, etc)
        group_names = aero_rep%group_names( )

        ! Count the number of aerosol types to output optical properties for
        ! For USPEC, output for each bin (fine and coarse)
        n_groups = 0
        n_groups_loading = 0

        do i_name = 1, size( group_names )
          if( group_names( i_name )%string .eq. "UNSPC" ) then
            n_groups = n_groups + aero_rep%num_bins( "UNSPC" )
          else if ( group_names( i_name )%string .eq. "DUST" ) then
            n_groups = n_groups + aero_rep%num_bins( "DUST" )
          else if ( group_names( i_name )%string .eq. "OM" ) then
            n_groups = n_groups + 1                                            !<-- Total OM
            if( aero_rep%num_bins( "OM" ) .eq. 8 ) n_groups = n_groups + 1     !<-- SOA
          else
            n_groups = n_groups + 1
          end if
          n_groups_loading = n_groups_loading +                              &
                             aero_rep%num_bins( group_names( i_name )%string )
        end do

        allocate( new_obj%aero_groups(                 n_groups ) )
        allocate( new_obj%aero_optics_names(           n_groups ) )
        allocate( new_obj%aero_groups_loading( n_groups_loading ) )
        allocate( new_obj%aero_loading_names(  n_groups_loading ) )

        ! TODO get from input file when available
        n_sat = 2
        allocate( new_obj%satellite_overpass_times__s( n_sat ) )
        allocate( new_obj%satellite_labels( n_sat ) )
        new_obj%satellite_labels( 1 )%string = "MODIS_TERRA_550nm"
        new_obj%satellite_labels( 2 )%string = "MODIS_AQUA_550nm"
        new_obj%satellite_overpass_times__s( 1 ) = 10.5 * 60.0 * 60.0   ! MODIS TERRA
        new_obj%satellite_overpass_times__s( 2 ) = 13.5 * 60.0 * 60.0   ! MODIS AQUA
        allocate( new_obj%satellite_group_labels( n_sat*n_groups ) )

        ! Save the group indices for each groups to output aerosol optical
        ! properties for
        i_group = 0
        aero_group => aero_rep%groups_fixed( )
        do i_name = 1, size( group_names )
          if( group_names( i_name )%string .eq. "UNSPC" ) then
            do i_bin = 1, aero_rep%num_bins( "UNSPC" )
              i_group = i_group + 1
              call aero_group%deselect_all( )
              call assert( 280253101, aero_group%select_bin( "UNSPC",        &
                                                             i_bin ) )
              new_obj%aero_groups( i_group )%val => aero_group%duplicate( )
              new_obj%aero_optics_names( i_group )%string =                  &
                                          "UNSPC_"//trim( to_string( i_bin ) )
            end do
          else if ( group_names( i_name )%string .eq. "DUST" ) then
            do i_bin = 1, aero_rep%num_bins( "DUST" )
              i_group = i_group + 1
              call aero_group%deselect_all( )
              call assert( 885795998, aero_group%select_bin( "DUST", i_bin ) )
              new_obj%aero_groups( i_group )%val => aero_group%duplicate( )
              new_obj%aero_optics_names( i_group )%string =                  &
                                           "DUST_"//trim( to_string( i_bin ) )
            end do
          else if ( group_names( i_name )%string .eq. "OM" ) then
            i_group = i_group + 1
            call aero_group%deselect_all( )
            call assert( 278685884, aero_group%select_group( "OM" ) )
            new_obj%aero_groups( i_group )%val => aero_group%duplicate( )
            new_obj%aero_optics_names( i_group )%string = "OM_total"
            if( aero_rep%num_bins( "OM" ) .eq. 8 ) then
              i_group = i_group + 1
              call aero_group%deselect_all( )
              do i_bin = 4, 7
                call assert( 955954727, aero_group%select_bin( "OM", i_bin ) )
              end do
              new_obj%aero_groups( i_group )%val => aero_group%duplicate( )
              new_obj%aero_optics_names( i_group )%string = "SOA_total"
            end if
          else
            i_group = i_group + 1
            call aero_group%deselect_all( )
            call assert( 322751034, aero_group%select_group(                 &
                                              group_names( i_name )%string ) )
            new_obj%aero_groups( i_group )%val => aero_group%duplicate( )
            new_obj%aero_optics_names( i_group )%string =                    &
                 group_names( i_name )%string//"_total"
          end if
        end do
        call assert( 215960922, i_group .eq. n_groups )

        ! Save the group indices for each groups to output aerosol loading for
        i_group = 0
        do i_name = 1, size( group_names )
          do i_bin = 1, aero_rep%num_bins( group_names( i_name )%string )
            i_group = i_group + 1
            call aero_group%deselect_all( )
            call assert( 371966513, aero_group%select_bin(                   &
                                       group_names( i_name )%string, i_bin ) )
            new_obj%aero_groups_loading( i_group )%val =>                    &
                                                       aero_group%duplicate( )
            new_obj%aero_loading_names( i_group )%string =                   &
                 group_names( i_name )%string//"_"//trim( to_string( i_bin ) )

            do i_sat = 1, n_sat 
               i_satgroup = (i_sat-1)*n_groups + i_group

               new_obj%satellite_group_labels ( i_satgroup )%string =        & 
                    new_obj%satellite_labels ( i_sat )%string//"_"//         & 
                    new_obj%aero_loading_names( i_group )%string      

            end do 

          end do
        end do
        call assert( 540934104, i_group .eq. n_groups_loading )

        deallocate( aero_group )

      class default
        call die_msg( 106806011, "Unsupported aerosol group type in "//      &
                   "aerosol optical diagnostics setup" )
    end select


  end function constructor

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

  !> 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

    !> Output diagnostics
    class(monarch_output_diag_aero_optics_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

    integer(kind=mint) :: i_aero_group

    ! Set up the arrays for aerosol optical properties
    allocate( this%array_ptr( 5 ) )

    ! Register an array to hold aerosol optical depth
    this%array_ptr( 1 ) = state_changer%register_per_column_real(            &
                                                "aerosol_optical_depth",     & !<-- unique name for the arrary
                                                .true.,                      & !<-- output to the history file
                                                size( this%aero_groups ),    & !<-- number of array elements
                                                this%aero_optics_names )       !<-- label for each element

    call this%array_ptr( 1 )%set_netcdf_long_name( "Aerosol optical depth" )
    call this%array_ptr( 1 )%set_netcdf_units( "unitless" )

    ! Register an array to hold AOD at satellite overpass times
    this%array_ptr( 2 ) = state_changer%register_per_column_real(            &
                                   "satellite_AOD",                          & !<-- unique name for array
                                   .true.,                                   & !<-- output to history file
                                   size( this%satellite_group_labels ),      & !<-- number of satellites*aero_groups
                                   this%satellite_group_labels )               !<-- satellite labels
    call this%array_ptr( 2 )%set_netcdf_long_name(                           &
                                        "AOD for satellite overpass times" )
    call this%array_ptr( 2 )%set_netcdf_units( "unitless" )

    ! Register an array to hold aerosol extinction [m-1]
    this%array_ptr( 3 ) = state_changer%register_per_cell_real(              &
                                   "aerosol_extinction",                     & !<-- unique name for array
                                   .true.,                                   & !<-- output to history file
                                   size( this%aero_groups ),                 & !<-- number of array elements
                                   this%aero_optics_names )                    !<-- label for each element
    call this%array_ptr( 3 )%set_netcdf_long_name(                           &
                                        "Aerosol extinction" )
    call this%array_ptr( 3 )%set_netcdf_units( "m-1" )

    ! Register an array to hold aerosol loading
    this%array_ptr( 4 ) = state_changer%register_per_column_real(            &
                                        "aerosol_loading",                   & !<-- unique name for the arrary
                                        .true.,                              & !<-- output to the history file
                                        size( this%aero_groups_loading ),    & !<-- number of array elements
                                        this%aero_loading_names )              !<-- label for each element
    call this%array_ptr( 4 )%set_netcdf_long_name( "Aerosol mass loading" )
    call this%array_ptr( 4 )%set_netcdf_units( "kg m-2" )

    ! Register an array to hold aerosol optical depth
    this%array_ptr( 5 ) = state_changer%register_per_column_real(            &
                                        "clear_sky_AOD",                     & !<-- unique name for the array
                                        .true.,                              & !<-- output to the history file
                                        size( this%aero_groups ),            & !<-- number of array elements
                                        this%aero_optics_names )               !<-- label for each element
    call this%array_ptr( 5 )%set_netcdf_long_name( "Aerosol optical depth (clear-sky)" )
    call this%array_ptr( 5 )%set_netcdf_units( "unitless" )

  end subroutine register

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

  !> Update output diagnostics once all MONARCH ids have been initialized
  subroutine update( this, diag )

    use monarch_diagnostics,                only : monarch_diagnostics_t

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

    ! Nothing to do for this diagnostic

  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
    use monarch_util,                       only : monarch_point_t

    !> Output diagnostics
    class(monarch_output_diag_aero_optics_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

    type(monarch_point_t) :: cell
    integer(kind=mint) :: i_group, i_sat, i_layer, i_satgroup
    real(kind=mdbl) :: local_time__s, step_size__s
    real(kind=mdbl) :: rnum
    real(kind=mdbl) :: fill_value

    cell = column%indices                                                      !<-- save indices to be able to loop
                                                                               !    through vertical layers
    call random_number(rnum)                                                   !<-- used for clear-sky AOD

    fill_value = -32767.0

    associate( int_state => state_changer%int_state )
    do i_group = 1, size( this%aero_groups )

      ! Aerosol optical depth
      call state_changer%save_property( this%array_ptr( 1 ),                 & !<-- aerosol optical depth
                     column%indices,                                         & !<-- column indices
                     i_group,                                                & !<-- aerosol group
                     real( column%aerosol_optical_depth(                     & !<-- aerosol optical depth
                                     this%aero_groups( i_group )%val,        &
                                     this%working_extinct__m ), kind=mflt ) )

      ! Aerosol extinction [m-1]
      do i_layer = 1, column%n_layers
         call cell%set_vertical_idx( i_layer )
         call state_changer%save_property( this%array_ptr( 3 ),               & !<-- aerosol extinction [m-1]
                     cell,                                                    & !<-- vertical layer in column index
                     i_group,                                                 & !<-- aerosol group
                     real(this%working_extinct__m( i_layer ), kind=mflt) )      !<-- set value (output from previous
                                                                                !    call to aerosol_optical_depth)
      end do

      ! Aerosol mass loading
      call state_changer%save_property( this%array_ptr( 4 ),                 & !<-- aerosol mass loading
                     column%indices,                                         & !<-- column indices
                     i_group,                                                & !<-- aerosol group
                     real( column%aerosol_mass_loading__kgm2(                & !<-- aerosol mass loading
                             this%aero_groups( i_group )%val ), kind=mflt ) )

      ! Aerosol optical depth (clear-sky)
      if ( rnum.le.column%clear_sky_probe( ) ) then  
         call state_changer%save_property( this%array_ptr( 5 ),              & !<-- aerosol optical depth
                     column%indices,                                         & !<-- column indices
                     i_group,                                                & !<-- aerosol group
                     real( column%aerosol_optical_depth(                     & !<-- aerosol optical depth
                                     this%aero_groups( i_group )%val,        &
                                     this%working_extinct__m ) , kind=mflt ) )
      else 
         call state_changer%save_property( this%array_ptr( 5 ),              & !<-- aerosol optical depth
                     column%indices,                                         & !<-- column indices
                     i_group,                                                & !<-- aerosol group
                     real( fill_value, kind=mflt) )
!                     real( state_changer%netcdf_fill_value_real, kind=mflt) ) ! FIXME - this should work, but does not currently
      end if 

    end do

    ! Check if it's time to flush the satellite AOD array
    if( this%next_satellite_flush_date .eq. -1 ) then
      this%next_satellite_flush_date = floor( column%diag%julian_day( ) ) + 1
    else if( this%next_satellite_flush_date .eq.                             &
             floor( column%diag%julian_day( ) ) ) then
      call this%array_ptr( 2 )%reset_array( state_changer )
      this%next_satellite_flush_date = floor( column%diag%julian_day( ) ) + 1
    end if

    ! Outputting satellite overpass data
    ! FIXME Ask Oriol if this is the right time step for diagnostic calls
    step_size__s = column%diag%meteorology_time_step__s( )
    local_time__s = column%diag%longitude_based_local_time__s( column%indices )
    do i_sat = 1, size( this%satellite_overpass_times__s )
      if( abs( local_time__s - this%satellite_overpass_times__s( i_sat ) )   &
          .le. 0.5 * step_size__s ) then
        do i_group = 1, size( this%aero_groups )
          i_satgroup = (i_sat-1)*size( this%aero_groups ) + i_group
          call state_changer%save_property( this%array_ptr( 2 ),             & !<-- satellite overpass AOD
                     column%indices,                                         & !<-- grid cell indices
                     i_satgroup,                                             & !<-- satellite index
                     real( column%aerosol_optical_depth(                     & !<-- aerosol optical depth
                                     this%aero_groups( i_group )%val,        &
                                     this%working_extinct__m ), kind=mflt ) ) 
        end do 
      end if
    end do
    end associate

  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_state_changer,              only : monarch_state_changer_t
    use monarch_util,                       only : monarch_point_t

    !> Output diagnostics
    class(monarch_output_diag_aero_optics_t), intent(inout) :: this
    !> 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

    ! Nothing to be done here

  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

    !> Output diagnostics
    class(monarch_output_diag_aero_optics_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

    integer(kind=mint) :: i_array

    ! See if the array is one that this output diagnostic registered
    do i_array = 1, size( this%array_ptr )
      call this%array_ptr( i_array )%output_to_netcdf( write_int_state,      &
                                                       array_name,           &
                                                       first_output )
    end do

  end subroutine output

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

  !> Determine the number of bytes required to pack the given object
  integer(kind=mint) 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_array

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

    integer(kind=mint) :: i_group, i_array

    pack_size =                                                              &
        mnrc_mpi_pack_size_double_array(  this%working_extinct__m, comm )  + &
        mnrc_mpi_pack_size_integer( 1, comm )
    do i_group = 1, size( this%aero_groups )
      pack_size = pack_size + this%aero_groups( i_group )%pack_size( comm )
    end do
    pack_size = pack_size + mnrc_mpi_pack_size_integer( 1, comm )
    do i_group = 1, size( this%aero_groups_loading )
      pack_size = pack_size + this%aero_groups_loading( i_group              &
                                                        )%pack_size( comm )
    end do
    pack_size = pack_size + mnrc_mpi_pack_size_integer( 1, comm )
    do i_array = 1, size( this%array_ptr )
      pack_size = pack_size + this%array_ptr( i_array )%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,             &
                                          mnrc_mpi_pack_integer_array,       &
                                          mnrc_mpi_pack_double_array
    use monarch_util,              only : assert
#endif

    !> Output diagnostics
    class(monarch_output_diag_aero_optics_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_group, i_array

    prev_position = pos
    call mnrc_mpi_pack_double_array( buffer, pos, this%working_extinct__m,   &
                                                                        comm )
    call mnrc_mpi_pack_integer(  buffer, pos, size( this%aero_groups ), comm )
    do i_group = 1, size( this%aero_groups )
      call this%aero_groups( i_group )%bin_pack( buffer, pos, comm )
    end do
    call mnrc_mpi_pack_integer(  buffer, pos,                                &
                                 size( this%aero_groups_loading ), comm )
    do i_group = 1, size( this%aero_groups_loading )
      call this%aero_groups_loading( i_group )%bin_pack( buffer, pos, comm )
    end do
    call mnrc_mpi_pack_integer( buffer, pos, size( this%array_ptr   ), comm )
    do i_array = 1, size( this%array_ptr )
      call this%array_ptr( i_array )%bin_pack( buffer, pos, comm )
    end do
    call assert( 235943818, &
         pos - prev_position <= this%pack_size( comm ) )
#endif

  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
#ifdef MONARCH_USE_MPI
    use monarch_mpi,               only : mnrc_mpi_unpack_integer,           &
                                          mnrc_mpi_unpack_integer_array,     &
                                          mnrc_mpi_unpack_double_array
    use monarch_util,              only : assert
#endif

    !> Output diagnostics
    class(monarch_output_diag_aero_optics_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

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position, i_group, n_groups, n_groups_loading,&
                          i_array, n_arrays

    call assert( 593338614, .not. allocated( this%aero_groups ) )

    prev_position = pos
    call mnrc_mpi_unpack_double_array(  buffer, pos, this%working_extinct__m,&
                                        comm )
    call mnrc_mpi_unpack_integer(       buffer, pos, n_groups, comm )
    allocate( this%aero_groups( n_groups ) )
    do i_group = 1, n_groups
      call this%aero_groups( i_group )%bin_unpack( buffer, pos, comm,        &
                                                   aero_rep )
    end do
    call mnrc_mpi_unpack_integer(       buffer, pos, n_groups_loading, comm )
    allocate( this%aero_groups_loading( n_groups_loading ) )
    do i_group = 1, n_groups_loading
      call this%aero_groups_loading( i_group )%bin_unpack( buffer, pos, comm,&
                                                   aero_rep )
    end do
    call mnrc_mpi_unpack_integer( buffer, pos, n_arrays, comm )
    allocate( this%array_ptr(   n_arrays ) )
    do i_array = 1, n_arrays
      call this%array_ptr( i_array )%bin_unpack( buffer, pos, comm )
    end do
    call assert( 235943818, &
         pos - prev_position <= this%pack_size( comm ) )
#endif

  end subroutine bin_unpack

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

  !> Print out the configuration of the output diagnostic
  subroutine do_print( this, file_unit )

    use monarch_util,                            only : to_string

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

    integer(kind=mint) :: i_group, i_sat
    real(kind=mdbl) :: local_time_hr
    character(len=*), parameter :: fmt_blank_line = "('')"
    character(len=*), parameter :: fmt_aero_hdr =                            &
                                   "('   | ',A5,' | ',A25,' |')"
    character(len=*), parameter :: fmt_aero_det =                            &
                                   "('   | ',I5,' | ',A25,' |')"
    character(len=*), parameter :: fmt_sat_hdr =                             &
                                   "('   | ',A20,' | ',A24,' |')"
    character(len=*), parameter :: fmt_sat_det =                             &
                                   "('   | ',A20,' | ',14X,F10.2,' |')"

    write(file_unit,*) "  *** Aerosol Optical Diagnostic Output ***"
    write(file_unit,*) "  Aerosol optical depth [unitless] saved to: "//     &
                       "aerosol_optical_depth"
    write(file_unit,*) "  Aerosol extinction [m-1] saved to:         "//     &
                       "aerosol_extinction"
    write(file_unit,fmt_aero_hdr) "Index", "Label"
    do i_group = 1, size( this%aero_groups )
      write(file_unit, fmt_aero_det)                                         &
                      i_group,                                               &
                      this%aero_optics_names( i_group )%string
    end do
    write(file_unit,*) "  Aerosol loading [kg m-2] saved to:         "//     &
                       "aerosol_loading"
    write(file_unit,fmt_aero_hdr) "Index", "Label"
    do i_group = 1, size( this%aero_groups_loading )
      write(file_unit, fmt_aero_det)                                         &
                      i_group,                                               &
                      this%aero_loading_names( i_group )%string
    end do
    write(file_unit,*) "  Satellite overpass AOD saved to:  satellite_AOD"
    write(file_unit,fmt_sat_hdr) "Satellite Label", "Overpass local time [hr]"
    do i_sat = 1, size( this%satellite_labels )
      local_time_hr = this%satellite_overpass_times__s( i_sat ) / 60.0 / 60.0
      write(file_unit,fmt_sat_det) this%satellite_labels( i_sat )%string,    &
                                   local_time_hr
    end do
    write(file_unit,*) "  Aerosol optical depth (clear-sky) [unitless] saved to: "//     &
                       "clear_sky_AOD"

  end subroutine do_print

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

end module monarch_output_diag_aero_optics
