module cvmix_io

!BOP
!\newpage
! !MODULE: cvmix_io
!
! !DESCRIPTION:
!  This module contains routines to read CVmix variables from data files or
!  output CVmix variables to data files. Currently only ascii and netCDF output
!  are supported, as well as netCDF input, but the plan is to also include plain
!  binary input / output as well.
!\\
!\\

! !USES:

   use cvmix_kinds_and_types, only : cvmix_data_type,                         &
                                     cvmix_r8,                                &
                                     cvmix_zero,                              &
                                     cvmix_strlen
   use cvmix_utils,           only : cvmix_att_name
#ifdef _NETCDF
   use netcdf
#endif

!EOP

  implicit none
  private
  save

!BOP
! !PUBLIC MEMBER FUNCTIONS:
  public :: cvmix_io_open
  public :: cvmix_input_read
#ifdef _NETCDF
  public :: cvmix_input_get_netcdf_dim
#endif
  public :: cvmix_output_write
  public :: cvmix_io_close
  public :: cvmix_io_close_all
  public :: print_open_files
  public :: cvmix_output_write_att

  interface cvmix_input_read
    module procedure cvmix_input_read_1d_double
    module procedure cvmix_input_read_2d_integer
    module procedure cvmix_input_read_2d_double
    module procedure cvmix_input_read_3d_double
  end interface

  interface cvmix_output_write
    module procedure cvmix_output_write_single_col
    module procedure cvmix_output_write_multi_col
    module procedure cvmix_output_write_2d_double
    module procedure cvmix_output_write_3d_double
  end interface

  interface cvmix_output_write_att
    module procedure cvmix_output_write_att_integer
    module procedure cvmix_output_write_att_real
    module procedure cvmix_output_write_att_string
  end interface

! !DEFINED PARAMETERS:
  integer, parameter :: ASCII_FILE_TYPE  = 1
  integer, parameter :: BIN_FILE_TYPE    = 2
  integer, parameter :: NETCDF_FILE_TYPE = 3
  integer, parameter :: FILE_NOT_FOUND   = 404

  ! Probably not the best technique, but going to use a linked list to keep
  ! track of what files are open / what format they are (ascii, bin, or nc)
  type :: cvmix_file_entry
    integer :: file_id
    integer :: file_type
    character(len=cvmix_strlen) :: file_name
    type(cvmix_file_entry), pointer :: prev
    type(cvmix_file_entry), pointer :: next
  end type

  type(cvmix_file_entry), allocatable, target :: file_database(:)
!EOP

contains

!BOP

! !IROUTINE: cvmix_io_open
! !INTERFACE:

  subroutine cvmix_io_open(file_id, file_name, file_format, read_only)

! !DESCRIPTION:
!  Routine to open a file for reading and / or writing. The goal is to support
!  plain text (currently working for writing only), netCDF (working for both
!  reading and writing), and plain binary (not supported at this time). Besides
!  opening the file, this routine also adds an entry to file\_database, a
!  linked list that keeps track of what files are open and what type of file
!  each identifier refers to. So it will be possible to output the same data in
!  ascii and netCDF, for example.
!\\
!\\

! !USES:
!  Only those used by entire module.

! !INPUT PARAMETERS:
    character(len=*),  intent(in) :: file_name, file_format
    logical, optional, intent(in) :: read_only

! !OUTPUT PARAMETERS:
    integer, intent(out) :: file_id

! !LOCAL VARIABLES:
    type(cvmix_file_entry), pointer :: file_index
    logical                         :: readonly
!EOP
!BOC

    if (present(read_only)) then
      readonly = read_only
    else
      readonly = .false.
    end if
    ! Need routine that will produce unique file_id
    ! Starting with 615 and incrementing by one for now...
    file_id = 615
    if (.not.(allocated(file_database))) then
      allocate(file_database(1))
      file_database(1)%file_id = file_id
      nullify(file_database(1)%prev)
      nullify(file_database(1)%next)
      file_index => file_database(1)
    else
      file_id = file_id+1
      file_index => file_database(1)
      do while(associated(file_index%next))
        file_id = file_id+1
        file_index => file_index%next
      end do
      allocate(file_index%next)
      file_index%next%file_id   = file_id
      file_index%next%prev     => file_index
      nullify(file_index%next%next)
      file_index => file_index%next
    end if
    file_index%file_name = trim(file_name)

    select case (trim(file_format))
      case ('nc')
#ifndef _NETCDF
        print*, "ERROR: you must compile -D_NETCDF to open a netCDF file"
        stop 1
#else
        file_index%file_type = NETCDF_FILE_TYPE
        ! Note: at this point, will either open file with NOWRITE for
        !       read-only, or will clobber file to write new data to it.
        !       Eventually we should add a check to see if the file exists
        !       and open it with NF90_WRITE for non-readonly files, but that
        !       will require checking to see if dims / variables already exist
        !       (and are correct dimension) before trying to define them.
        if (readonly) then
          call netcdf_check(nf90_open(file_name, NF90_NOWRITE, file_id))
        else
          call netcdf_check(nf90_create(file_name, NF90_CLOBBER, file_id))
        end if
        file_index%file_id = file_id
        ! For outputting params, want vertical dimension to be unlimited?
        ! (Will be looping through the levels)
#endif
      case ('ascii')
        file_index%file_type = ASCII_FILE_TYPE
        if (readonly) then
          open(file_id, file = file_name, status="old")
        else
          open(file_id, file = file_name, status="replace")
        end if
      case default
        print*, "ERROR: ", trim(file_format)," is not a valid file type"

    end select
!EOC

  end subroutine cvmix_io_open

!BOP

! !IROUTINE: cvmix_input_read_1d_double
! !INTERFACE:

  subroutine cvmix_input_read_1d_double(file_id, var_name, local_copy)

! !DESCRIPTION:
!  Routine to read the requested 1D variable from a netcdf file and save it to
!  a local array (file must be opened using cvmix\_io\_open with the optional
!  argument readonly = .true.). Called with cvmix\_input\_read (see interface
!  in PUBLIC MEMBER FUNCTIONS above). At this time, only works with netcdf
!  files.
!\\
!\\

! !USES:
!  Only those used by entire module.

! !INPUT PARAMETERS:
    integer,          intent(in)  :: file_id
    character(len=*), intent(in)  :: var_name
    real(cvmix_r8), dimension(:), intent(out) :: local_copy

! !LOCAL VARIABLES:
    logical :: lerr_in_read
#ifdef _NETCDF
    integer :: varid, ndims, xtype
    integer :: dims1, dims2
    integer, dimension(1) :: dims
#endif
!EOP
!BOC

  local_copy = 0.0
  lerr_in_read = .false.
    select case (get_file_type(file_id))
