! CSIRO Open Source Software License Agreement (variation of the BSD / MIT License)
! Copyright (c) 2015, Commonwealth Scientific and Industrial Research Organisation
! (CSIRO) ABN 41 687 119 230.

module cable_netcdf_mod
  !* Interface for netCDF file handling in CABLE.
  !
  ! This module defines abstract types and interfaces for working with netCDF
  ! files using different underlying libraries (e.g., NetCDF Fortran, ParallelIO).
  ! Concrete implementations should extend the abstract types and implement the
  ! deferred procedures.

  use iso_fortran_env, only: CABLE_NETCDF_INT32_KIND => int32
  use iso_fortran_env, only: CABLE_NETCDF_REAL32_KIND => real32
  use iso_fortran_env, only: CABLE_NETCDF_REAL64_KIND => real64

  use cable_mpi_mod, only: mpi_grp_t

  use cable_error_handler_mod, only: cable_abort

  implicit none

  private

  public :: &
    cable_netcdf_decomp_t, &
    cable_netcdf_file_t, &
    cable_netcdf_io_t

  public :: &
    cable_netcdf_mod_init, &
    cable_netcdf_mod_end, &
    cable_netcdf_create_file, &
    cable_netcdf_open_file, &
    cable_netcdf_create_decomp

  public :: &
    CABLE_NETCDF_INT32_KIND, &
    CABLE_NETCDF_REAL32_KIND, &
    CABLE_NETCDF_REAL64_KIND, &
    CABLE_NETCDF_INT, &
    CABLE_NETCDF_FLOAT, &
    CABLE_NETCDF_DOUBLE, &
    CABLE_NETCDF_UNLIMITED, &
    CABLE_NETCDF_IOTYPE_NETCDF, &
    CABLE_NETCDF_IOTYPE_CLASSIC, &
    CABLE_NETCDF_IOTYPE_NETCDF4C, &
    CABLE_NETCDF_IOTYPE_NETCDF4P, &
    CABLE_NETCDF_MODE_CLOBBER, &
    CABLE_NETCDF_MODE_NOCLOBBER, &
    CABLE_NETCDF_MODE_WRITE, &
    CABLE_NETCDF_MODE_NOWRITE

  !> Data type constants for netCDF variables and attributes.
  enum, bind(c)
    enumerator :: CABLE_NETCDF_INT
      !! Data type constant for 32-bit integer variables and attributes in netCDF files.
    enumerator :: CABLE_NETCDF_FLOAT
      !! Data type constant for 32-bit real variables and attributes in netCDF files.
    enumerator :: CABLE_NETCDF_DOUBLE
      !! Data type constant for 64-bit real variables and attributes in netCDF files.
  end enum

  !> I/O type options for opening and creating files.
  !! Follows the I/O type options defined in ParallelIO:
  !! <https://github.com/NCAR/ParallelIO/blob/pio2_6_8/src/flib/pio_types.F90#L102-L106>
  enum, bind(c)
    enumerator :: CABLE_NETCDF_IOTYPE_NETCDF
      !! Serial read/write of NetCDF files.
    enumerator :: CABLE_NETCDF_IOTYPE_CLASSIC
      !! Parallel read/write of pNetCDF files.
    enumerator :: CABLE_NETCDF_IOTYPE_NETCDF4C
      !! NetCDF4 (HDF5 format) file opened for compression (serial write access only).
    enumerator :: CABLE_NETCDF_IOTYPE_NETCDF4P
      !! NetCDF4 (HDF5 format) file opened in parallel.
  end enum

  !> File mode options for opening and creating files
  enum, bind(c)
    enumerator :: CABLE_NETCDF_MODE_CLOBBER
      !! Overwrite existing file.
    enumerator :: CABLE_NETCDF_MODE_NOCLOBBER
      !! Do not overwrite existing file.
    enumerator :: CABLE_NETCDF_MODE_WRITE
      !! Open file for writing.
    enumerator :: CABLE_NETCDF_MODE_NOWRITE
      !! Open file for reading only.
  end enum

  integer, parameter :: CABLE_NETCDF_UNLIMITED = -1
    !! Constant to represent an unlimited dimension length in netCDF files.

  type, abstract :: cable_netcdf_decomp_t
    !* Abstract type for describing netCDF decomposition information.
    ! This type describes the mapping from the local in-memory layout of an array
    ! on the current process to the global layout of a netCDF variable on disk
    ! following the degree of freedom decomposition described in Denis et al.
    ! (2011)
    ! [[10.1177/1094342011428143](https://www.doi.org/10.1177/1094342011428143)].
  end type

  type, abstract :: cable_netcdf_file_t
    !* Abstract type for netCDF file handling.
    ! This type defines the interface for operations on netCDF files, such as
    ! defining dimensions and variables, writing and reading data, and managing
    ! attributes.
  contains
    procedure(cable_netcdf_file_close), deferred :: close
    procedure(cable_netcdf_file_end_def), deferred :: end_def
    procedure(cable_netcdf_file_redef), deferred :: redef
    procedure(cable_netcdf_file_sync), deferred :: sync
    procedure(cable_netcdf_file_def_dims), deferred :: def_dims
    procedure(cable_netcdf_file_def_var), deferred :: def_var
    procedure(cable_netcdf_file_put_att_global_string), deferred :: put_att_global_string
    procedure(cable_netcdf_file_put_att_global_int32), deferred :: put_att_global_int32
    procedure(cable_netcdf_file_put_att_global_real32), deferred :: put_att_global_real32
    procedure(cable_netcdf_file_put_att_global_real64), deferred :: put_att_global_real64
    procedure(cable_netcdf_file_put_att_var_string), deferred :: put_att_var_string
    procedure(cable_netcdf_file_put_att_var_int32), deferred :: put_att_var_int32
    procedure(cable_netcdf_file_put_att_var_real32), deferred :: put_att_var_real32
    procedure(cable_netcdf_file_put_att_var_real64), deferred :: put_att_var_real64
    generic :: put_att => &
      put_att_global_string, put_att_global_int32, put_att_global_real32, put_att_global_real64, &
      put_att_var_string, put_att_var_int32, put_att_var_real32, put_att_var_real64
    procedure(cable_netcdf_file_get_att_global_string), deferred :: get_att_global_string
    procedure(cable_netcdf_file_get_att_global_int32), deferred :: get_att_global_int32
    procedure(cable_netcdf_file_get_att_global_real32), deferred :: get_att_global_real32
    procedure(cable_netcdf_file_get_att_global_real64), deferred :: get_att_global_real64
    procedure(cable_netcdf_file_get_att_var_string), deferred :: get_att_var_string
    procedure(cable_netcdf_file_get_att_var_int32), deferred :: get_att_var_int32
    procedure(cable_netcdf_file_get_att_var_real32), deferred :: get_att_var_real32
    procedure(cable_netcdf_file_get_att_var_real64), deferred :: get_att_var_real64
    generic :: get_att => &
      get_att_global_string, get_att_global_int32, get_att_global_real32, get_att_global_real64, &
      get_att_var_string, get_att_var_int32, get_att_var_real32, get_att_var_real64
    procedure(cable_netcdf_file_inq_dim_len), deferred :: inq_dim_len
    procedure(cable_netcdf_file_inq_var_ndims), deferred :: inq_var_ndims
    procedure(cable_netcdf_file_put_var_int32_0d), deferred :: put_var_int32_0d
    procedure(cable_netcdf_file_put_var_int32_1d), deferred :: put_var_int32_1d
    procedure(cable_netcdf_file_put_var_int32_2d), deferred :: put_var_int32_2d
    procedure(cable_netcdf_file_put_var_int32_3d), deferred :: put_var_int32_3d
    procedure(cable_netcdf_file_put_var_real32_0d), deferred :: put_var_real32_0d
    procedure(cable_netcdf_file_put_var_real32_1d), deferred :: put_var_real32_1d
    procedure(cable_netcdf_file_put_var_real32_2d), deferred :: put_var_real32_2d
    procedure(cable_netcdf_file_put_var_real32_3d), deferred :: put_var_real32_3d
    procedure(cable_netcdf_file_put_var_real64_0d), deferred :: put_var_real64_0d
    procedure(cable_netcdf_file_put_var_real64_1d), deferred :: put_var_real64_1d
    procedure(cable_netcdf_file_put_var_real64_2d), deferred :: put_var_real64_2d
    procedure(cable_netcdf_file_put_var_real64_3d), deferred :: put_var_real64_3d
    generic :: put_var => &
      put_var_int32_0d, put_var_int32_1d, put_var_int32_2d, put_var_int32_3d, &
      put_var_real32_0d, put_var_real32_1d, put_var_real32_2d, put_var_real32_3d, &
      put_var_real64_0d, put_var_real64_1d, put_var_real64_2d, put_var_real64_3d
    procedure(cable_netcdf_file_write_darray_int32_1d), deferred :: write_darray_int32_1d
    procedure(cable_netcdf_file_write_darray_int32_2d), deferred :: write_darray_int32_2d
    procedure(cable_netcdf_file_write_darray_int32_3d), deferred :: write_darray_int32_3d
    procedure(cable_netcdf_file_write_darray_real32_1d), deferred :: write_darray_real32_1d
    procedure(cable_netcdf_file_write_darray_real32_2d), deferred :: write_darray_real32_2d
    procedure(cable_netcdf_file_write_darray_real32_3d), deferred :: write_darray_real32_3d
    procedure(cable_netcdf_file_write_darray_real64_1d), deferred :: write_darray_real64_1d
    procedure(cable_netcdf_file_write_darray_real64_2d), deferred :: write_darray_real64_2d
    procedure(cable_netcdf_file_write_darray_real64_3d), deferred :: write_darray_real64_3d
    generic :: write_darray => &
      write_darray_int32_1d, write_darray_int32_2d, write_darray_int32_3d, &
      write_darray_real32_1d, write_darray_real32_2d, write_darray_real32_3d, &
      write_darray_real64_1d, write_darray_real64_2d, write_darray_real64_3d
    procedure(cable_netcdf_file_get_var_int32_0d), deferred :: get_var_int32_0d
    procedure(cable_netcdf_file_get_var_int32_1d), deferred :: get_var_int32_1d
    procedure(cable_netcdf_file_get_var_int32_2d), deferred :: get_var_int32_2d
    procedure(cable_netcdf_file_get_var_int32_3d), deferred :: get_var_int32_3d
    procedure(cable_netcdf_file_get_var_real32_0d), deferred :: get_var_real32_0d
    procedure(cable_netcdf_file_get_var_real32_1d), deferred :: get_var_real32_1d
    procedure(cable_netcdf_file_get_var_real32_2d), deferred :: get_var_real32_2d
    procedure(cable_netcdf_file_get_var_real32_3d), deferred :: get_var_real32_3d
    procedure(cable_netcdf_file_get_var_real64_0d), deferred :: get_var_real64_0d
    procedure(cable_netcdf_file_get_var_real64_1d), deferred :: get_var_real64_1d
    procedure(cable_netcdf_file_get_var_real64_2d), deferred :: get_var_real64_2d
    procedure(cable_netcdf_file_get_var_real64_3d), deferred :: get_var_real64_3d
    generic :: get_var => &
      get_var_int32_0d, get_var_int32_1d, get_var_int32_2d, get_var_int32_3d, &
      get_var_real32_0d, get_var_real32_1d, get_var_real32_2d, get_var_real32_3d, &
      get_var_real64_0d, get_var_real64_1d, get_var_real64_2d, get_var_real64_3d
    procedure(cable_netcdf_file_read_darray_int32_1d), deferred :: read_darray_int32_1d
    procedure(cable_netcdf_file_read_darray_int32_2d), deferred :: read_darray_int32_2d
    procedure(cable_netcdf_file_read_darray_int32_3d), deferred :: read_darray_int32_3d
    procedure(cable_netcdf_file_read_darray_real32_1d), deferred :: read_darray_real32_1d
    procedure(cable_netcdf_file_read_darray_real32_2d), deferred :: read_darray_real32_2d
    procedure(cable_netcdf_file_read_darray_real32_3d), deferred :: read_darray_real32_3d
    procedure(cable_netcdf_file_read_darray_real64_1d), deferred :: read_darray_real64_1d
    procedure(cable_netcdf_file_read_darray_real64_2d), deferred :: read_darray_real64_2d
    procedure(cable_netcdf_file_read_darray_real64_3d), deferred :: read_darray_real64_3d
    generic :: read_darray => &
      read_darray_int32_1d, read_darray_int32_2d, read_darray_int32_3d, &
      read_darray_real32_1d, read_darray_real32_2d, read_darray_real32_3d, &
      read_darray_real64_1d, read_darray_real64_2d, read_darray_real64_3d
  end type

  abstract interface
    !> Close the netCDF file and release any associated resources.
    subroutine cable_netcdf_file_close(this)
      import cable_netcdf_file_t
      class(cable_netcdf_file_t), intent(inout) :: this
    end subroutine
    !> End the definition mode of the netCDF file, allowing data to be written.
    subroutine cable_netcdf_file_end_def(this)
      import cable_netcdf_file_t
      class(cable_netcdf_file_t), intent(inout) :: this
    end subroutine
    !> Re-enter definition mode for the netCDF file, allowing dimensions and
    !! variables to be defined.
    subroutine cable_netcdf_file_redef(this)
      import cable_netcdf_file_t
      class(cable_netcdf_file_t), intent(inout) :: this
    end subroutine
    !> Synchronize the netCDF file, ensuring that all data is written to disk
    !! and visible to other processes.
    subroutine cable_netcdf_file_sync(this)
      import cable_netcdf_file_t
      class(cable_netcdf_file_t), intent(inout) :: this
    end subroutine
    !> Define dimensions in the netCDF file.
    subroutine cable_netcdf_file_def_dims(this, dim_names, dim_lens)
      import cable_netcdf_file_t
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: dim_names(:)
        !! Array of dimension names to define.
      integer, intent(in) :: dim_lens(:)
        !* Array of dimension lengths corresponding to dim_names. Use
        ! `CABLE_NETCDF_UNLIMITED` for unlimited dimensions.
    end subroutine
    !> Define a variable in the netCDF file with the specified name, dimensions, and type.
    subroutine cable_netcdf_file_def_var(this, var_name, type, dim_names)
      import cable_netcdf_file_t
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to define.
      integer, intent(in) :: type
        !* Data type of the variable, using the `CABLE_NETCDF_*` constants (e.g.,
        ! `CABLE_NETCDF_INT`, `CABLE_NETCDF_REAL32`).
      character(len=*), intent(in), optional :: dim_names(:)
        !* Array of dimension names for the variable. If not provided, the
        ! variable will be defined as a scalar.
    end subroutine
    !> Define a global attribute with a string value in the netCDF file.
    subroutine cable_netcdf_file_put_att_global_string(this, att_name, att_value)
      import cable_netcdf_file_t
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: att_name
        !! Name of the global attribute to define.
      character(len=*), intent(in) :: att_value
        !! Value of the global attribute to define.
    end subroutine
    !> Define a global attribute with an 32-bit integer value in the netCDF file.
    subroutine cable_netcdf_file_put_att_global_int32(this, att_name, att_value)
      import cable_netcdf_file_t, CABLE_NETCDF_INT32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: att_name
        !! Name of the global attribute to define.
      integer(kind=CABLE_NETCDF_INT32_KIND), intent(in) :: att_value
        !! Value of the global attribute to define.
    end subroutine
    !> Define a global attribute with a 32-bit real value in the netCDF file.
    subroutine cable_netcdf_file_put_att_global_real32(this, att_name, att_value)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: att_name
        !! Name of the global attribute to define.
      real(kind=CABLE_NETCDF_REAL32_KIND), intent(in) :: att_value
        !! Value of the global attribute to define.
    end subroutine
    !> Define a global attribute with a 64-bit real value in the netCDF file.
    subroutine cable_netcdf_file_put_att_global_real64(this, att_name, att_value)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL64_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: att_name
        !! Name of the global attribute to define.
      real(kind=CABLE_NETCDF_REAL64_KIND), intent(in) :: att_value
        !! Value of the global attribute to define.
    end subroutine
    !> Define a variable attribute with a string value in the netCDF file.
    subroutine cable_netcdf_file_put_att_var_string(this, var_name, att_name, att_value)
      import cable_netcdf_file_t
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to define the attribute for.
      character(len=*), intent(in) :: att_name
        !! Name of the variable attribute to define.
      character(len=*), intent(in) :: att_value
        !! Value of the variable attribute to define.
    end subroutine
    !> Define a variable attribute with an 32-bit integer value in the netCDF file.
    subroutine cable_netcdf_file_put_att_var_int32(this, var_name, att_name, att_value)
      import cable_netcdf_file_t, CABLE_NETCDF_INT32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to define the attribute for.
      character(len=*), intent(in) :: att_name
        !! Name of the variable attribute to define.
      integer(kind=CABLE_NETCDF_INT32_KIND), intent(in) :: att_value
        !! Value of the variable attribute to define.
    end subroutine
    !> Define a variable attribute with a 32-bit real value in the netCDF file.
    subroutine cable_netcdf_file_put_att_var_real32(this, var_name, att_name, att_value)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to define the attribute for.
      character(len=*), intent(in) :: att_name
        !! Name of the variable attribute to define.
      real(kind=CABLE_NETCDF_REAL32_KIND), intent(in) :: att_value
        !! Value of the variable attribute to define.
    end subroutine
    !> Define a variable attribute with a 64-bit real value in the netCDF file.
    subroutine cable_netcdf_file_put_att_var_real64(this, var_name, att_name, att_value)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL64_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to define the attribute for.
      character(len=*), intent(in) :: att_name
        !! Name of the variable attribute to define.
      real(kind=CABLE_NETCDF_REAL64_KIND), intent(in) :: att_value
        !! Value of the variable attribute to define.
    end subroutine
    !> Read a global attribute with a string value from the netCDF file.
    subroutine cable_netcdf_file_get_att_global_string(this, att_name, att_value)
      import cable_netcdf_file_t
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: att_name
        !! Name of the global attribute to read.
      character(len=*), intent(out) :: att_value
        !! Value of the global attribute read.
    end subroutine
    !> Read a global attribute with an 32-bit integer value from the netCDF file.
    subroutine cable_netcdf_file_get_att_global_int32(this, att_name, att_value)
      import cable_netcdf_file_t, CABLE_NETCDF_INT32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: att_name
        !! Name of the global attribute to read.
      integer(kind=CABLE_NETCDF_INT32_KIND), intent(out) :: att_value
        !! Value of the global attribute read.
    end subroutine
    !> Read a global attribute with a 32-bit real value from the netCDF file.
    subroutine cable_netcdf_file_get_att_global_real32(this, att_name, att_value)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: att_name
        !! Name of the global attribute to read.
      real(kind=CABLE_NETCDF_REAL32_KIND), intent(out) :: att_value
        !! Value of the global attribute read.
    end subroutine
    !> Read a global attribute with a 64-bit real value from the netCDF file.
    subroutine cable_netcdf_file_get_att_global_real64(this, att_name, att_value)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL64_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: att_name
        !! Name of the global attribute to read.
      real(kind=CABLE_NETCDF_REAL64_KIND), intent(out) :: att_value
        !! Value of the global attribute read.
    end subroutine
    !> Read a variable attribute with a string value from the netCDF file.
    subroutine cable_netcdf_file_get_att_var_string(this, var_name, att_name, att_value)
      import cable_netcdf_file_t
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read the attribute from.
      character(len=*), intent(in) :: att_name
        !! Name of the variable attribute to read.
      character(len=*), intent(out) :: att_value
        !! Value of the variable attribute read.
    end subroutine
    !> Read a variable attribute with an 32-bit integer value from the netCDF file.
    subroutine cable_netcdf_file_get_att_var_int32(this, var_name, att_name, att_value)
      import cable_netcdf_file_t, CABLE_NETCDF_INT32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read the attribute from.
      character(len=*), intent(in) :: att_name
        !! Name of the variable attribute to read.
      integer(kind=CABLE_NETCDF_INT32_KIND), intent(out) :: att_value
        !! Value of the variable attribute read.
    end subroutine
    !> Read a variable attribute with a 32-bit real value from the netCDF file.
    subroutine cable_netcdf_file_get_att_var_real32(this, var_name, att_name, att_value)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read the attribute from.
      character(len=*), intent(in) :: att_name
        !! Name of the variable attribute to read.
      real(kind=CABLE_NETCDF_REAL32_KIND), intent(out) :: att_value
        !! Value of the variable attribute read.
    end subroutine
    !> Read a variable attribute with a 64-bit real value from the netCDF file.
    subroutine cable_netcdf_file_get_att_var_real64(this, var_name, att_name, att_value)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL64_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read the attribute from.
      character(len=*), intent(in) :: att_name
        !! Name of the variable attribute to read.
      real(kind=CABLE_NETCDF_REAL64_KIND), intent(out) :: att_value
        !! Value of the variable attribute read.
    end subroutine
    !> Inquire the length of a dimension in the netCDF file by its name.
    subroutine cable_netcdf_file_inq_dim_len(this, dim_name, dim_len)
      import cable_netcdf_file_t
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: dim_name
        !! Name of the dimension to inquire.
      integer, intent(out) :: dim_len
        !* Length of the dimension returned. For unlimited dimensions, the
        ! number of records will be returned.
    end subroutine
    !> Inquire the number of dimensions of a variable in the netCDF file by its name.
    subroutine cable_netcdf_file_inq_var_ndims(this, var_name, ndims)
      import cable_netcdf_file_t
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to inquire.
      integer, intent(out) :: ndims
        !! Number of dimensions of the variable returned.
    end subroutine
    !> Write a 0-dimensional (scalar) 32-bit integer variable to the netCDF file.
    subroutine cable_netcdf_file_put_var_int32_0d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_INT32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to write to.
      integer(kind=CABLE_NETCDF_INT32_KIND), intent(in) :: values
        !! Value to write to the variable.
      integer, intent(in), optional :: start(:)
        !* Starting indices for writing the variable. If not provided, writing
        ! will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !* Count of elements to write for each dimension. If not provided, the
        ! entire variable will be written.
    end subroutine
    !> Write a 1-dimensional array of 32-bit integers to the netCDF file.
    subroutine cable_netcdf_file_put_var_int32_1d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_INT32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to write to.
      integer(kind=CABLE_NETCDF_INT32_KIND), intent(in) :: values(:)
        !! Array of values to write to the variable.
      integer, intent(in), optional :: start(:)
        !* Starting indices for writing the variable. If not provided, writing
        ! will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !* Count of elements to write for each dimension. If not provided, the
        ! entire variable will be written.
    end subroutine
    !> Write a 2-dimensional array of 32-bit integers to the netCDF file.
    subroutine cable_netcdf_file_put_var_int32_2d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_INT32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to write to.
      integer(kind=CABLE_NETCDF_INT32_KIND), intent(in) :: values(:, :)
        !! 2-dimensional array of values to write to the variable.
      integer, intent(in), optional :: start(:)
        !* Starting indices for writing the variable. If not provided, writing
        ! will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !* Count of elements to write for each dimension. If not provided, the
        ! entire variable will be written.
    end subroutine
    !> Write a 3-dimensional array of 32-bit integers to the netCDF file.
    subroutine cable_netcdf_file_put_var_int32_3d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_INT32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to write to.
      integer(kind=CABLE_NETCDF_INT32_KIND), intent(in) :: values(:, :, :)
        !! 3-dimensional array of values to write to the variable.
      integer, intent(in), optional :: start(:)
        !* Starting indices for writing the variable. If not provided, writing
        ! will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !* Count of elements to write for each dimension. If not provided, the
        ! entire variable will be written.
    end subroutine
    !> Write a 0-dimensional (scalar) 32-bit real variable to the netCDF file.
    subroutine cable_netcdf_file_put_var_real32_0d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to write to.
      real(kind=CABLE_NETCDF_REAL32_KIND), intent(in) :: values
        !! Scalar value to write to the variable.
      integer, intent(in), optional :: start(:)
        !* Starting indices for writing the variable. If not provided, writing
        ! will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !* Count of elements to write for each dimension. If not provided, the
        ! entire variable will be written.
    end subroutine
    !> Write a 1-dimensional array of 32-bit real values to the netCDF file.
    subroutine cable_netcdf_file_put_var_real32_1d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to write to.
      real(kind=CABLE_NETCDF_REAL32_KIND), intent(in) :: values(:)
        !! 1-dimensional array of values to write to the variable.
      integer, intent(in), optional :: start(:)
        !* Starting indices for writing the variable. If not provided, writing
        ! will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !* Count of elements to write for each dimension. If not provided, the
        ! entire variable will be written.
    end subroutine
    !> Write a 2-dimensional array of 32-bit real values to the netCDF file.
    subroutine cable_netcdf_file_put_var_real32_2d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to write to.
      real(kind=CABLE_NETCDF_REAL32_KIND), intent(in) :: values(:, :)
        !! 2-dimensional array of values to write to the variable.
      integer, intent(in), optional :: start(:)
        !* Starting indices for writing the variable. If not provided, writing
        ! will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !* Count of elements to write for each dimension. If not provided, the
        ! entire variable will be written.
    end subroutine
    !> Write a 3-dimensional array of 32-bit real values to the netCDF file.
    subroutine cable_netcdf_file_put_var_real32_3d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to write to.
      real(kind=CABLE_NETCDF_REAL32_KIND), intent(in) :: values(:, :, :)
        !! 3-dimensional array of values to write to the variable.
      integer, intent(in), optional :: start(:)
        !* Starting indices for writing the variable. If not provided, writing
        ! will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !* Count of elements to write for each dimension. If not provided, the
        ! entire variable will be written.
    end subroutine
    !> Write a 0-dimensional (scalar) 64-bit real variable to the netCDF file.
    subroutine cable_netcdf_file_put_var_real64_0d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL64_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to write to.
      real(kind=CABLE_NETCDF_REAL64_KIND), intent(in) :: values
        !! The scalar value to write.
      integer, intent(in), optional :: start(:)
        !* Starting indices for writing the variable. If not provided, writing
        ! will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !* Count of elements to write for each dimension. If not provided, the
        ! entire variable will be written.
    end subroutine
    !> Write a 1-dimensional array of 64-bit real values to the netCDF file.
    subroutine cable_netcdf_file_put_var_real64_1d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL64_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to write to.
      real(kind=CABLE_NETCDF_REAL64_KIND), intent(in) :: values(:)
        !! 1-dimensional array of values to write to the variable.
      integer, intent(in), optional :: start(:)
        !* Starting indices for writing the variable. If not provided, writing
        ! will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !* Count of elements to write for each dimension. If not provided, the
        ! entire variable will be written.
    end subroutine
    !> Write a 2-dimensional array of 64-bit real values to the netCDF file.
    subroutine cable_netcdf_file_put_var_real64_2d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL64_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to write to.
      real(kind=CABLE_NETCDF_REAL64_KIND), intent(in) :: values(:, :)
        !! 2-dimensional array of values to write to the variable.
      integer, intent(in), optional :: start(:)
        !* Starting indices for writing the variable. If not provided, writing
        ! will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !* Count of elements to write for each dimension. If not provided, the
        ! entire variable will be written.
    end subroutine
    !> Write a 3-dimensional array of 64-bit real values to the netCDF file.
    subroutine cable_netcdf_file_put_var_real64_3d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL64_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to write to.
      real(kind=CABLE_NETCDF_REAL64_KIND), intent(in) :: values(:, :, :)
        !! 3-dimensional array of values to write to the variable.
      integer, intent(in), optional :: start(:)
        !* Starting indices for writing the variable. If not provided, writing
        ! will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !* Count of elements to write for each dimension. If not provided, the
        ! entire variable will be written.
    end subroutine
    !> Write a 1-dimensional array of 32-bit integers to the netCDF file using decomposition information for parallel I/O.
    subroutine cable_netcdf_file_write_darray_int32_1d(this, var_name, values, decomp, fill_value, frame)
      import cable_netcdf_file_t, cable_netcdf_decomp_t, CABLE_NETCDF_INT32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to write to.
      integer(kind=CABLE_NETCDF_INT32_KIND), intent(in) :: values(:)
        !! 1-dimensional array of values to write to the variable.
      class(cable_netcdf_decomp_t), intent(inout) :: decomp
        !! Decomposition information for parallel I/O.
      integer(kind=CABLE_NETCDF_INT32_KIND), intent(in), optional :: fill_value
        !! Fill value to use for any missing data. If not provided, the default fill value for the variable's type will be used.
      integer, intent(in), optional :: frame
        !! Optional frame index for record variables. If provided, the data will be written to the specified frame along the unlimited dimension.
    end subroutine
    !> Write a 2-dimensional array of 32-bit integers to the netCDF file using decomposition information for parallel I/O.
    subroutine cable_netcdf_file_write_darray_int32_2d(this, var_name, values, decomp, fill_value, frame)
      import cable_netcdf_file_t, cable_netcdf_decomp_t, CABLE_NETCDF_INT32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to write to.
      integer(kind=CABLE_NETCDF_INT32_KIND), intent(in) :: values(:, :)
        !! 2-dimensional array of values to write to the variable.
      class(cable_netcdf_decomp_t), intent(inout) :: decomp
        !! Decomposition information for parallel I/O.
      integer(kind=CABLE_NETCDF_INT32_KIND), intent(in), optional :: fill_value
        !! Fill value to use for any missing data. If not provided, the default fill value for the variable's type will be used.
      integer, intent(in), optional :: frame
        !! Optional frame index for record variables. If provided, the data will be written to the specified frame along the unlimited dimension.
    end subroutine
    !> Write a 3-dimensional array of 32-bit integers to the netCDF file using decomposition information for parallel I/O.
    subroutine cable_netcdf_file_write_darray_int32_3d(this, var_name, values, decomp, fill_value, frame)
      import cable_netcdf_file_t, cable_netcdf_decomp_t, CABLE_NETCDF_INT32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to write to.
      integer(kind=CABLE_NETCDF_INT32_KIND), intent(in) :: values(:, :, :)
        !! 3-dimensional array of values to write to the variable.
      class(cable_netcdf_decomp_t), intent(inout) :: decomp
        !! Decomposition information for parallel I/O.
      integer(kind=CABLE_NETCDF_INT32_KIND), intent(in), optional :: fill_value
        !! Fill value to use for any missing data. If not provided, the default fill value for the variable's type will be used.
      integer, intent(in), optional :: frame
        !! Optional frame index for record variables. If provided, the data will be written to the specified frame along the unlimited dimension.
    end subroutine
    !> Write a 1-dimensional array of 32-bit real values to the netCDF file using decomposition information for parallel I/O.
    subroutine cable_netcdf_file_write_darray_real32_1d(this, var_name, values, decomp, fill_value, frame)
      import cable_netcdf_file_t, cable_netcdf_decomp_t, CABLE_NETCDF_REAL32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to write to.
      real(kind=CABLE_NETCDF_REAL32_KIND), intent(in) :: values(:)
        !! 1-dimensional array of values to write to the variable.
      class(cable_netcdf_decomp_t), intent(inout) :: decomp
        !! Decomposition information for parallel I/O.
      real(kind=CABLE_NETCDF_REAL32_KIND), intent(in), optional :: fill_value
        !! Fill value to use for any missing data. If not provided, the default fill value for the variable's type will be used.
      integer, intent(in), optional :: frame
        !! Optional frame index for record variables. If provided, the data will be written to the specified frame along the unlimited dimension.
    end subroutine
    !> Write a 2-dimensional array of 32-bit real values to the netCDF file using decomposition information for parallel I/O.
    subroutine cable_netcdf_file_write_darray_real32_2d(this, var_name, values, decomp, fill_value, frame)
      import cable_netcdf_file_t, cable_netcdf_decomp_t, CABLE_NETCDF_REAL32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to write to.
      real(kind=CABLE_NETCDF_REAL32_KIND), intent(in) :: values(:, :)
        !! 2-dimensional array of values to write to the variable.
      class(cable_netcdf_decomp_t), intent(inout) :: decomp
        !! Decomposition information for parallel I/O.
      real(kind=CABLE_NETCDF_REAL32_KIND), intent(in), optional :: fill_value
        !! Fill value to use for any missing data. If not provided, the default fill value for the variable's type will be used.
      integer, intent(in), optional :: frame
        !! Optional frame index for record variables. If provided, the data will be written to the specified frame along the unlimited dimension.
    end subroutine
    !> Write a 3-dimensional array of 32-bit real values to the netCDF file using decomposition information for parallel I/O.
    subroutine cable_netcdf_file_write_darray_real32_3d(this, var_name, values, decomp, fill_value, frame)
      import cable_netcdf_file_t, cable_netcdf_decomp_t, CABLE_NETCDF_REAL32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to write to.
      real(kind=CABLE_NETCDF_REAL32_KIND), intent(in) :: values(:, :, :)
        !! 3-dimensional array of values to write to the variable.
      class(cable_netcdf_decomp_t), intent(inout) :: decomp
        !! Decomposition information for parallel I/O.
      real(kind=CABLE_NETCDF_REAL32_KIND), intent(in), optional :: fill_value
        !! Fill value to use for any missing data. If not provided, the default fill value for the variable's type will be used.
      integer, intent(in), optional :: frame
        !! Optional frame index for record variables. If provided, the data will be written to the specified frame along the unlimited dimension.
    end subroutine
    !> Write a 2-dimensional array of 32-bit real values to the netCDF file using decomposition information for parallel I/O.
    subroutine cable_netcdf_file_write_darray_real64_1d(this, var_name, values, decomp, fill_value, frame)
      import cable_netcdf_file_t, cable_netcdf_decomp_t, CABLE_NETCDF_REAL64_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to write to.
      real(kind=CABLE_NETCDF_REAL64_KIND), intent(in) :: values(:)
        !! 1-dimensional array of values to write to the variable.
      class(cable_netcdf_decomp_t), intent(inout) :: decomp
        !! Decomposition information for parallel I/O.
      real(kind=CABLE_NETCDF_REAL64_KIND), intent(in), optional :: fill_value
        !! Fill value to use for any missing data. If not provided, the default fill value for the variable's type will be used.
      integer, intent(in), optional :: frame
        !! Optional frame index for record variables. If provided, the data will be written to the specified frame along the unlimited dimension.
    end subroutine
    !> Write a 2-dimensional array of 32-bit real values to the netCDF file using decomposition information for parallel I/O.
    subroutine cable_netcdf_file_write_darray_real64_2d(this, var_name, values, decomp, fill_value, frame)
      import cable_netcdf_file_t, cable_netcdf_decomp_t, CABLE_NETCDF_REAL64_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to write to.
      real(kind=CABLE_NETCDF_REAL64_KIND), intent(in) :: values(:, :)
        !! 2-dimensional array of values to write to the variable.
      class(cable_netcdf_decomp_t), intent(inout) :: decomp
        !! Decomposition information for parallel I/O.
      real(kind=CABLE_NETCDF_REAL64_KIND), intent(in), optional :: fill_value
        !! Fill value to use for any missing data. If not provided, the default fill value for the variable's type will be used.
      integer, intent(in), optional :: frame
        !! Optional frame index for record variables. If provided, the data will be written to the specified frame along the unlimited dimension.
    end subroutine
    !> Write a 3-dimensional array of 32-bit real values to the netCDF file using decomposition information for parallel I/O.
    subroutine cable_netcdf_file_write_darray_real64_3d(this, var_name, values, decomp, fill_value, frame)
      import cable_netcdf_file_t, cable_netcdf_decomp_t, CABLE_NETCDF_REAL64_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to write to.
      real(kind=CABLE_NETCDF_REAL64_KIND), intent(in) :: values(:, :, :)
        !! 3-dimensional array of values to write to the variable.
      class(cable_netcdf_decomp_t), intent(inout) :: decomp
        !! Decomposition information for parallel I/O.
      real(kind=CABLE_NETCDF_REAL64_KIND), intent(in), optional :: fill_value
        !! Fill value to use for any missing data. If not provided, the default fill value for the variable's type will be used.
      integer, intent(in), optional :: frame
        !! Optional frame index for record variables. If provided, the data will be written to the specified frame along the unlimited dimension.
    end subroutine
    !> Read a 0-dimensional (scalar) 32-bit integer variable from the netCDF file.
    subroutine cable_netcdf_file_get_var_int32_0d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_INT32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read from.
      integer(kind=CABLE_NETCDF_INT32_KIND), intent(out) :: values
        !! The scalar value read from the variable.
      integer, intent(in), optional :: start(:)
        !! Starting indices for reading the variable. If not provided, reading will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !! Count of elements to read for each dimension. If not provided, the entire variable will be read.
    end subroutine
    !> Read a 1-dimensional array of 32-bit integers from the netCDF file.
    subroutine cable_netcdf_file_get_var_int32_1d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_INT32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read from.
      integer(kind=CABLE_NETCDF_INT32_KIND), intent(out) :: values(:)
        !! 1-dimensional array to store the values read from the variable.
      integer, intent(in), optional :: start(:)
        !! Starting indices for reading the variable. If not provided, reading will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !! Count of elements to read for each dimension. If not provided, the entire variable will
    end subroutine
    !> Read a 2-dimensional array of 32-bit integers from the netCDF file.
    subroutine cable_netcdf_file_get_var_int32_2d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_INT32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read from.
      integer(kind=CABLE_NETCDF_INT32_KIND), intent(out) :: values(:, :)
        !! 2-dimensional array to store the values read from the variable.
      integer, intent(in), optional :: start(:)
        !! Starting indices for reading the variable. If not provided, reading will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !! Count of elements to read for each dimension. If not provided, the entire variable will be read.
    end subroutine
    !> Read a 3-dimensional array of 32-bit integers from the netCDF file.
    subroutine cable_netcdf_file_get_var_int32_3d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_INT32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read from.
      integer(kind=CABLE_NETCDF_INT32_KIND), intent(out) :: values(:, :, :)
        !! 3-dimensional array to store the values read from the variable.
      integer, intent(in), optional :: start(:)
        !! Starting indices for reading the variable. If not provided, reading will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !! Count of elements to read for each dimension. If not provided, the entire variable will be read.
    end subroutine
    !> Read a 0-dimensional (scalar) 32-bit real variable from the netCDF file.
    subroutine cable_netcdf_file_get_var_real32_0d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read from.
      real(kind=CABLE_NETCDF_REAL32_KIND), intent(out) :: values
        !! Scalar value to store the value read from the variable.
      integer, intent(in), optional :: start(:)
        !! Starting indices for reading the variable. If not provided, reading will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !! Count of elements to read for each dimension. If not provided, the entire variable will be read.
    end subroutine
    !> Read a 1-dimensional array of 32-bit real values from the netCDF file.
    subroutine cable_netcdf_file_get_var_real32_1d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read from.
      real(kind=CABLE_NETCDF_REAL32_KIND), intent(out) :: values(:)
        !! 1-dimensional array to store the values read from the variable.
      integer, intent(in), optional :: start(:)
        !! Starting indices for reading the variable. If not provided, reading will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !! Count of elements to read for each dimension. If not provided, the entire variable will be read.
    end subroutine
    !> Read a 2-dimensional array of 32-bit real values from the netCDF file.
    subroutine cable_netcdf_file_get_var_real32_2d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read from.
      real(kind=CABLE_NETCDF_REAL32_KIND), intent(out) :: values(:, :)
        !! 2-dimensional array to store the values read from the variable.
      integer, intent(in), optional :: start(:)
        !! Starting indices for reading the variable. If not provided, reading will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !! Count of elements to read for each dimension. If not provided, the entire variable will be read.
    end subroutine
    !> Read a 3-dimensional array of 32-bit real values from the netCDF file.
    subroutine cable_netcdf_file_get_var_real32_3d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read from.
      real(kind=CABLE_NETCDF_REAL32_KIND), intent(out) :: values(:, :, :)
        !! 3-dimensional array to store the values read from the variable.
      integer, intent(in), optional :: start(:)
        !! Starting indices for reading the variable. If not provided, reading will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !! Count of elements to read for each dimension. If not provided, the entire variable will be read.
    end subroutine
    !> Read a 0-dimensional (scalar) 64-bit real variable from the netCDF file.
    subroutine cable_netcdf_file_get_var_real64_0d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL64_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read from.
      real(kind=CABLE_NETCDF_REAL64_KIND), intent(out) :: values
        !! Scalar value to store the value read from the variable.
      integer, intent(in), optional :: start(:)
        !! Starting indices for reading the variable. If not provided, reading will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !! Count of elements to read for each dimension. If not provided, the entire variable will be read.
    end subroutine
    !> Read a 1-dimensional array of 64-bit real values from the netCDF file.
    subroutine cable_netcdf_file_get_var_real64_1d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL64_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read from.
      real(kind=CABLE_NETCDF_REAL64_KIND), intent(out) :: values(:)
        !! 1-dimensional array to store the values read from the variable.
      integer, intent(in), optional :: start(:)
        !! Starting indices for reading the variable. If not provided, reading will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !! Count of elements to read for each dimension. If not provided, the entire variable will be read.
    end subroutine
    !> Read a 2-dimensional array of 64-bit real values from the netCDF file.
    subroutine cable_netcdf_file_get_var_real64_2d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL64_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read from.
      real(kind=CABLE_NETCDF_REAL64_KIND), intent(out) :: values(:, :)
        !! 2-dimensional array to store the values read from the variable.
      integer, intent(in), optional :: start(:)
        !! Starting indices for reading the variable. If not provided, reading will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !! Count of elements to read for each dimension. If not provided, the entire variable will be read.
    end subroutine
    !> Read a 3-dimensional array of 64-bit real values from the netCDF file.
    subroutine cable_netcdf_file_get_var_real64_3d(this, var_name, values, start, count)
      import cable_netcdf_file_t, CABLE_NETCDF_REAL64_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read from.
      real(kind=CABLE_NETCDF_REAL64_KIND), intent(out) :: values(:, :, :)
        !! 3-dimensional array to store the values read from the variable.
      integer, intent(in), optional :: start(:)
        !! Starting indices for reading the variable. If not provided, reading will start at the beginning of the variable.
      integer, intent(in), optional :: count(:)
        !! Count of elements to read for each dimension. If not provided, the entire variable will be read.
    end subroutine
    !> Read a 1-dimensional array of 32-bit integers from the netCDF file using decomposition information for parallel I/O.
    subroutine cable_netcdf_file_read_darray_int32_1d(this, var_name, values, decomp, frame)
      import cable_netcdf_file_t, cable_netcdf_decomp_t, CABLE_NETCDF_INT32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read from.
      integer(kind=CABLE_NETCDF_INT32_KIND), intent(out) :: values(:)
        !! 1-dimensional array to store the values read from the variable.
      class(cable_netcdf_decomp_t), intent(inout) :: decomp
        !! Decomposition information for parallel I/O.
      integer, intent(in), optional :: frame
        !! Optional frame index for record variables. If provided, the data will be read from the specified frame along the unlimited dimension.
    end subroutine
    !> Read a 2-dimensional array of 32-bit integers from the netCDF file using decomposition information for parallel I/O.
    subroutine cable_netcdf_file_read_darray_int32_2d(this, var_name, values, decomp, frame)
      import cable_netcdf_file_t, cable_netcdf_decomp_t, CABLE_NETCDF_INT32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read from.
      integer(kind=CABLE_NETCDF_INT32_KIND), intent(out) :: values(:, :)
        !! 2-dimensional array to store the values read from the variable.
      class(cable_netcdf_decomp_t), intent(inout) :: decomp
        !! Decomposition information for parallel I/O.
      integer, intent(in), optional :: frame
        !! Optional frame index for record variables. If provided, the data will be read from the specified frame along the unlimited dimension.
    end subroutine
    !> Read a 3-dimensional array of 32-bit integers from the netCDF file using decomposition information for parallel I/O.
    subroutine cable_netcdf_file_read_darray_int32_3d(this, var_name, values, decomp, frame)
      import cable_netcdf_file_t, cable_netcdf_decomp_t, CABLE_NETCDF_INT32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read from.
      integer(kind=CABLE_NETCDF_INT32_KIND), intent(out) :: values(:, :, :)
        !! 3-dimensional array to store the values read from the variable.
      class(cable_netcdf_decomp_t), intent(inout) :: decomp
        !! Decomposition information for parallel I/O.
      integer, intent(in), optional :: frame
        !! Optional frame index for record variables. If provided, the data will be read from the specified frame along the unlimited dimension.
    end subroutine
    !> Read a 1-dimensional array of 32-bit real values from the netCDF file using decomposition information for parallel I/O.
    subroutine cable_netcdf_file_read_darray_real32_1d(this, var_name, values, decomp, frame)
      import cable_netcdf_file_t, cable_netcdf_decomp_t, CABLE_NETCDF_REAL32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read from.
      real(kind=CABLE_NETCDF_REAL32_KIND), intent(out) :: values(:)
        !! 1-dimensional array to store the values read from the variable.
      class(cable_netcdf_decomp_t), intent(inout) :: decomp
        !! Decomposition information for parallel I/O.
      integer, intent(in), optional :: frame
        !! Optional frame index for record variables. If provided, the data will be read from the specified frame along the unlimited dimension.
    end subroutine
    !> Read a 2-dimensional array of 32-bit real values from the netCDF file using decomposition information for parallel I/O.
    subroutine cable_netcdf_file_read_darray_real32_2d(this, var_name, values, decomp, frame)
      import cable_netcdf_file_t, cable_netcdf_decomp_t, CABLE_NETCDF_REAL32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read from.
      real(kind=CABLE_NETCDF_REAL32_KIND), intent(out) :: values(:, :)
        !! 2-dimensional array to store the values read from the variable.
      class(cable_netcdf_decomp_t), intent(inout) :: decomp
        !! Decomposition information for parallel I/O.
      integer, intent(in), optional :: frame
        !! Optional frame index for record variables. If provided, the data will be read from the specified frame along the unlimited dimension.
    end subroutine
    !> Read a 3-dimensional array of 32-bit real values from the netCDF file using decomposition information for parallel I/O.
    subroutine cable_netcdf_file_read_darray_real32_3d(this, var_name, values, decomp, frame)
      import cable_netcdf_file_t, cable_netcdf_decomp_t, CABLE_NETCDF_REAL32_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read from.
      real(kind=CABLE_NETCDF_REAL32_KIND), intent(out) :: values(:, :, :)
        !! 3-dimensional array to store the values read from the variable.
      class(cable_netcdf_decomp_t), intent(inout) :: decomp
        !! Decomposition information for parallel I/O.
      integer, intent(in), optional :: frame
        !! Optional frame index for record variables. If provided, the data will be read from the specified frame along the unlimited dimension.
    end subroutine
    !> Read a 1-dimensional array of 64-bit real values from the netCDF file using decomposition information for parallel I/O.
    subroutine cable_netcdf_file_read_darray_real64_1d(this, var_name, values, decomp, frame)
      import cable_netcdf_file_t, cable_netcdf_decomp_t, CABLE_NETCDF_REAL64_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read from.
      real(kind=CABLE_NETCDF_REAL64_KIND), intent(out) :: values(:)
        !! 1-dimensional array to store the values read from the variable.
      class(cable_netcdf_decomp_t), intent(inout) :: decomp
        !! Decomposition information for parallel I/O.
      integer, intent(in), optional :: frame
        !! Optional frame index for record variables. If provided, the data will be read from the specified frame along the unlimited dimension.
    end subroutine
    !> Read a 2-dimensional array of 64-bit real values from the netCDF file using decomposition information for parallel I/O.
    subroutine cable_netcdf_file_read_darray_real64_2d(this, var_name, values, decomp, frame)
      import cable_netcdf_file_t, cable_netcdf_decomp_t, CABLE_NETCDF_REAL64_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read from.
      real(kind=CABLE_NETCDF_REAL64_KIND), intent(out) :: values(:, :)
        !! 2-dimensional array to store the values read from the variable.
      class(cable_netcdf_decomp_t), intent(inout) :: decomp
        !! Decomposition information for parallel I/O.
      integer, intent(in), optional :: frame
        !! Optional frame index for record variables. If provided, the data will be read from the specified frame along the unlimited dimension.
    end subroutine
    !> Read a 3-dimensional array of 64-bit real values from the netCDF file using decomposition information for parallel I/O.
    subroutine cable_netcdf_file_read_darray_real64_3d(this, var_name, values, decomp, frame)
      import cable_netcdf_file_t, cable_netcdf_decomp_t, CABLE_NETCDF_REAL64_KIND
      class(cable_netcdf_file_t), intent(inout) :: this
      character(len=*), intent(in) :: var_name
        !! Name of the variable to read from.
      real(kind=CABLE_NETCDF_REAL64_KIND), intent(out) :: values(:, :, :)
        !! 3-dimensional array to store the values read from the variable.
      class(cable_netcdf_decomp_t), intent(inout) :: decomp
        !! Decomposition information for parallel I/O.
      integer, intent(in), optional :: frame
        !! Optional frame index for record variables. If provided, the data will be read from the specified frame along the unlimited dimension.
    end subroutine
  end interface

  type, abstract :: cable_netcdf_io_t
    !* Abstract type defining the interface for netCDF I/O handlers.
    ! This allows for different implementations (e.g. NetCDF, ParallelIO) to be
    ! used interchangeably within the CABLE code
  contains
    procedure(cable_netcdf_io_init), deferred :: init
    procedure(cable_netcdf_io_finalise), deferred :: finalise
    procedure(cable_netcdf_io_create_file), deferred :: create_file
    procedure(cable_netcdf_io_open_file), deferred :: open_file
    procedure(cable_netcdf_io_create_decomp), deferred :: create_decomp
  end type

  abstract interface
    !> Initialise the netcdf I/O handler.
    !! This procedure is called during module initialisation and can be used to set
    !! up any necessary state or resources for the I/O handler.
    subroutine cable_netcdf_io_init(this)
      import cable_netcdf_io_t
      class(cable_netcdf_io_t), intent(inout) :: this
    end subroutine
    !> Finalise the netcdf I/O handler.
    !! This procedure is called during module finalization and can be used to clean
    !! up any resources allocated by the I/O handler.
    subroutine cable_netcdf_io_finalise(this)
      import cable_netcdf_io_t
      class(cable_netcdf_io_t), intent(inout) :: this
    end subroutine
    !> Create a new netCDF file with the specified path and I/O type.
    function cable_netcdf_io_create_file(this, path, iotype, mode) result(file)
      import cable_netcdf_io_t, cable_netcdf_file_t
      class(cable_netcdf_io_t), intent(inout) :: this
      character(len=*), intent(in) :: path
        !! Path to the netCDF file to create.
      integer, intent(in) :: iotype
        !! I/O type to use for the file using the `CABLE_NETCDF_IOTYPE_*` constants.
      integer, intent(in), optional :: mode
        !! Optional mode flags for file creation using the `CABLE_NETCDF_MODE_*` constants.
      class(cable_netcdf_file_t), allocatable :: file
    end function
    !> Open an existing netCDF file with the specified path and I/O type.
    function cable_netcdf_io_open_file(this, path, iotype, mode) result(file)
      import cable_netcdf_io_t, cable_netcdf_file_t
      class(cable_netcdf_io_t), intent(inout) :: this
      character(len=*), intent(in) :: path
        !! Path to the netCDF file to open.
      integer, intent(in) :: iotype
        !! I/O type to use for the file using the `CABLE_NETCDF_IOTYPE_*` constants.
      integer, intent(in), optional :: mode
        !! Optional mode flags for file opening using the `CABLE_NETCDF_MODE_*` constants.
      class(cable_netcdf_file_t), allocatable :: file
    end function
    !> Create a new decomposition for parallel I/O.
    !! This follows the degree of freedom approach for specifying I/O
    !! decompositions as described in Denis et al. (2011)
    !! [[10.1177/1094342011428143](https://www.doi.org/10.1177/1094342011428143)].
    function cable_netcdf_io_create_decomp(this, compmap, dims, type) result(decomp)
      import cable_netcdf_io_t, cable_netcdf_decomp_t
      class(cable_netcdf_io_t), intent(inout) :: this
      integer, intent(in) :: compmap(:)
        !* An array of data offsets describing where each element of the
        ! in-memory array is located in the netCDF variable data on disk.
      integer, intent(in) :: dims(:)
        !! An array of the global dimensions used to describe the shape of the data in the netCDF file
      integer, intent(in) :: type
        !! The data type of the in-memory array using the `CABLE_NETCDF_TYPE_*` constants.
      class(cable_netcdf_decomp_t), allocatable :: decomp
    end function
  end interface

  interface
    module subroutine cable_netcdf_mod_init(mpi_grp)
      !* Module initialisation procedure for cable_netcdf_mod.
      ! This procedure should be called before any other procedures in this module.
      type(mpi_grp_t), intent(in) :: mpi_grp
        !* The MPI group for the set of processes that will be performing netCDF I/O operations.
        ! All procedures in cable_netcdf_mod should be called collectively by all
        ! processes in this group.
    end subroutine
  end interface

  !> The internal I/O handler to use for all netCDF file operations.
  class(cable_netcdf_io_t), allocatable, private :: cable_netcdf_io_handler

contains

  !> Module finalization procedure for the cable_netcdf_mod module.
  subroutine cable_netcdf_mod_end()
    if (.not. allocated(cable_netcdf_io_handler)) then
      call cable_abort("cable_netcdf_io_handler is not allocated. Ensure cable_netcdf_mod_init has been called.", file=__FILE__, line=__LINE__)
    end if
    call cable_netcdf_io_handler%finalise()
  end subroutine

  !> Create a new netCDF file with the specified path and I/O type.
  function cable_netcdf_create_file(path, iotype, mode) result(file)
    character(len=*), intent(in) :: path
      !! Path to the netCDF file to create.
    integer, intent(in) :: iotype
      !! I/O type to use for the file using the `CABLE_NETCDF_IOTYPE_*` constants.
    integer, intent(in), optional :: mode
      !! Optional mode flags for file creation using the `CABLE_NETCDF_MODE_*` constants.
    class(cable_netcdf_file_t), allocatable :: file
    if (.not. allocated(cable_netcdf_io_handler)) then
      call cable_abort("cable_netcdf_io_handler is not allocated. Ensure cable_netcdf_mod_init has been called.", file=__FILE__, line=__LINE__)
    end if
    file = cable_netcdf_io_handler%create_file(path, iotype, mode)
  end function

  !> Open an existing netCDF file with the specified path and I/O type.
  function cable_netcdf_open_file(path, iotype, mode) result(file)
    character(len=*), intent(in) :: path
      !! Path to the netCDF file to open.
    integer, intent(in) :: iotype
      !! I/O type to use for the file using the `CABLE_NETCDF_IOTYPE_*` constants.
    integer, intent(in), optional :: mode
      !! Optional mode flags for file opening using the `CABLE_NETCDF_MODE_*` constants.
    class(cable_netcdf_file_t), allocatable :: file
    if (.not. allocated(cable_netcdf_io_handler)) then
      call cable_abort("cable_netcdf_io_handler is not allocated. Ensure cable_netcdf_mod_init has been called.", file=__FILE__, line=__LINE__)
    end if
    file = cable_netcdf_io_handler%open_file(path, iotype, mode)
  end function

  !> Create a new decomposition for parallel I/O.
  !! This follows the degree of freedom approach for specifying I/O
  !! decompositions as described in Denis et al. (2011)
  !! [[10.1177/1094342011428143](https://www.doi.org/10.1177/1094342011428143)].
  function cable_netcdf_create_decomp(compmap, dims, type) result(decomp)
    integer, intent(in) :: compmap(:)
      !* An array of data offsets describing where each element of the
      ! in-memory array is located in the netCDF variable data on disk.
    integer, intent(in) :: dims(:)
      !! An array of the global dimensions used to describe the shape of the data in the netCDF file
    integer, intent(in) :: type
      !! The data type of the in-memory array using the `CABLE_NETCDF_TYPE_*` constants.
    class(cable_netcdf_decomp_t), allocatable :: decomp
    if (.not. allocated(cable_netcdf_io_handler)) then
      call cable_abort("cable_netcdf_io_handler is not allocated. Ensure cable_netcdf_mod_init has been called.", file=__FILE__, line=__LINE__)
    end if
    decomp = cable_netcdf_io_handler%create_decomp(compmap, dims, type)
  end function

end module cable_netcdf_mod
