!
! File: nfw.f90
!
! Author: Pavel Sakov, CSIRO Marine Research
!
! Created: 17 March 2005
!
! Purpose: Contains wrappers to netcdf functions, mainly for easier
!          error handling.
! 
! Description:
!
!          Each subroutine in nfw.f90 is a simple wrapper of a similar
!          function in the NetCDF Fortran interface. The rules of use are
!          pretty simple: for a given NetCDF Fortran function, replace
!          prefix "nf_" by "nfw_" and add the NetCDF file name as the
!          first argument.
!
!          Here is the current list of subroutines in nfw_mod:
!
!          nfw_create(fname, mode, ncid)
!          nfw_open(fname, mode, ncid)
!          nfw_enddef(fname, ncid)
!          nfw_close(fname, ncid)
!          nfw_inq_unlimdim(fname, ncid, unlimdimid)
!          nfw_inq_dimid(fname, ncid, name, dimid)
!          nfw_inq_dimlen(fname, ncid, dimid, length)
!          nfw_def_dim(fname, ncid, name, length, dimid)
!          nfw_def_var(fname, ncid, name, type, ndims, dimids, varid)
!          nfw_inq_varid(fname, ncid, name, varid)
!          nfw_inq_varname(fname, ncid, varid, name)
!          nfw_inq_varndims(fname, ncid, varid, ndims)
!          nfw_inq_vardimid(fname, ncid, varid, dimids)
!          nfw_rename_var(fname, ncid, oldname, newname)
!          nfw_put_var_int(fname, ncid, varid, v)
!          nfw_put_var_double(fname, ncid, varid, v)
!          nfw_put_var_real(fname, ncid, varid, v)
!          nfw_get_var_int(fname, ncid, varid, v)
!          nfw_get_var_double(fname, ncid, varid, v)
!          nfw_put_vara_int(fname, ncid, varid, start, length, v)
!          nfw_put_vara_double(fname, ncid, varid, start, length, v)
!          nfw_get_vara_int(fname, ncid, varid, start, length, v)
!          nfw_get_vara_double(fname, ncid, varid, start, length, v)
!          nfw_get_att_int(fname, ncid, varid, attname, v)
!          nfw_get_att_real(fname, ncid, varid, attname, v)
!          nfw_get_att_double(fname, ncid, varid, attname, v)
!          nfw_put_att_text(fname, ncid, varid, attname, length, text)
!          nfw_put_att_int(fname, ncid, varid, attname, type, length, v)
!          nfw_put_att_real(fname, ncid, varid, attname, type, length, v)
!          nfw_put_att_double(fname, ncid, varid, attname, type, length, v)
!
!          Derived procedures:
!
!          nfw_get_var_double_firstrecord(fname, ncid, varid, v)
!          nfw_var_exists(ncid, name)
!          nfw_dim_exists(ncid, name)
! Modifications:
!
! 29/04/2008 PS: added nfw_rename_var(fname, ncid, oldname, newname)
! 21/10/2009 PS: added nfw_var_exists(ncid, name)
! 22/10/2009 PS: added nfw_put_att_double(fname, ncid, varid, attname, type, 
!                                         length, v)
! 06/11/2009 PS: added nfw_dim_exists(ncid, name)
!                nfw_put_att_real(fname, ncid, varid, attname, type, length, v)
!                nfw_get_att_real(fname, ncid, varid, attname, v)

module nfw_mod
  implicit none
  include 'netcdf.inc'

  character(*), private, parameter :: nfw_version = "0.03"
  integer, private, parameter :: logunit = 6
  character(*), private, parameter :: errprefix = "nfw: error: "
  private quit1, quit2, quit3

contains

#if defined(F90_NOFLUSH)
  subroutine flush(dummy)
    integer, intent(in) :: dummy
  end subroutine flush