#ifdef _NETCDF
      case (NETCDF_FILE_TYPE)
        varid = get_netcdf_varid(file_id, var_name, xtype, ndims)
        lerr_in_read = (varid.eq.-1)

        if (lerr_in_read) then
          write(*,"(A,A,1X,A,A)") "Could not find variable ", trim(var_name), &
                                  "in ", trim(get_file_name(file_id))
        else
          ! A couple more error checks
          if (xtype.ne.NF90_DOUBLE) then
            write(*, "(A,1X,A,1X,A)") "ERROR: variable", trim(var_name), &
                                      "is not a single-precision float!"
            lerr_in_read = .true.
          end if
          if (ndims.ne.1) then
            write(*,"(A,1X,I0,A)") "ERROR: you are trying to read a", ndims, &
                                   "-dimensional array into a 1D array."
            lerr_in_read = .true.
          end if
        end if

        if (.not.lerr_in_read) then
          call netcdf_check(nf90_inquire_variable(file_id, varid, dimids=dims))
          dims1 = dims(1)
          call netcdf_check(nf90_inquire_dimension(file_id, dims1, len=dims2))

          dims1 = size(local_copy)
          if (dims1.eq.dims2) then
            call netcdf_check(nf90_get_var(file_id, varid, local_copy))
          else
            write(*,"(A,1X,I0,A,1X,I0,A)") "ERROR: you are trying to read a", &
                   dims2, "-dimensional array into a local variable that is", &
                   dims1, "-dimensional."
            lerr_in_read = .true.
          end if
        end if
#endif
      case DEFAULT
        lerr_in_read = .true.
        write(*,"(A,1X,A,1X,A)") "ERROR: no read support for binary files,", &
                                 "use netCDF to read", trim(var_name)
    end select

    if (lerr_in_read) then
      call cvmix_io_close_all
      stop 1
    end if
!EOC

  end subroutine cvmix_input_read_1d_double

!BOP

! !IROUTINE: cvmix_input_read_2d_integer
! !INTERFACE:

  subroutine cvmix_input_read_2d_integer(file_id, var_name, local_copy)

! !DESCRIPTION:
!  Routine to read the requested 2D variable from a netcdf file and save it to
!  a local array (file must be opened using cvmix\_io\_open with the optional
!  argument readonly = .true.). Called with cvmix\_input\_read (see interface
!  in PUBLIC MEMBER FUNCTIONS above). At this time, only works with netcdf
!  files.
!\\
!\\

! !USES:
!  Only those used by entire module.

! !INPUT PARAMETERS:
    integer,          intent(in)  :: file_id
    character(len=*), intent(in)  :: var_name
    integer, dimension(:,:),  intent(out) :: local_copy

! !LOCAL VARIABLES:
    logical :: lerr_in_read
#ifdef _NETCDF
    integer :: varid, ndims, xtype, i
    integer, dimension(2) :: dims1, dims2
#endif
!EOP
!BOC

  local_copy = 0
  lerr_in_read = .false.
    select case (get_file_type(file_id))
#ifdef _NETCDF
      case (NETCDF_FILE_TYPE)
        varid = get_netcdf_varid(file_id, var_name, xtype, ndims)
        lerr_in_read = (varid.eq.-1)

        if (lerr_in_read) then
          write(*,"(A,A,1X,A,A)") "Could not find variable ", trim(var_name), &
                                  "in ", trim(get_file_name(file_id))
        else
          ! A couple more error checks
          if (xtype.ne.NF90_INT) then
            write(*, "(A,1X,A,1X,A)") "ERROR: variable", trim(var_name), &
                                      "is not an integer!"
            lerr_in_read = .true.
          end if
          if (ndims.ne.2) then
            write(*,"(A,1X,I0,A)") "ERROR: you are trying to read a", ndims, &
                                   "-dimensional array into a 2D array."
            lerr_in_read = .true.
          end if
        end if

        if (.not.lerr_in_read) then
          call netcdf_check(nf90_inquire_variable(file_id, varid, dimids=dims1))
          do i=1,2
            call netcdf_check(nf90_inquire_dimension(file_id, dims1(i), &
                              len=dims2(i)))
          end do

          dims1 = shape(local_copy)
          if (all(dims1.eq.dims2)) then
            call netcdf_check(nf90_get_var(file_id, varid, local_copy))
          else
            write(*,"(A,1X,I0,1X,A,1X,I0,1X,A,1X,I0,1X,A,1X,I0)") &
                    "ERROR: you are trying to read a", dims2(1), "by", dims2(2), &
                    "array into a local variable that is", dims1(1), "by", dims1(2)
            lerr_in_read = .true.
          end if
        end if
#endif
      case DEFAULT
        lerr_in_read = .true.
        write(*,"(A,1X,A,1X,A)") "ERROR: no read support for binary files,", &
                                 "use netCDF to read", trim(var_name)
    end select

    if (lerr_in_read) then
      call cvmix_io_close_all
      stop 1
    end if
!EOC

  end subroutine cvmix_input_read_2d_integer

!BOP

! !IROUTINE: cvmix_input_read_2d_double
! !INTERFACE:

  subroutine cvmix_input_read_2d_double(file_id, var_name, local_copy)

! !DESCRIPTION:
!  Routine to read the requested 2D variable from a netcdf file and save it to
!  a local array (file must be opened using cvmix\_io\_open with the optional
!  argument readonly = .true.). Called with cvmix\_input\_read (see interface
!  in PUBLIC MEMBER FUNCTIONS above). At this time, only works with netcdf
!  files.
!\\
!\\

! !USES:
!  Only those used by entire module.

! !INPUT PARAMETERS:
    integer,          intent(in)  :: file_id
    character(len=*), intent(in)  :: var_name
    real(cvmix_r8), dimension(:,:),  intent(out) :: local_copy

! !LOCAL VARIABLES:
    logical :: lerr_in_read
#ifdef _NETCDF
    integer :: varid, i, ndims, xtype
    integer, dimension(2) :: dims1, dims2
#endif
!EOP
!BOC

  local_copy = cvmix_zero
  lerr_in_read = .false.
    select case (get_file_type(file_id))
#ifdef _NETCDF
      case (NETCDF_FILE_TYPE)
        varid = get_netcdf_varid(file_id, var_name, xtype, ndims)
        lerr_in_read = (varid.eq.-1)

        if (lerr_in_read) then
          write(*,"(A,A,1X,A,A)") "Could not find variable ", trim(var_name), &
                                  "in ", trim(get_file_name(file_id))
        else
          ! A couple more error checks
          if (xtype.ne.NF90_DOUBLE) then
            write(*, "(A,1X,A,1X,A)") "ERROR: variable", trim(var_name), &
                                      "is not a double-precision float!"
            lerr_in_read = .true.
          end if
          if (ndims.ne.2) then
            write(*,"(A,1X,I0,A)") "ERROR: you are trying to read a", ndims, &
                                   "-dimensional array into a 2D array."
            lerr_in_read = .true.
          end if
        end if

        if (.not.lerr_in_read) then
          call netcdf_check(nf90_inquire_variable(file_id, varid, dimids=dims1))
          do i=1,2
            call netcdf_check(nf90_inquire_dimension(file_id, dims1(i), &
                              len=dims2(i)))
          end do

          dims1 = shape(local_copy)
          if (all(dims1.eq.dims2)) then
            call netcdf_check(nf90_get_var(file_id, varid, local_copy))
          else
            write(*,"(A,1X,I0,1X,A,1X,I0,1X,A,1X,I0,1X,A,1X,I0)") &
                    "ERROR: you are trying to read a", dims2(1), "by", dims2(2), &
                    "array into a local variable that is", dims1(1), "by", dims1(2)
            lerr_in_read = .true.
          end if
        end if
#endif
      case DEFAULT
        lerr_in_read = .true.
        write(*,"(A,1X,A,1X,A)") "ERROR: no read support for binary files,", &
                                 "use netCDF to read", trim(var_name)
    end select

    if (lerr_in_read) then
      call cvmix_io_close_all
      stop 1
    end if
!EOC

  end subroutine cvmix_input_read_2d_double

!BOP

! !IROUTINE: cvmix_input_read_3d_double
! !INTERFACE:

  subroutine cvmix_input_read_3d_double(file_id, var_name, local_copy)

! !DESCRIPTION:
!  Routine to read the requested 2D variable from a netcdf file and save it to
!  a local array (file must be opened using cvmix\_io\_open with the optional
!  argument readonly = .true.). Called with cvmix\_input\_read (see interface
!  in PUBLIC MEMBER FUNCTIONS above). At this time, only works with netcdf
!  files.
!\\
!\\

! !USES:
!  Only those used by entire module.

! !INPUT PARAMETERS:
    integer,          intent(in)  :: file_id
    character(len=*), intent(in)  :: var_name
    real(cvmix_r8), dimension(:,:,:),  intent(out) :: local_copy

! !LOCAL VARIABLES:
    logical :: lerr_in_read
#ifdef _NETCDF
    integer :: varid, i, ndims, xtype
    integer, dimension(3) :: dims1, dims2
#endif
!EOP
!BOC

  local_copy = cvmix_zero
  lerr_in_read = .false.
    select case (get_file_type(file_id))
#ifdef _NETCDF
      case (NETCDF_FILE_TYPE)
        varid = get_netcdf_varid(file_id, var_name, xtype, ndims)
        lerr_in_read = (varid.eq.-1)

        if (lerr_in_read) then
          write(*,"(A,A,1X,A,A)") "Could not find variable ", trim(var_name), &
                                  "in ", trim(get_file_name(file_id))
        else
          ! A couple more error checks
          if (xtype.ne.NF90_DOUBLE) then
            write(*, "(A,1X,A,1X,A)") "ERROR: variable", trim(var_name), &
                                      "is not a double-precision float!"
            lerr_in_read = .true.
          end if
          if (ndims.ne.3) then
            write(*,"(A,1X,I0,A)") "ERROR: you are trying to read a", ndims, &
                                   "-dimensional array into a 2D array."
            lerr_in_read = .true.
          end if
        end if

        if (.not.lerr_in_read) then
          call netcdf_check(nf90_inquire_variable(file_id, varid, dimids=dims1))
          do i=1,3
            call netcdf_check(nf90_inquire_dimension(file_id, dims1(i), &
                              len=dims2(i)))
          end do

          dims1 = shape(local_copy)
          if (all(dims1.eq.dims2)) then
            call netcdf_check(nf90_get_var(file_id, varid, local_copy))
          else
            write(*,"(A,1X,I0,1X,A,1X,I0,1X,A,1X,I0,1X,A,1X,I0,1X,A,1X,I0,1X,A,1X,I0)") &
                    "ERROR: you are trying to read a", dims2(1), "by", dims2(2), &
                    "by", dims2(3), "array into a local variable that is", &
                    dims1(1), "by", dims1(2), "by", dims1(3)
            lerr_in_read = .true.
          end if
        end if
#endif
      case DEFAULT
        lerr_in_read = .true.
        write(*,"(A,1X,A,1X,A)") "ERROR: no read support for binary files,", &
                                 "use netCDF to read", trim(var_name)
    end select

    if (lerr_in_read) then
      call cvmix_io_close_all
      stop 1
    end if
!EOC

  end subroutine cvmix_input_read_3d_double

!BOP

! !IROUTINE: cvmix_output_write_single_col
! !INTERFACE:

  subroutine cvmix_output_write_single_col(file_id, CVmix_vars, var_names,    &
                                           buoyancy_cntr)

! !DESCRIPTION:
!  Routine to write the requested variables from a single column to a file
!  (file must be opened using cvmix\_io\_open to ensure it is written
!  correctly). Called with cvmix\_output\_write (see interface in PUBLIC
!  MEMBER FUNCTIONS above).
!\\
!\\

! !USES:
!  Only those used by entire module.

! !INPUT PARAMETERS:
    integer,                                    intent(in) :: file_id
    type(cvmix_data_type)    ,                  intent(in) :: CVmix_vars
    character(len=*),             dimension(:), intent(in) :: var_names
    real(cvmix_r8), optional,                                                 &
                    dimension(CVmix_vars%nlev), intent(in) :: buoyancy_cntr

! !LOCAL VARIABLES:
    integer :: kw, var, nlev
#ifdef _NETCDF
    integer                            :: nt, nt_id, nw, nw_id
    integer, dimension(:), allocatable :: var_id
#endif
!EOP
!BOC

    nlev = CVmix_vars%nlev
    select case (get_file_type(file_id))