#endif

  ! Common exit point -- for the sake of debugging
  subroutine quit
    stop
  end subroutine quit

  subroutine quit1(fname, procname, status)
    character*(*), intent(in) :: fname
    character*(*), intent(in) :: procname
    integer, intent(in) :: status
 
    write(logunit, *)
    write(logunit, *) errprefix, '"', trim(fname), '": ', procname, '(): ',&
         nf_strerror(status)
    call flush(logunit)
    call quit
  end subroutine quit1

  subroutine quit2(fname, procname, name, status)
    character*(*), intent(in) :: fname
    character*(*), intent(in) :: procname
    character*(*), intent(in) :: name
    integer, intent(in) :: status

    write(logunit, *)
    write(logunit, *) errprefix, '"', trim(fname), '": ', procname, '(): "',&
         trim(name), '": ', nf_strerror(status)
    call flush(logunit)
    call quit
  end subroutine quit2

  subroutine quit3(fname, procname, name1, name2, status)
    character*(*), intent(in) :: fname
    character*(*), intent(in) :: procname
    character*(*), intent(in) :: name1
    character*(*), intent(in) :: name2
    integer, intent(in) :: status

    write(logunit, *)
    write(logunit, *) errprefix, '"', trim(fname), '": ', procname, '(): "',&
         trim(name1), '": "', trim(name2), '": ', nf_strerror(status)
    call flush(logunit)
    call quit
  end subroutine quit3

  subroutine nfw_create(fname, mode, ncid)
    character*(*), intent(in) :: fname
    integer, intent(in) :: mode
    integer, intent(out) :: ncid

    integer :: status

    status = nf_create(trim(fname), mode, ncid)
    if (status /= 0) call quit1(fname, 'nf_create', status)
  end subroutine nfw_create

  subroutine nfw_open(fname, mode, ncid)
    character*(*), intent(in) :: fname
    integer, intent(in) :: mode
    integer, intent(out) :: ncid

    integer :: status

    status = nf_open(trim(fname), mode, ncid)
    if (status /= 0) call quit1(fname, 'nf_open', status)
  end subroutine nfw_open

  subroutine nfw_enddef(fname, ncid)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid

    integer :: status

    status = nf_enddef(ncid)
    if (status /= 0) call quit1(fname, 'nf_enddef', status)
  end subroutine nfw_enddef

  subroutine nfw_redef(fname, ncid)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid

    integer :: status

    status = nf_redef(ncid)
    if (status /= 0) call quit1(fname, 'nf_redef', status)
  end subroutine nfw_redef

  subroutine nfw_close(fname, ncid)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid

    integer :: status

    status = nf_close(ncid)
    if (status /= 0) call quit1(fname, 'nf_close', status)
  end subroutine nfw_close

  subroutine nfw_inq_unlimdim(fname, ncid, unlimdimid)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    integer, intent(out) :: unlimdimid

    integer :: status
    
    status = nf_inq_unlimdim(ncid, unlimdimid)
    if (status /= 0) call quit1(fname, 'nf_inq_unlimdimid', status)
  end subroutine nfw_inq_unlimdim

  subroutine nfw_inq_dimid(fname, ncid, name, dimid)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    character*(*), intent(in) :: name
    integer, intent(out) :: dimid

    integer :: status
    
    status = nf_inq_dimid(ncid, trim(name), dimid)
    if (status /= 0) call quit2(fname, 'nf_inq_dimid', name, status)
  end subroutine nfw_inq_dimid

  subroutine nfw_inq_dimlen(fname, ncid, dimid, length)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    integer, intent(in) :: dimid
    integer, intent(out) :: length

    integer :: status

    status = nf_inq_dimlen(ncid, dimid, length)
    if (status /= 0) call quit1(fname, 'nf_inq_dimlen', status)
  end subroutine nfw_inq_dimlen

  subroutine nfw_def_dim(fname, ncid, name, length, dimid)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    character*(*), intent(in) :: name
    integer, intent(in) :: length
    integer, intent(out) :: dimid

    integer :: status

    status = nf_def_dim(ncid, name, length, dimid)
    if (status /= 0) call quit2(fname, 'nf_def_dim', name, status)
  end subroutine nfw_def_dim

  subroutine nfw_def_var(fname, ncid, name, type, ndims, dimids, varid)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    character*(*), intent(in) :: name
    integer, intent(in) :: type
    integer, intent(in) :: ndims
    integer, intent(in) :: dimids(*)
    integer, intent(out) :: varid

    integer :: status

    status = nf_def_var(ncid, name, type, ndims, dimids, varid)
    if (status /= 0) call quit2(fname, 'nf_def_var', name, status)
  end subroutine nfw_def_var

  subroutine nfw_inq_varid(fname, ncid, name, varid)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    character*(*), intent(in) :: name
    integer, intent(out) :: varid

    integer :: status
   
    status = nf_inq_varid(ncid, trim(name), varid)
    if (status /= 0) call quit2(fname, 'nf_inq_varid', name, status)
  end subroutine nfw_inq_varid

  subroutine nfw_inq_varname(fname, ncid, varid, name)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    integer, intent(in) :: varid
    character*(*), intent(out) :: name

    integer :: status

    status = nf_inq_varname(ncid, varid, name)
    if (status /= 0) call quit1(fname, 'nf_inq_varname', status)
  end subroutine nfw_inq_varname

  subroutine nfw_inq_varndims(fname, ncid, varid, ndims)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    integer, intent(in) :: varid
    integer, intent(out) :: ndims

    character*(NF_MAX_NAME) :: name
    integer :: status

    status = nf_inq_varndims(ncid, varid, ndims)
    if (status /= 0) then
       call nfw_inq_varname(fname, ncid, varid, name)
       call quit2(fname, 'nf_inq_varndims', name, status)
    end if
  end subroutine nfw_inq_varndims

  subroutine nfw_inq_vardimid(fname, ncid, varid, dimids)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    integer, intent(in) :: varid
    integer, intent(out) :: dimids(*)

    character*(NF_MAX_NAME) :: name
    integer :: status

    status = nf_inq_vardimid(ncid, varid, dimids)
    if (status /= 0) then
       call nfw_inq_varname(fname, ncid, varid, name)
       call quit2(fname, 'nf_inq_vardimid', name, status)
    end if
  end subroutine nfw_inq_vardimid

  subroutine nfw_rename_var(fname, ncid, oldname, newname)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    character*(*), intent(in) :: oldname
    character*(*), intent(in) :: newname

    integer :: varid
    integer :: status

    call nfw_inq_varid(fname, ncid, oldname, varid)
    status = nf_rename_var(ncid, varid, newname)
    if (status /= 0) then
       call quit2(fname, 'nf_rename_var', oldname, status)
    end if
  end subroutine nfw_rename_var

  subroutine nfw_put_var_int(fname, ncid, varid, v)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    integer, intent(in) :: varid
    integer, intent(in) :: v(*)

    character*(NF_MAX_NAME) :: name
    integer :: status

    status = nf_put_var_int(ncid, varid, v)
    if (status /= 0) then
       call nfw_inq_varname(fname, ncid, varid, name)
       call quit2(fname, 'nf_put_var_double', name, status)
    end if
  end subroutine nfw_put_var_int

  subroutine nfw_put_var_double(fname, ncid, varid, v)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    integer, intent(in) :: varid
    real(8), intent(in) :: v(*)

    character*(NF_MAX_NAME) :: name
    integer :: status

    status = nf_put_var_double(ncid, varid, v)
    if (status /= 0) then
       call nfw_inq_varname(fname, ncid, varid, name)
       call quit2(fname, 'nf_put_var_double', name, status)
    end if
  end subroutine nfw_put_var_double

  subroutine nfw_put_var_real(fname, ncid, varid, v)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    integer, intent(in) :: varid
    real(4), intent(in) :: v(*)

    character*(NF_MAX_NAME) :: name
    integer :: status

    status = nf_put_var_real(ncid, varid, v)
    if (status /= 0) then
       call nfw_inq_varname(fname, ncid, varid, name)
       call quit2(fname, 'nf_put_var_real', name, status)
    end if
  end subroutine nfw_put_var_real

  subroutine nfw_get_var_int(fname, ncid, varid, v)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    integer, intent(in) :: varid
    integer, intent(out) :: v(*)

    character*(NF_MAX_NAME) :: name
    integer :: status

    status = nf_get_var_int(ncid, varid, v)
    if (status /= 0) then
       call nfw_inq_varname(fname, ncid, varid, name)
       call quit2(fname, 'nf_get_var_int', name, status)
    end if
  end subroutine nfw_get_var_int

  subroutine nfw_get_var_double(fname, ncid, varid, v)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    integer, intent(in) :: varid
    real(8), intent(out) :: v(*)

    character*(NF_MAX_NAME) :: name
    integer :: status

    status = nf_get_var_double(ncid, varid, v)
    if (status /= 0) then
       call nfw_inq_varname(fname, ncid, varid, name)
       call quit2(fname, 'nf_get_var_double', name, status)
    end if
  end subroutine nfw_get_var_double

  subroutine nfw_get_var_text(fname, ncid, varid, v)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    integer, intent(in) :: varid
    character, intent(out) :: v(*)

    character*(NF_MAX_NAME) :: name
    integer :: status

    status = nf_get_var_text(ncid, varid, v)
    if (status /= 0) then
       call nfw_inq_varname(fname, ncid, varid, name)
       call quit2(fname, 'nf_get_var_int', name, status)
    end if
  end subroutine nfw_get_var_text

  subroutine nfw_put_vara_int(fname, ncid, varid, start, length, v)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    integer, intent(in) :: varid
    integer, intent(in) :: start(*)
    integer, intent(in) :: length(*)
    integer, intent(in) :: v(*)

    character*(NF_MAX_NAME) :: name
    integer :: status

    status = nf_put_vara_int(ncid, varid, start, length, v)
    if (status /= 0) then
       call nfw_inq_varname(fname, ncid, varid, name)
       call quit2(fname, 'nf_put_vara_int', name, status)
    end if
  end subroutine nfw_put_vara_int

  subroutine nfw_put_vara_double(fname, ncid, varid, start, length, v)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    integer, intent(in) :: varid
    integer, intent(in) :: start(*)
    integer, intent(in) :: length(*)
    real(8), intent(in) :: v(*)

    character*(NF_MAX_NAME) :: name
    integer :: status

    status = nf_put_vara_double(ncid, varid, start, length, v)
    if (status /= 0) then
       call nfw_inq_varname(fname, ncid, varid, name)
       call quit2(fname, 'nf_put_vara_double', name, status)
    end if
  end subroutine nfw_put_vara_double

  subroutine nfw_get_vara_int(fname, ncid, varid, start, length, v)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    integer, intent(in) :: varid
    integer, intent(in) :: start(*)
    integer, intent(in) :: length(*)
    integer, intent(out) :: v(*)

    character*(NF_MAX_NAME) :: name
    integer :: status

    status = nf_get_vara_int(ncid, varid, start, length, v)
    if (status /= 0) then
       call nfw_inq_varname(fname, ncid, varid, name)
       call quit2(fname, 'nf_get_vara_int', name, status)
    end if
  end subroutine nfw_get_vara_int

  subroutine nfw_get_vara_double(fname, ncid, varid, start, length, v)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    integer, intent(in) :: varid
    integer, intent(in) :: start(*)
    integer, intent(in) :: length(*)
    real(8), intent(out) :: v(*)

    character*(NF_MAX_NAME) :: name
    integer :: status

    status = nf_get_vara_double(ncid, varid, start, length, v)
    if (status /= 0) then
       call nfw_inq_varname(fname, ncid, varid, name)
       call quit2(fname, 'nf_get_vara_double', name, status)
    end if
  end subroutine nfw_get_vara_double

  subroutine nfw_get_att_int(fname, ncid, varid, attname, v)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    integer, intent(in) :: varid
    character*(*), intent(in) :: attname
    integer, intent(out) :: v(*)

    character*(NF_MAX_NAME) :: varname
    integer :: status

    status = nf_get_att_int(ncid, varid, attname, v)
    if (status /= 0) then
       if (varid /= nf_global) then
          call nfw_inq_varname(fname, ncid, varid, varname)
       else
          varname = 'NF_GLOBAL'
       end if
       call quit3(fname, 'nf_get_att_int', varname, attname, status)
    end if
  end subroutine nfw_get_att_int

  subroutine nfw_get_att_real(fname, ncid, varid, attname, v)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    integer, intent(in) :: varid
    character*(*), intent(in) :: attname
    real(4), intent(out) :: v(*)

    character*(NF_MAX_NAME) :: varname
    integer :: status

    status = nf_get_att_real(ncid, varid, attname, v)
    if (status /= 0) then
       if (varid /= nf_global) then
          call nfw_inq_varname(fname, ncid, varid, varname)
       else
          varname = 'NF_GLOBAL'
       end if
       call quit3(fname, 'nf_get_att_real', varname, attname, status)
    end if
  end subroutine nfw_get_att_real

  subroutine nfw_get_att_double(fname, ncid, varid, attname, v)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    integer, intent(in) :: varid
    character*(*), intent(in) :: attname
    real(8), intent(out) :: v(*)

    character*(NF_MAX_NAME) :: varname
    integer :: status

    status = nf_get_att_double(ncid, varid, attname, v)
    if (status /= 0) then
       if (varid /= nf_global) then
          call nfw_inq_varname(fname, ncid, varid, varname)
       else
          varname = 'NF_GLOBAL'
       end if
       call quit3(fname, 'nf_get_att_double', varname, attname, status)
    end if
  end subroutine nfw_get_att_double

  subroutine nfw_put_att_text(fname, ncid, varid, attname, length, text)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    integer, intent(in) :: varid
    character*(*), intent(in) :: attname
    integer, intent(in) :: length
    character*(*), intent(in) :: text

    integer :: status
    character*(NF_MAX_NAME) :: varname

    status = nf_put_att_text(ncid, varid, attname, length, trim(text))
    if (status /= 0) then
       if (varid /= nf_global) then
          call nfw_inq_varname(fname, ncid, varid, varname)
       else
          varname = 'NF_GLOBAL'
       end if
       call quit3(fname, 'nf_put_att_text', varname, attname, status)
    end if
  end subroutine nfw_put_att_text

  subroutine nfw_put_att_int(fname, ncid, varid, attname, type, length, v)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    integer, intent(in) :: varid
    character*(*), intent(in) :: attname
    integer, intent(in) :: type
    integer, intent(in) :: length
    integer, intent(in) :: v(*)

    integer :: status
    character*(NF_MAX_NAME) :: varname

    status = nf_put_att_int(ncid, varid, attname, type, length, v)
    if (status /= 0) then
       if (varid /= nf_global) then
          call nfw_inq_varname(fname, ncid, varid, varname)
       else
          varname = 'NF_GLOBAL'
       end if
       call quit3(fname, 'nf_put_att_int', varname, attname, status)
    end if
  end subroutine nfw_put_att_int

  subroutine nfw_put_att_real(fname, ncid, varid, attname, type, length, v)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    integer, intent(in) :: varid
    character*(*), intent(in) :: attname
    integer, intent(in) :: type
    integer, intent(in) :: length
    real(4), intent(in) :: v(*)

    integer :: status
    character*(NF_MAX_NAME) :: varname

    status = nf_put_att_real(ncid, varid, attname, type, length, v)
    if (status /= 0) then
       if (varid /= nf_global) then
          call nfw_inq_varname(fname, ncid, varid, varname)
       else
          varname = 'NF_GLOBAL'
       end if
       call quit3(fname, 'nf_put_att_real', varname, attname, status)
    end if
  end subroutine nfw_put_att_real

  subroutine nfw_put_att_double(fname, ncid, varid, attname, type, length, v)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    integer, intent(in) :: varid
    character*(*), intent(in) :: attname
    integer, intent(in) :: type
    integer, intent(in) :: length
    real(8), intent(in) :: v(*)

    integer :: status
    character*(NF_MAX_NAME) :: varname

    status = nf_put_att_double(ncid, varid, attname, type, length, v)
    if (status /= 0) then
       if (varid /= nf_global) then
          call nfw_inq_varname(fname, ncid, varid, varname)
       else
          varname = 'NF_GLOBAL'
       end if
       call quit3(fname, 'nf_put_att_double', varname, attname, status)
    end if
  end subroutine nfw_put_att_double