#ifdef _NETCDF
      case (NETCDF_FILE_TYPE)
        nt = nlev
        nw = nlev+1
        call netcdf_check(nf90_def_dim(file_id, "nt", nt, nt_id))
        call netcdf_check(nf90_def_dim(file_id, "nw", nw, nw_id))
        allocate(var_id(size(var_names)))
        do var=1,size(var_names)
          select case(trim(cvmix_att_name(var_names(var))))
            case ("zt_cntr")
              call netcdf_check(nf90_def_var(file_id, "zt", NF90_DOUBLE,      &
                                             (/nt_id/), var_id(var)))
            case ("zw_iface")
              call netcdf_check(nf90_def_var(file_id, "zw", NF90_DOUBLE,      &
                                             (/nw_id/), var_id(var)))
            case ("ShearRichardson_iface")
              call netcdf_check(nf90_def_var(file_id, "ShearRichardson",      &
                                             NF90_DOUBLE, (/nw_id/),          &
                                             var_id(var)))
            case ("BulkRichardson_cntr")
              call netcdf_check(nf90_def_var(file_id, "BulkRichardson",       &
                                             NF90_DOUBLE, (/nt_id/),          &
                                             var_id(var)))
            case ("Mdiff_iface")
              call netcdf_check(nf90_def_var(file_id, "Mdiff", NF90_DOUBLE,   &
                                             (/nw_id/), var_id(var)))
            case ("Tdiff_iface")
              call netcdf_check(nf90_def_var(file_id, "Tdiff", NF90_DOUBLE,   &
                                             (/nw_id/), var_id(var)))
            case ("Sdiff_iface")
              call netcdf_check(nf90_def_var(file_id, "Sdiff", NF90_DOUBLE,   &
                                             (/nw_id/), var_id(var)))
            case ("strat_param")
              call netcdf_check(nf90_def_var(file_id, "Rrho", NF90_DOUBLE,    &
                                             (/nt_id/), var_id(var)))
            case ("buoyancy_cntr")
              call netcdf_check(nf90_def_var(file_id, "buoyancy", NF90_DOUBLE,&
                                             (/nt_id/), var_id(var)))
            case ("SqrBuoyancyFreq_iface")
              call netcdf_check(nf90_def_var(file_id, "SqrBuoyancyFreq",      &
                                             NF90_DOUBLE, (/nw_id/),          &
                                             var_id(var)))
            case ("Vx_cntr")
              call netcdf_check(nf90_def_var(file_id, "U", NF90_DOUBLE,       &
                                             (/nt_id/), var_id(var)))
            case ("Vy_cntr")
              call netcdf_check(nf90_def_var(file_id, "V", NF90_DOUBLE,       &
                                             (/nt_id/), var_id(var)))
            case DEFAULT
              print*, "ERROR: unable to write variable ", var_names(var)
              stop 1
          end select
        end do
        call netcdf_check(nf90_enddef(file_id))
        do var=1,size(var_names)
          select case(trim(cvmix_att_name(var_names(var))))
            case ("zt_cntr")
              call netcdf_check(nf90_put_var(file_id, var_id(var),            &
                                             CVmix_vars%zt_cntr(1:nlev)))
            case ("zw_iface")
              call netcdf_check(nf90_put_var(file_id, var_id(var),            &
                                             CVmix_vars%zw_iface(1:nlev+1)))
            case ("ShearRichardson_iface")
              call netcdf_check(nf90_put_var(file_id, var_id(var),            &
                                  CVmix_vars%ShearRichardson_iface(1:nlev+1)))
            case ("BulkRichardson_cntr")
              call netcdf_check(nf90_put_var(file_id, var_id(var),            &
                                      CVmix_vars%BulkRichardson_cntr(1:nlev)))
            case ("Mdiff_iface")
              call netcdf_check(nf90_put_var(file_id, var_id(var),            &
                                            CVmix_vars%Mdiff_iface(1:nlev+1)))
            case ("Tdiff_iface")
              call netcdf_check(nf90_put_var(file_id, var_id(var),            &
                                            CVmix_vars%Tdiff_iface(1:nlev+1)))
            case ("Sdiff_iface")
              call netcdf_check(nf90_put_var(file_id, var_id(var),            &
                                            CVmix_vars%Sdiff_iface(1:nlev+1)))
            case ("strat_param")
              call netcdf_check(nf90_put_var(file_id, var_id(var),            &
                                        CVmix_vars%strat_param_num(1:nlev) /  &
                                        CVmix_vars%strat_param_denom(1:nlev)))
            case ("buoyancy_cntr")
              if (present(buoyancy_cntr)) then
                call netcdf_check(nf90_put_var(file_id, var_id(var),          &
                                               buoyancy_cntr(1:nlev)))
              else
                print*, "ERROR: to write buoyancy at cell center in ",        &
                        "cvmix_io, you need to provide the optional ",        &
                        "buoyancy_cntr argument!"
                stop
              end if
            case ("SqrBuoyancyFreq_iface")
              call netcdf_check(nf90_put_var(file_id, var_id(var),            &
                                  CVmix_vars%SqrBuoyancyFreq_iface(1:nlev+1)))
            case ("Vx_cntr")
              call netcdf_check(nf90_put_var(file_id, var_id(var),            &
                                             CVmix_vars%Vx_cntr(1:nlev)))
            case ("Vy_cntr")
              call netcdf_check(nf90_put_var(file_id, var_id(var),            &
                                             CVmix_vars%Vy_cntr(1:nlev)))
            case DEFAULT
              print*, "ERROR: unable to write variable ", var_names(var)
              stop 1
          end select
        end do
#endif
      case (ASCII_FILE_TYPE)
        do kw=1,nlev+1
          do var=1,size(var_names)
            select case(trim(cvmix_att_name(var_names(var))))
              case ("zt_cntr")
                if (kw.gt.1) then
                  write(file_id,"(E24.17E2)",advance='no')                    &
                        CVmix_vars%zt_cntr(kw-1)
                else
                  write(file_id,"(A)",advance='no') "--- Cell Center Vals ---"
                end if
              case ("zw_iface")
                write(file_id,"(E24.17E2)",advance='no')                      &
                      CVmix_vars%zw_iface(kw)
              case ("ShearRichardson_iface")
                write(file_id,"(E24.17E2)",advance='no')                      &
                      CVmix_vars%ShearRichardson_iface(kw)
              case ("BulkRichardson_cntr")
                if (kw.gt.1) then
                  write(file_id,"(E24.17E2)",advance='no')                    &
                        CVmix_vars%BulkRichardson_cntr(kw-1)
                else
                  write(file_id,"(A)",advance='no') "--- Cell Center Vals ---"
                end if
              case ("Mdiff_iface")
                write(file_id,"(E24.17E2)",advance='no')                      &
                      CVmix_vars%Mdiff_iface(kw)
              case ("Tdiff_iface")
                write(file_id,"(E24.17E2)",advance='no')                      &
                      CVmix_vars%Tdiff_iface(kw)
              case ("Sdiff_iface")
                write(file_id,"(E24.17E2)",advance='no')                      &
                      CVmix_vars%Sdiff_iface(kw)
              case ("strat_param")
                if (kw.lt.nlev+1) then
                  write(file_id,"(E24.17E2)",advance='no')                    &
                        CVmix_vars%strat_param_num(kw) /                      &
                        CVmix_vars%strat_param_denom(kw)
                else
                  write(file_id,"(E24.17E2)",advance='no') 0.0
                end if
              case ("buoyancy_cntr")
                if (present(buoyancy_cntr)) then
                  if (kw.gt.1) then
                    write(file_id,"(E24.17E2)",advance='no')                  &
                          buoyancy_cntr(kw-1)
                  else
                    write(file_id,"(A)",advance='no')                         &
                          "--- Cell Center Vals ---"
                  end if
                else
                  print*, "ERROR: to write buoyancy at cell center in ",      &
                          "cvmix_io, you need to provide the optional ",      &
                          "buoyancy_cntr argument!"
                  stop
                end if
              case ("SqrBuoyancyFreq_iface")
                write(file_id,"(E24.17E2)",advance='no')                      &
                      CVmix_vars%SqrBuoyancyFreq_iface(kw)
              case ("Vx_cntr")
                if (kw.gt.1) then
                  write(file_id,"(E24.17E2)",advance='no')                    &
                        CVmix_vars%Vx_cntr(kw-1)
                else
                  write(file_id,"(A)",advance='no') "--- Cell Center Vals ---"
                end if
              case ("Vy_cntr")
                if (kw.gt.1) then
                  write(file_id,"(E24.17E2)",advance='no')                    &
                        CVmix_vars%Vy_cntr(kw-1)
                else
                  write(file_id,"(A)",advance='no') "--- Cell Center Vals ---"
                end if
              case DEFAULT
                print*, "ERROR: unable to write variable ", var_names(var)
                stop 1
            end select
            if (var.ne.size(var_names)) write(file_id, "(1X)", advance='no')
          end do
          write(file_id, *)
        end do
      case DEFAULT
        print*, "ERROR: Invalid file type"
        stop 1
    end select
!EOC

  end subroutine cvmix_output_write_single_col

!BOP

! !IROUTINE: cvmix_output_write_multi_col
! !INTERFACE:

  subroutine cvmix_output_write_multi_col(file_id, CVmix_vars, var_names)

! !DESCRIPTION:
!  Routine to write the requested variables from multiple columns to a file
!  (file must be opened using vmix\_output\_open to ensure it is written
!  correctly). Called with vmix\_output\_write (see interface in PUBLIC
!  MEMBER FUNCTIONS above).
!\\
!\\

! !USES:
!  Only those used by entire module.

! !INPUT PARAMETERS:
    integer,                             intent(in) :: file_id
    type(cvmix_data_type), dimension(:), intent(in) :: CVmix_vars
    character(len=*),      dimension(:), intent(in) :: var_names

! !LOCAL VARIABLES:
    integer :: nlev, ncol, icol, kw, var
    logical :: z_err
#ifdef _NETCDF
    integer                     :: nt_id, nw_id, ncol_id
    character(len=cvmix_strlen) :: var_name
    integer,             dimension(:),   allocatable :: var_id
    real(kind=cvmix_r8), dimension(:,:), allocatable :: lcl_Mdiff, lcl_Tdiff, &
                                                        lcl_Sdiff
#endif
!EOP
!BOC

    z_err = .false.
    ncol = size(CVmix_vars)
    nlev = CVmix_vars(1)%nlev
    ! Make sure all levels are the same
    do icol=2,ncol
      if (CVmix_vars(icol)%nlev+1.ne.nlev+1) then
        z_err = .true.
      else
        ! Make sure z_iface lines up for Bryan-Lewis case
        if (associated(CVmix_vars(1)%zw_iface)) then
          if (any(CVmix_vars(icol)%zw_iface.ne.CVmix_vars(icol-1)%zw_iface)) then
            z_err = .true.
          end if
        end if
      end if
    end do

    if (z_err) then
      print*, "ERROR: z-coordinates are not the same in every column!"
      stop 1
    end if

    select case (get_file_type(file_id))
#ifdef _NETCDF
      case (NETCDF_FILE_TYPE)
        call netcdf_check(nf90_def_dim(file_id, "nt",   nlev,   nt_id))
        call netcdf_check(nf90_def_dim(file_id, "nw",   nlev+1, nw_id))
        call netcdf_check(nf90_def_dim(file_id, "ncol", ncol,   ncol_id))
        allocate(var_id(size(var_names)))
        do var=1,size(var_names)
          var_name = cvmix_att_name(var_names(var))
          select case(var_name)
            case("zw_iface")
              call netcdf_check(nf90_def_var(file_id, "zw", NF90_DOUBLE,      &
                                             (/nw_id/), var_id(var)))
            case("strat_param")
              call netcdf_check(nf90_def_var(file_id, "Rrho", NF90_DOUBLE,    &
                                             (/nt_id/), var_id(var)))
            case("ShearRichardson_iface")
              call netcdf_check(nf90_def_var(file_id, "ShearRichardson",      &
                                              NF90_DOUBLE, (/nw_id/),         &
                                              var_id(var)))
            case("Mdiff_iface")
              call netcdf_check(nf90_def_var(file_id, "Mdiff", NF90_DOUBLE,   &
                                             (/ncol_id,nw_id/), var_id(var)))
            case("Tdiff_iface")
              call netcdf_check(nf90_def_var(file_id, "Tdiff", NF90_DOUBLE,   &
                                             (/ncol_id,nw_id/), var_id(var)))
            case("Sdiff_iface")
              call netcdf_check(nf90_def_var(file_id, "Sdiff", NF90_DOUBLE,   &
                                             (/ncol_id,nw_id/), var_id(var)))
          end select

          ! Before writing netcdf file, we gather data from all the columns
          ! into a local array
          if (trim(var_name).eq."Mdiff_iface") then
            allocate(lcl_Mdiff(ncol,nlev+1))
            do icol=1,ncol
              lcl_Mdiff(icol,:) = CVmix_vars(icol)%Mdiff_iface(1:nlev+1)
            end do
          endif
          if (trim(var_name).eq."Tdiff_iface") then
            allocate(lcl_Tdiff(ncol,nlev+1))
            do icol=1,ncol
              lcl_Tdiff(icol,:) = CVmix_vars(icol)%Tdiff_iface(1:nlev+1)
            end do
          endif
          if (trim(var_name).eq."Sdiff_iface") then
            allocate(lcl_Sdiff(ncol,nlev+1))
            do icol=1,ncol
              lcl_Sdiff(icol,:) = CVmix_vars(icol)%Sdiff_iface(1:nlev+1)
            end do
          endif

        end do
        call netcdf_check(nf90_enddef(file_id))

        ! Write data to netCDF file
        do var=1,size(var_names)
          select case(trim(cvmix_att_name(var_names(var))))
            case ("zw_iface")
              call netcdf_check(nf90_put_var(file_id, var_id(var),            &
                                CVmix_vars(1)%zw_iface(1:nlev+1)))
            case("strat_param")
              call netcdf_check(nf90_put_var(file_id, var_id(var),            &
                                CVmix_vars(1)%strat_param_num(1:nlev) /       &
                                CVmix_vars(1)%strat_param_denom(1:nlev)))
            case ("ShearRichardson_iface")
              call netcdf_check(nf90_put_var(file_id, var_id(var),            &
                               CVmix_vars(1)%ShearRichardson_iface(1:nlev+1)))
            case("Mdiff_iface")
              call netcdf_check(nf90_put_var(file_id, var_id(var),            &
                                lcl_Mdiff))
              deallocate(lcl_Mdiff)
            case("Tdiff_iface")
              call netcdf_check(nf90_put_var(file_id, var_id(var),            &
                                lcl_Tdiff))
              deallocate(lcl_Tdiff)
            case("Sdiff_iface")
              call netcdf_check(nf90_put_var(file_id, var_id(var),            &
                                lcl_Sdiff))
              deallocate(lcl_Sdiff)
            case DEFAULT
              print*, "ERROR: unable to write variable ", var_names(var)
              stop 1
          end select
        end do
#endif
      case (ASCII_FILE_TYPE)
        do kw=1,nlev+1
          do var=1,size(var_names)
            select case(trim(cvmix_att_name(var_names(var))))
              case ("zw_iface")
                write(file_id,"(E24.17E2)",advance='no') &
                      CVmix_vars(1)%zw_iface(kw)
              case ("strat_param")
                if (kw.ne.nlev+1) then
                  write(file_id,"(E24.17E2)",advance='no')                    &
                        CVmix_vars(1)%strat_param_num(kw) /                   &
                        CVmix_vars(1)%strat_param_denom(kw)
                else
                  write(file_id,"(E24.17E2)",advance='no') 0.0
                end if
              case ("ShearRichardson_iface")
                write(file_id,"(E24.17E2)",advance='no') &
                      CVmix_vars(1)%ShearRichardson_iface(kw)
              case ("Mdiff_iface")
                do icol=1,ncol
                  write(file_id,"(E24.17E2)",advance='no') &
                        CVmix_vars(icol)%Mdiff_iface(kw)
                  if (icol.ne.ncol) write(file_id, "(1X)", advance='no')
                end do
              case ("Tdiff_iface")
                do icol=1,ncol
                  write(file_id,"(E24.17E2)",advance='no') &
                        CVmix_vars(icol)%Tdiff_iface(kw)
                  if (icol.ne.ncol) write(file_id, "(1X)", advance='no')
                end do
              case ("Sdiff_iface")
                do icol=1,ncol
                  write(file_id,"(E24.17E2)",advance='no') &
                        CVmix_vars(icol)%Sdiff_iface(kw)
                  if (icol.ne.ncol) write(file_id, "(1X)", advance='no')
                end do
              case DEFAULT
                print*, "ERROR: unable to write variable ", var_names(var)
                stop 1
            end select
            if (var.ne.size(var_names)) write(file_id, "(1X)", advance='no')
          end do
          write(file_id, *)
        end do
      case DEFAULT
        print*, "ERROR: Invalid file type"
        stop 1
    end select