! Derived subroutines

  ! Reads the first record only
  subroutine nfw_get_var_double_firstrecord(fname, ncid, varid, v)
    character*(*), intent(in) :: fname
    integer, intent(in) :: ncid
    integer, intent(in) :: varid
    real(8), intent(out) :: v(*)

    integer :: ndims
    integer :: unlimdimid
    integer :: dimids(NF_MAX_VAR_DIMS)
    integer :: dimlen(NF_MAX_VAR_DIMS)
    integer :: dstart(NF_MAX_VAR_DIMS)
    integer :: i
    character*(NF_MAX_NAME) :: name
    integer :: status

    call nfw_inq_varndims(fname, ncid, varid, ndims)
    call nfw_inq_vardimid(fname, ncid, varid, dimids)
    call nfw_inq_unlimdim(fname, ncid, unlimdimid)
    
    do i = 1, ndims
       call nfw_inq_dimlen(fname, ncid, dimids(i), dimlen(i))
       dstart(i) = 1
    end do

    ! check size of v
    if (dimids(ndims) == unlimdimid) then
       dimlen(ndims) = 1 ! 1 record only
    end if

    status = nf_get_vara_double(ncid, varid, dstart, dimlen, v)
    if (status /= 0) then
       call nfw_inq_varname(fname, ncid, varid, name)
       call quit2(fname, 'nf_get_vara_double', name, status)
    end if
  end subroutine nfw_get_var_double_firstrecord

  logical function nfw_var_exists(ncid, name)
    integer, intent(in) :: ncid
    character*(*), intent(in) :: name

    integer :: varid
    integer :: status

    status = nf_inq_varid(ncid, trim(name), varid)
    nfw_var_exists = (status == 0)
  end function nfw_var_exists

  logical function nfw_dim_exists(ncid, name)
    integer, intent(in) :: ncid
    character*(*), intent(in) :: name

    integer :: dimid
    integer :: status

    status = nf_inq_dimid(ncid, trim(name), dimid)
    nfw_dim_exists = (status == 0)
  end function nfw_dim_exists

end module nfw_mod