!EOC

  end subroutine cvmix_output_write_multi_col

!BOP

! !IROUTINE: cvmix_write_2d_double
! !INTERFACE:

  subroutine cvmix_output_write_2d_double(file_id, var_name, dim_names,       &
                                          field, FillVal)

! !DESCRIPTION:
!  Routine to write a 2d field to a netcdf file. Called with cvmix\_output\_
!  write (see interface in PUBLIC MEMBER FUNCTIONS above).
!\\
!\\

! !USES:
!  Only those used by entire module.

! !INPUT PARAMETERS:
    integer,                           intent(in) :: file_id
    character(len=*),                  intent(in) :: var_name
    character(len=*), dimension(2),    intent(in) :: dim_names
    real(cvmix_r8),   dimension(:,:),  intent(in) :: field
    real(cvmix_r8), optional,          intent(in) :: FillVal

! !LOCAL VARIABLES:
    integer, dimension(2) :: dims
    integer               :: i,j
    logical               :: add_fill
#ifdef _NETCDF
    integer, dimension(2) :: dimids
    integer               :: varid
#endif
!EOP
!BOC

    dims = shape(field)
    add_fill = present(FillVal)
    select case(get_file_type(file_id))
#ifdef _NETCDF
      case (NETCDF_FILE_TYPE)
        do i=1,2
          call netcdf_check(nf90_def_dim(file_id, trim(dim_names(i)), dims(i), &
                                         dimids(i)))
        end do
        call netcdf_check(nf90_def_var(file_id, trim(var_name), NF90_DOUBLE,   &
                                       dimids, varid))
        if (add_fill) &
          call netcdf_check(nf90_put_att(file_id, varid, "_FillValue", &
                                         FillVal))
        call netcdf_check(nf90_enddef(file_id))
        call netcdf_check(nf90_put_var(file_id, varid, field))
#endif

      case (ASCII_FILE_TYPE)
        do i=1,dims(1)
          do j=1,dims(2)
            write(file_id, "(E24.17E2)",advance='no') field(i,j)
            if (j.ne.dims(2)) write(file_id, "(1X)", advance='no')
          end do
          write(file_id, *)
        end do
      case DEFAULT
        print*, "ERROR: cvmix_output_write_2d_double only writes to netcdf"
        print*, "(attempt to write ", trim(var_name), " with dimensions ", &
                trim(dim_names(1)), " and ", trim(dim_names(2))
        call cvmix_io_close_all
        stop 1
        ! Dummy code to supress unused variable warnings
        if (add_fill) &
          dims(1) = dims(2)
    end select
!EOC

  end subroutine cvmix_output_write_2d_double

!BOP

! !IROUTINE: cvmix_write_3d_double
! !INTERFACE:

  subroutine cvmix_output_write_3d_double(file_id, var_name, dim_names,       &
                                          field, FillVal)

! !DESCRIPTION:
!  Routine to write a 3d field to a netcdf file. Called with cvmix\_output\_
!  write (see interface in PUBLIC MEMBER FUNCTIONS above).
!\\
!\\

! !USES:
!  Only those used by entire module.

! !INPUT PARAMETERS:
    integer,                             intent(in) :: file_id
    character(len=*),                    intent(in) :: var_name
    character(len=*), dimension(3),      intent(in) :: dim_names
    real(cvmix_r8),   dimension(:,:,:),  intent(in) :: field
    real(cvmix_r8), optional,            intent(in) :: FillVal

! !LOCAL VARIABLES:
    integer, dimension(3) :: dims
    logical               :: add_fill
#ifdef _NETCDF
    integer, dimension(3) :: dimids
    integer               :: varid, i
#endif
!EOP
!BOC

    dims = shape(field)
    add_fill = present(FillVal)
    select case(get_file_type(file_id))
#ifdef _NETCDF
      case (NETCDF_FILE_TYPE)
        do i=1,3
          call netcdf_check(nf90_def_dim(file_id, trim(dim_names(i)), dims(i), &
                                         dimids(i)))
        end do
        call netcdf_check(nf90_def_var(file_id, trim(var_name), NF90_DOUBLE,   &
                                       dimids, varid))
        if (add_fill) &
          call netcdf_check(nf90_put_att(file_id, varid, "_FillValue", &
                                         FillVal))
        call netcdf_check(nf90_enddef(file_id))
        call netcdf_check(nf90_put_var(file_id, varid, field))
#endif

      case DEFAULT
        print*, "ERROR: cvmix_output_write_3d_double only writes to netcdf"
        print*, "(attempt to write ", trim(var_name), " with dimensions ", &
                trim(dim_names(1)), ", ", trim(dim_names(2)), ", and ",    &
                trim(dim_names(3))
        call cvmix_io_close_all
        stop 1
        ! Dummy code to supress unused variable warnings
        if (add_fill) &
          dims(1) = dims(2)
    end select
!EOC

  end subroutine cvmix_output_write_3d_double

!BOP

! !IROUTINE: cvmix_write_att_integer
! !INTERFACE:

  subroutine cvmix_output_write_att_integer(file_id, att_name, att_val,       &
                                            var_name)

! !DESCRIPTION:
!  Routine to write an attribute with an integer value to a netcdf file. If
!  var\_name is omitted, routine writes a global attribute. Called with
!  cvmix\_output\_write\_att (see interface in PUBLIC MEMBER FUNCTIONS above).
!\\
!\\

! !USES:
!  Only those used by entire module.

! !INPUT PARAMETERS:
    integer,          intent(in)           :: file_id
    character(len=*), intent(in)           :: att_name
    integer,          intent(in)           :: att_val
    character(len=*), intent(in), optional :: var_name

! !LOCAL VARIABLES:
#ifdef _NETCDF
    integer :: varid
    logical :: var_found
#endif
!EOP
!BOC

    select case(get_file_type(file_id))
#ifdef _NETCDF
      case (NETCDF_FILE_TYPE)
        var_found = .true.
        if (present(var_name)) then
          varid = get_netcdf_varid(file_id, var_name)
          if (varid.eq.-1) then
            print*, "WARNING: can not find variable ", trim(var_name), " in ", &
                    trim(get_file_name(file_id)), "... can not add attribute."
            var_found = .false.
          end if
        else
          varid=NF90_GLOBAL
        end if
        if (var_found) then
          call netcdf_check(nf90_redef(file_id))
          call netcdf_check(nf90_put_att(file_id, varid, trim(att_name), &
                            att_val))
          call netcdf_check(nf90_enddef(file_id))
        end if
#endif
      case DEFAULT
        print*, "ERROR: cvmix_output_write_att_integer only writes to netcdf"
        print*, "(attempted to set attribute ", trim(att_name), " to ", &
                att_val
        if (present(var_name)) &
          print*, "(for variable ", trim(var_name), ")"
        call cvmix_io_close_all
        stop 1
    end select
!EOC

  end subroutine cvmix_output_write_att_integer

!BOP

! !IROUTINE: cvmix_write_att_real
! !INTERFACE:

  subroutine cvmix_output_write_att_real(file_id, att_name, att_val, var_name)

! !DESCRIPTION:
!  Routine to write an attribute with a real value to a netcdf file. If
!  var\_name is omitted, routine writes a global attribute. Called with
!  cvmix\_output\_write\_att (see interface in PUBLIC MEMBER FUNCTIONS above).
!\\
!\\

! !USES:
!  Only those used by entire module.

! !INPUT PARAMETERS:
    integer,          intent(in)           :: file_id
    character(len=*), intent(in)           :: att_name
    real(cvmix_r8),   intent(in)           :: att_val
    character(len=*), intent(in), optional :: var_name

! !LOCAL VARIABLES:
#ifdef _NETCDF
    integer :: varid
    logical :: var_found
#endif
!EOP
!BOC

    select case(get_file_type(file_id))
#ifdef _NETCDF
      case (NETCDF_FILE_TYPE)
        var_found = .true.
        if (present(var_name)) then
          varid = get_netcdf_varid(file_id, var_name)
          if (varid.eq.-1) then
            print*, "WARNING: can not find variable ", trim(var_name), " in ", &
                    trim(get_file_name(file_id)), "... can not add attribute."
            var_found = .false.
          end if
        else
          varid=NF90_GLOBAL
        end if
        if (var_found) then
          call netcdf_check(nf90_redef(file_id))
          call netcdf_check(nf90_put_att(file_id, varid, trim(att_name), &
                            att_val))
          call netcdf_check(nf90_enddef(file_id))
        end if
#endif
      case DEFAULT
        print*, "ERROR: cvmix_output_write_att_real only writes to netcdf"
        print*, "(attempted to set attribute ", trim(att_name), " to ", &
                att_val
        if (present(var_name)) &
          print*, "(for variable ", trim(var_name), ")"
        call cvmix_io_close_all
        stop 1
    end select
!EOC

  end subroutine cvmix_output_write_att_real

!BOP

! !IROUTINE: cvmix_write_att_string
! !INTERFACE:

  subroutine cvmix_output_write_att_string(file_id, att_name, att_val, var_name)

! !DESCRIPTION:
!  Routine to write an attribute with a string value to a netcdf file. If
!  var\_name is omitted, routine writes a global attribute. Called with
!  cvmix\_output\_write\_att (see interface in PUBLIC MEMBER FUNCTIONS above).
!\\
!\\

! !USES:
!  Only those used by entire module.

! !INPUT PARAMETERS:
    integer,          intent(in)           :: file_id
    character(len=*), intent(in)           :: att_name, att_val
    character(len=*), intent(in), optional :: var_name

! !LOCAL VARIABLES:
#ifdef _NETCDF
    integer :: varid
    logical :: var_found
#endif
!EOP
!BOC

    select case(get_file_type(file_id))
#ifdef _NETCDF
      case (NETCDF_FILE_TYPE)
        var_found = .true.
        if (present(var_name)) then
          varid = get_netcdf_varid(file_id, var_name)
          if (varid.eq.-1) then
            print*, "WARNING: can not find variable ", trim(var_name), " in ", &
                    trim(get_file_name(file_id)), "... can not add attribute."
            var_found = .false.
          end if
        else
          varid=NF90_GLOBAL
        end if
        if (var_found) then
          call netcdf_check(nf90_redef(file_id))
          call netcdf_check(nf90_put_att(file_id, varid, trim(att_name), &
                            trim(adjustl(att_val))))
          call netcdf_check(nf90_enddef(file_id))
        end if
#endif
      case DEFAULT
        print*, "ERROR: cvmix_output_write_att_string only writes to netcdf"
        print*, "(attempted to set attribute ", trim(att_name), " to ", &
                trim(att_val)
        if (present(var_name)) &
          print*, "(for variable ", trim(var_name), ")"
        call cvmix_io_close_all
        stop 1
    end select
!EOC

  end subroutine cvmix_output_write_att_string

!BOP

! !IROUTINE: cvmix_io_close
! !INTERFACE:

  subroutine cvmix_io_close(file_id)

! !DESCRIPTION:
!  Routine to close a file once all writing has been completed. In addition
!  to closing the file, this routine also deletes its entry in file\_database
!  to avoid trying to write to the file in the future.
!\\
!\\

! !USES:
!  Only those used by entire module.

! !INPUT PARAMETERS:
    integer, intent(in) :: file_id

! !LOCAL VARIABLES:
    type(cvmix_file_entry), pointer :: ifile, file_to_close
    logical                         :: file_found
    integer                         :: file_type
!EOP
!BOC

    ! Is fid in the file database?
    nullify(file_to_close)
    if (allocated(file_database)) then
      ifile => file_database(1)
      do while(associated(ifile%next))
        if (ifile%file_id.eq.file_id) then
          file_to_close => ifile
        end if
        ifile => ifile%next
      end do
      if (ifile%file_id.eq.file_id) then
         file_to_close => ifile
      end if
    end if
    file_found = associated(file_to_close)

    if (.not.file_found) then
      write(*,"(A,I0,A)") "Warning: file id ", file_id, " is not an open file!"
      return
    end if
    file_type = file_to_close%file_type

    if (associated(file_to_close%prev)) then
      ifile => file_to_close%prev
      if (associated(file_to_close%next)) then
        ifile%next => file_to_close%next
        ifile%next%prev => ifile
      else
        nullify(ifile%next)
      end if
      deallocate(file_to_close)
    else
      ! file_id is stored in the first entry
      if (associated(file_database(1)%next)) then
        ! Database has more than one entry, so copy last entry into first
        file_to_close => file_database(1)
        do while(associated(file_to_close%next))
          file_to_close => file_to_close%next
        end do
        ifile => file_to_close%prev
        file_database(1)%file_id   = file_to_close%file_id
        file_database(1)%file_type = file_to_close%file_type
        file_database(1)%file_name = file_to_close%file_name
        nullify(ifile%next)
        deallocate(file_to_close)
      else
        ! file_id is only entry in database
        deallocate(file_database)
      end if
    end if

    select case (file_type)
#ifdef _NETCDF
      case (NETCDF_FILE_TYPE)
        call netcdf_check(nf90_close(file_id))
#endif
      case (ASCII_FILE_TYPE)
        close(file_id)
      case (BIN_FILE_TYPE)
        close(file_id)
    end select
!EOC

  end subroutine cvmix_io_close

!BOP

! !IROUTINE: cvmix_io_close_all
! !INTERFACE:

  subroutine cvmix_io_close_all

! !DESCRIPTION:
!  Routine to close all files open (meant to be called prior to an abort)
!\\
!\\

! !USES:
!  Only those used by entire module.

! !LOCAL VARIABLES:
    integer :: fid

!EOP
!BOC

    write(*,"(A)") "Closing all open files..."
    do while (allocated(file_database))
      fid = file_database(1)%file_id
      write(*, "(A,1X,A)") "...", trim(get_file_name(fid))
      call cvmix_io_close(fid)
    end do
    write(*,"(A)") "All files closed."
!EOC
  end subroutine cvmix_io_close_all

!BOP

! !IROUTINE: get_file_name
! !INTERFACE:

  function get_file_name(file_id)

! !DESCRIPTION:
!  Returns the name of the file associated with a given file\_id. If the file
!  is not in the database, returns FILE\_NOT\_FOUND.
!\\
!\\

! !USES:
!  Only those used by entire module.

! !INPUT PARAMETERS:
    integer, intent(in) :: file_id

! !OUTPUT PARAMETERS:
    character(len=cvmix_strlen) :: get_file_name

! !LOCAL VARIABLES:
    type(cvmix_file_entry), pointer :: ifile
!EOP
!BOC

    ifile => file_database(1)
    if (ifile%file_id.eq.file_id) then
      get_file_name = ifile%file_name
      return
    end if
    do while(associated(ifile%next))
      ifile => ifile%next
      if (ifile%file_id.eq.file_id) then
        get_file_name = ifile%file_name
        return
      end if
    end do
    get_file_name = "FILE_NOT_FOUND"
!EOC

  end function get_file_name

!BOP

! !IROUTINE: get_file_type
! !INTERFACE:

  function get_file_type(file_id)

! !DESCRIPTION:
!  Returns the file format (enumerated in DEFINED PARAMETERS section) of a
!  given file. If the file is not in the database, returns FILE\_NOT\_FOUND.
!\\
!\\

! !USES:
!  Only those used by entire module.

! !INPUT PARAMETERS:
    integer, intent(in) :: file_id

! !OUTPUT PARAMETERS:
    integer             :: get_file_type

! !LOCAL VARIABLES:
    type(cvmix_file_entry), pointer :: ifile
!EOP
!BOC

    ifile => file_database(1)
    if (ifile%file_id.eq.file_id) then
      get_file_type = ifile%file_type
      return
    end if
    do while(associated(ifile%next))
      ifile => ifile%next
      if (ifile%file_id.eq.file_id) then
        get_file_type = ifile%file_type
        return
      end if
    end do
    get_file_type = FILE_NOT_FOUND
!EOC

  end function get_file_type

#ifdef _NETCDF
!BOP

! !IROUTINE: cvmix_input_get_netcdf_dim
! !INTERFACE:

  function cvmix_input_get_netcdf_dim(file_id, dim_name)

! !DESCRIPTION:
!  Returns the value of the dimension dim\_name in the netcdf file file\_id. If
!  the dimension does not exist, returns -1.
!\\
!\\

! !USES:
!  Only those used by entire module.

! !INPUT PARAMETERS:
    integer,          intent(in) :: file_id
    character(len=*), intent(in) :: dim_name

! !OUTPUT PARAMETERS:
    integer                        :: cvmix_input_get_netcdf_dim

! !LOCAL VARIABLES:
    character(len=cvmix_strlen) :: tmp_name
    integer                     :: i, ndim, dimid
!EOP
!BOC

    cvmix_input_get_netcdf_dim = -1
    if (get_file_type(file_id).ne.NETCDF_FILE_TYPE) then
      print*, "WARNING: can not find dimid, ", trim(get_file_name(file_id)), &
              " is not a netcdf file."
      return
    end if

    dimid = -1
    ! Find number of variables in file
    call netcdf_check(nf90_inquire(file_id, nDimensions=ndim))
    i = 1
    do while((i.le.ndim).and.(dimid.eq.-1))
      ! Loop to figure out if var_name is a valid variable in the file
      call netcdf_check(nf90_inquire_dimension(file_id, i, name=tmp_name))
      if (trim(dim_name).eq.trim(tmp_name)) then
        dimid = i
      else
        i = i+1
      end if
    end do
    if (dimid.ne.-1) &
      call netcdf_check(nf90_inquire_dimension(file_id, dimid, &
                        len=cvmix_input_get_netcdf_dim))

!EOC

  end function cvmix_input_get_netcdf_dim

!BOP

! !IROUTINE: get_netcdf_varid
! !INTERFACE:

  function get_netcdf_varid(file_id, var_name, xtype, ndims)

! !DESCRIPTION:
!  Returns the varid associated with the variable var\_name in the netcdf file
!  file\_id. If the variable does not exist, returns -1.
!\\
!\\

! !USES:
!  Only those used by entire module.

! !INPUT PARAMETERS:
    integer,          intent(in) :: file_id
    character(len=*), intent(in) :: var_name

! !OUTPUT PARAMETERS:
    integer, optional, intent(out) :: xtype, ndims
    integer                        :: get_netcdf_varid

! !LOCAL VARIABLES:
    character(len=cvmix_strlen) :: tmp_name
    integer                     :: i, nvar
!EOP
!BOC

    get_netcdf_varid = -1
    if (get_file_type(file_id).ne.NETCDF_FILE_TYPE) then
      print*, "WARNING: can not find varid, ", trim(get_file_name(file_id)), &
              " is not a netcdf file."
      return
    end if

    ! Find number of variables in file
    call netcdf_check(nf90_inquire(file_id, nVariables=nvar))
    i = 1
    do while((i.le.nvar).and.(get_netcdf_varid.eq.-1))
      ! Loop to figure out if var_name is a valid variable in the file
      call netcdf_check(nf90_inquire_variable(file_id, i, name=tmp_name, &
                                              xtype=xtype, ndims=ndims))
      if (trim(var_name).eq.trim(tmp_name)) then
        get_netcdf_varid = i
      else
        i = i+1
      end if
    end do

!EOC

  end function get_netcdf_varid

! Routine to handle errors returned from netcdf
  subroutine netcdf_check(status)

    integer, intent(in) :: status

    if (status.ne.nf90_noerr) then
      print*, "netCDF error: ", trim(nf90_strerror(status))
      stop 1
    end if

  end subroutine netcdf_check

#endif

! DEBUGGING ROUTINE
  subroutine print_open_files()

    type(cvmix_file_entry), pointer :: ifile

    if (.not.allocated(file_database)) then
      print*, "No Open files"
    else
      ifile => file_database(1)
      do while (associated(ifile%next))
        print*, "file id: ", ifile%file_id, ifile%file_type, trim(ifile%file_name)
        ifile => ifile%next
      end do
      print*, "file id: ", ifile%file_id, ifile%file_type, trim(ifile%file_name)
    end if
    print*, "----"

  end subroutine print_open_files

end module cvmix_io

