!! io_utils.f90
!!
!======================================================================
!! All the various bits and pieces which could be useful regarding i/o,
!! such as opening the right files, getting numerical values for
!! command line arguments, etc...
!======================================================================


!! some of the constants used in the io_utils module
module io_utils_consts
  integer, parameter :: max_arg_len  = 180
  integer, parameter :: max_line_len = 600
end module io_utils_consts

! for backwards compatibility
module sub_defs_io_consts
   use io_utils_consts
end module sub_defs_io_consts

!----------------------------------------------------------------------
! Interfaces to the dec f90 iargc and getarg routines
!----------------------------------------------------------------------
integer function lcl_iargc()
   implicit none
   lcl_iargc = COMMAND_ARGUMENT_COUNT()
end function lcl_iargc

subroutine lcl_getarg(k, argk)
   implicit none
   integer,      intent(in)  :: k
   character(*), intent(out) :: argk

   call GET_COMMAND_ARGUMENT(k, argk)
end subroutine lcl_getarg


subroutine lcl_flush(idev)
   implicit none
   integer, intent(in) :: idev

   flush(idev)
end subroutine lcl_flush

subroutine lcl_system(string)
   implicit none
   character(*), intent(in) :: string
   !------------------------------------------------------------
   !integer return_val, system

   call  EXECUTE_COMMAND_LINE(string)
end subroutine lcl_system



!----------------------------------------------------------------------
!! All the interfaces needed here and in the lcl_???.f90 routines
!----------------------------------------------------------------------
module io_utils
  use io_utils_consts


  interface
     subroutine open_arg(iarg, idev, ext, status, form, default_val)
       implicit none
       integer, intent(in) :: iarg, idev
       character(*), optional, intent(in) :: ext, status, form, default_val
     end subroutine open_arg
  end interface

  interface
     function idev_open_opt(opt, default_val, ext, status, form) result(idev)
       implicit none
       character(*), intent(in) :: opt
       character(*), optional, intent(in) :: default_val, ext, status, form
       integer  :: idev
     end function idev_open_opt
  end interface
  
  interface
     function idev_open_arg(iarg, ext, status, form) result(idev)
       implicit none
       integer             :: idev
       integer, intent(in) :: iarg
       character(*), optional, intent(in) :: ext, status, form
     end function idev_open_arg
  end interface

  interface
     function iargc_opt(opt)
       implicit none
       integer                      :: iargc_opt
       character(len=*), intent(in) :: opt
     end function iargc_opt
  end interface

  interface
     function command_line()
       use io_utils_consts
       implicit none
       character(len=max_line_len) ::  command_line
     end function command_line
  end interface

  interface
     function string_val_opt(opt,default_val)
       use io_utils_consts
       implicit none
       character(len=max_arg_len)             :: string_val_opt
       character(len=*), intent(in)           :: opt
       character(len=*), intent(in), optional :: default_val
     end function string_val_opt
  end interface
  
  interface
     function int_val_opt(opt,default_val)
       implicit none
       integer                      :: int_val_opt
       character(len=*), intent(in) :: opt
       integer, intent(in), optional :: default_val
     end function int_val_opt
  end interface

  interface
     function log_val_opt(opt,default_val)
       implicit none
       logical                       :: log_val_opt
       character(len=*), intent(in)  :: opt
       logical, intent(in), optional :: default_val
     end function log_val_opt
  end interface

  interface
     function dble_val_opt(opt,default_val)
       implicit none
       real(kind(1d0))                      :: dble_val_opt
       character(len=*), intent(in) :: opt
       real(kind(1d0)), intent(in), optional :: default_val
     end function dble_val_opt
  end interface

  interface
     integer function value(string)
        implicit none
        character(*),  intent(in)  ::   string
     end function value
  end interface

  interface
     integer function int_val_arg(argk, default_val)
       implicit none
       integer, intent(in)           :: argk
       integer, intent(in), optional :: default_val
     end function int_val_arg
  end interface

  interface
     real(kind(1d0)) function dble_val_arg(argk, default_val)
       implicit none
       integer,         intent(in)           :: argk
       real(kind(1d0)), intent(in), optional :: default_val
     end function dble_val_arg
  end interface

  interface
     function string_val_arg(argk, default_val)
       use io_utils_consts
       implicit none
       character(len=max_arg_len)             :: string_val_arg
       integer,          intent(in)           :: argk
       character(len=*), intent(in), optional :: default_val
     end function string_val_arg
  end interface

  interface
     real(kind(1d0)) function dble_value(string)
       implicit none
       character(*),  intent(in)  ::   string
     end function dble_value
  end interface


  interface num2char
     function int2char(num,frmt)
       implicit none
       character(len=30) :: int2char
       integer, intent(in) :: num
       character(*), intent(in), optional :: frmt
     end function int2char
     function sp2char(num,frmt)
       implicit none
       character(len=30) :: sp2char
       real(kind(1.0)), intent(in) :: num
       character(*), intent(in), optional :: frmt
     end function sp2char
     function dp2char(num,frmt)
       implicit none
       character(len=30) :: dp2char
       real(kind(1d0)), intent(in) :: num
       character(*), intent(in), optional :: frmt
     end function dp2char
  end interface
  

  interface
     function get_new_device() result(dev)
       implicit none
       integer :: dev
     end function get_new_device
  end interface
  
  interface
     subroutine time_stamp(idev, string)
       implicit none
       integer,          intent(in),  optional :: idev
       character(len=*), intent(out), optional :: string
     end subroutine time_stamp
  end interface
  
  interface
     subroutine error_report(subname,line1,line2,line3,line4,action)
       implicit none
       character(*), intent(in) :: subname
       character(*), intent(in), optional :: line1, line2, line3, line4
       character(*), intent(in), optional :: action
     end subroutine error_report
  end interface
       
       
   !==================================================================
   ! ====
   ! Interfaces to the standard routines which provide the links to
   ! platform dependent calls
   !======================================================================

  interface
     subroutine lcl_write_2d(idev, array)
       implicit none
       integer,         intent(in) :: idev
       real(kind(1d0)), intent(in) :: array(1:, 1:)
     end subroutine lcl_write_2d
  end interface

  interface
     subroutine lcl_read_2d(idev, array, ifail)
       implicit none
       integer,           intent(in)  :: idev
       real(kind(1d0)),   intent(out) :: array(1:, 1:)
       integer, optional, intent(out) :: ifail
     end subroutine lcl_read_2d
  end interface

  interface
     integer function lcl_iargc()
     end function lcl_iargc
  end interface

  interface
     subroutine lcl_getarg(k, argk)
       implicit none
       integer,      intent(in)  :: k
       character(*), intent(out) :: argk
     end subroutine lcl_getarg
  end interface

  interface
     subroutine lcl_flush(idev)
       implicit none
       integer, intent(in) :: idev
     end subroutine lcl_flush
  end interface

  
  interface  
     function CheckAllArgsUsed(ErrDev) result(check)
       logical :: check
       integer, optional :: ErrDev
     end function CheckAllArgsUsed
  end interface
  interface  
     function CheckAllOptsUsed(ErrDev) result(check)
       logical :: check
       integer, optional :: ErrDev
     end function CheckAllOptsUsed
  end interface
  
contains
   
  !! sets hostname to the name of the machine (up to a max of 255 or the length
  !! of the string, whichever is smaller)
  !!
  !! Based in part on code at https://rosettacode.org/wiki/Hostname#Fortran
  subroutine get_hostname(hostname)
    use, intrinsic  :: iso_c_binding
    implicit none
    character(len=*), intent(out) :: hostname
    !------------
    interface !to function: int gethostname(char *name, size_t namelen);
    integer(c_int) function gethostname(name, namelen) bind(c)
       use, intrinsic  :: iso_c_binding, only: c_char, c_int, c_size_t
       integer(c_size_t), value, intent(in) :: namelen
       character(len=1,kind=c_char), dimension(namelen),  intent(inout) ::  name
    end function gethostname
    end interface    
    integer(c_int) :: status
    integer,parameter :: HOST_NAME_MAX=255
    character(kind=c_char,len=1),dimension(HOST_NAME_MAX) :: cstr_hostname
    integer(c_size_t) :: lenstr
    integer :: i

    lenstr = HOST_NAME_MAX
    status = gethostname(cstr_hostname, lenstr)
    
    if (status /= 0) then
       write(0,*) 'Error in get_hostname: gethostname returned ',status
       hostname = "hostname_error"
       return
    end if

    do i = 1, len(hostname)
       hostname(i:i) = " "
    end do
    do i = 1, HOST_NAME_MAX
       if (i > lenstr .or. cstr_hostname(i) == c_null_char) exit
       hostname(i:i) = cstr_hostname(i)
    end do

  end subroutine get_hostname
end module io_utils


!--------------------------------------------------------------
!! When an argument is used register the fact here. Then at the
!! end of program initialisation one can check to see whether all
!! arguments have been used
module track_args
  ! putting the "only" here ensures that this compiles under intel 7.1
  ! (not immediately clear why, though it could be related to fact
  ! that interface CheckAllArgsUsed is being used, but that the 
  ! corresponding subroutine uses track_args?)
  use io_utils, only: lcl_iargc, lcl_getarg
  use io_utils_consts
  implicit none
  private
  logical, allocatable, save :: argsused(:)
  logical, save :: started = .false.
  integer, save :: narg
  character(len=*), parameter :: sep = &
       &'=============================================================='

  public :: ta_RegisterUsed, ta_CheckAllArgsUsed, ta_CheckAllOptsUsed

contains

  !----------------------------------------------------------------------
  ! registers the fact that argument number iarg has been used
  subroutine ta_RegisterUsed(iarg)
    integer, intent(in)  :: iarg
    
    call ta_TrackingOn() 

    if (iarg > narg .or. iarg < 1) then
       write(0,*) 'In ta_RegisterUsed tried to set illegal arg', iarg
       stop
    end if
    argsused(iarg) = .true.
  end subroutine ta_RegisterUsed

  !----------------------------------------------------------------------
  ! makes sure that tracking of argument usage is turned on
  subroutine ta_TrackingOn()
    if (.not. started) then
       narg = lcl_iargc()
       allocate(argsused(narg))
       argsused = .false.
       started = .true.
    end if
  end subroutine ta_TrackingOn
  
  
  !--------------------------------------------------------
  !! ErrDev = device to which one should send the list of 
  !! unused args
  function ta_CheckAllOptsUsed(ErrDev) result(check)
    logical :: check
    integer, optional :: ErrDev
    integer :: i
    character(len=max_arg_len) :: argi
    logical :: writeit

    call ta_TrackingOn() 

    check = .true.
    writeit = present(ErrDev)
    do i = 1, narg
       if (argsused(i)) cycle
       call lcl_getarg(i, argi)
       if (argi(1:1) == "-") then
          if (check .and. writeit) then
             write(ErrDev,*) sep
             write(ErrDev,*) &
                  &'WARNING / ERROR: the following options were not recognized'
          end if
          check = .false.
          if(writeit) write(ErrDev,*) trim(argi)
       end if
    end do
    if ((.not. check) .and. writeit ) write(ErrDev,*) sep
  end function ta_CheckAllOptsUsed


  !--------------------------------------------------------
  !! ErrDev = device to which one should send the list of 
  !! unused args
  function ta_CheckAllArgsUsed(ErrDev) result(check)
    logical :: check
    integer, optional :: ErrDev
    integer :: i
    character(len=max_arg_len) :: argi

    call ta_TrackingOn() 

    if (narg > 0 ) then 
       check = all(argsused)
    else
       check = .true. 
       return
    end if
    if ((.not. check) .and. present(ErrDev)) then
       write(ErrDev,*) sep
       write(ErrDev,*) &
            &'WARNING / ERROR: the following args were not recognized'
       do i = 1, narg 
          if (.not. argsused(i)) then
             call lcl_getarg(i, argi)
             write(ErrDev,*) trim(argi)
          end if
       end do
       write(ErrDev,*) sep
    end if
  end function ta_CheckAllArgsUsed
  
  
end module track_args




!--------------------------------------------------------
!! ErrDev = device to which one should send the list of 
!! unused args
function CheckAllArgsUsed(ErrDev) result(check)
  use track_args
  logical :: check
  integer, optional :: ErrDev
  check = ta_CheckAllArgsUsed(ErrDev)
end function CheckAllArgsUsed
function CheckAllOptsUsed(ErrDev) result(check)
  use track_args
  logical :: check
  integer, optional :: ErrDev
  check = ta_CheckAllOptsUsed(ErrDev)
end function CheckAllOptsUsed


!----------------------------------------------------------------------
!! As open_arg -- except that it automatically allocates and returns a
!! device number
!----------------------------------------------------------------------
function idev_open_arg(iarg, ext, status, form) result(idev)
  use io_utils, except => idev_open_arg
  implicit none
  integer             :: idev
  integer, intent(in) :: iarg
  character(*), optional, intent(in) :: ext, status, form
  !--------------------------------------------------------------------
  idev = get_new_device()
  call open_arg(iarg, idev, ext, status, form)
end function idev_open_arg


!---------------------------------------------------------------------
!! Open file indicated by command line option and return the 
!! device number.
!!
!! GPS 05/01/01
!---------------------------------------------------------------------
function idev_open_opt(opt, default_val, ext, status, form) result(idev)
  use io_utils, except => idev_open_opt
  implicit none
  character(*), intent(in) :: opt
  character(*), optional, intent(in) :: default_val, ext, status, form
  integer  :: idev, iarg
  !--------------------------------------------------------------------
  idev = get_new_device()
  iarg = iargc_opt(opt) + 1 ! will return -100 if opt is not present
  if (iarg < 0) then
     write(0,*) 'ERROR in idev_open_opt:'
     write(0,*) 'command-line option "'//opt//'" missing'
     stop
  end if
  call open_arg(iarg, idev, ext, status, form, default_val)
end function idev_open_opt


!----------------------------------------------------------------------
!! Open file corresponding the name in command line argument 'iarg'
!! 
!! GPS 20/03/97
!----------------------------------------------------------------------
subroutine open_arg(iarg, idev, ext, status, form, default_val)
  use io_utils, only : lcl_getarg, lcl_iargc, max_arg_len, time_stamp
  use track_args
  implicit none
  integer, intent(in) :: iarg, idev
  character(*), optional, intent(in) :: ext, status, form, default_val
  !----------------------------------------------------------------------
  character(len=max_arg_len) :: arg
  integer       :: l, le
  
  l = lcl_iargc()
  if (iarg > l .or. iarg < 0) then
     if (present(default_val)) then
        arg = trim(default_val)
     else
        write(0,*) 'Number of args is',l
        write(0,*) 'Could not open file corresponding to arg',iarg
        write(0,*) 'because that arg does not exist'
        call time_stamp(0)
        write(0,*) 'stop'
        stop
     end if
  else
     call lcl_getarg(iarg, arg)
     call ta_RegisterUsed(iarg)
  end if
  
  l = index(arg,' ') - 1
  if (present(ext)) then
     le = index(ext,' ') - 1
     if (le <= 0) le = len(ext)
     arg(l+1:l+le) = ext(1:le)
     l = l + le
  end if
  !-- the following stupid sequence should not be necessary, but the
  ! standard doesn't specify open as having `optional args'
  if (present(status)) then
     if (present(form)) then
        open(unit=idev,file=arg(1:l),status=status,form=form)
     else
        open(unit=idev,file=arg(1:l),status=status)
     end if
  else
     if (present(form)) then
        open(unit=idev,file=arg(1:l),form=form)
     else
        open(unit=idev,file=arg(1:l))
     end if
  end if

end subroutine open_arg


!----------------------------------------------------------------------
!! It can often be useful to have a record of the command line -- this 
!! returns a string which conatins the command name and all the arguments
!----------------------------------------------------------------------
function command_line()
  use io_utils, except => command_line
  implicit none
  character(len=max_line_len) ::  command_line
  !----------------------------------------------------------------------
  character(len=max_arg_len) :: string
  integer :: i, n

  n = lcl_iargc()
  call lcl_getarg(0,command_line)
  do i = 1, n
     call lcl_getarg(i,string)
     command_line = trim(command_line)//' '//trim(string)
  end do
end function command_line

!----------------------------------------------------------------------
!! Return the index of the argument which matches the string opt -- 
!! Intended to be used to get hold of the presence of an option, and/or
!! to locate an option so that one can extract values that follow it
!!
!! Trailing spaces are ignored.
!!
!! Returns a negative number if the option is not found.
!!
!! GPS 24/01/98 
!----------------------------------------------------------------------
function iargc_opt(opt)
  use io_utils, except => iargc_opt
  use track_args
  implicit none
  integer                      :: iargc_opt
  character(len=*), intent(in) :: opt
  !------------------------------------------------------------
  integer :: i, n
  character(len=max_arg_len) :: string

  n = lcl_iargc()
  do i = 1, n
     call lcl_getarg(i,string)
     if (trim(string) == trim(opt)) exit
  end do
  ! Check to see if argument found
  if (i > n) then
     !-- make sure that iargc_opt + reasonable number remains negative
     iargc_opt = -100
  else
     iargc_opt = i
     call ta_RegisterUsed(iargc_opt)
  end if
end function iargc_opt



!----------------------------------------------------------------------
!! Return the string value corresponding to the argument which follows
!! the command line option opt
!!
!! GPS 8/11/95 (CCN8 23)
!----------------------------------------------------------------------
function string_val_opt(opt,default_val)
  use io_utils, except => string_val_opt
  use track_args
  implicit none
  character(len=max_arg_len)             :: string_val_opt
  character(len=*), intent(in)           :: opt
  character(len=*), intent(in), optional :: default_val
  integer :: i,n
  i = iargc_opt(opt)
  n = lcl_iargc()
  if (i >= n .or. i < 0) then
     if (present(default_val)) then
        string_val_opt = default_val
     else
        write(0,*) 'String value for option ',trim(opt),' has been&
             & requested'
        write(0,*) 'but that option is not present, and no default value&
             & was provided'
        stop
     end if
  else
     call lcl_getarg(i+1,string_val_opt)
     call ta_RegisterUsed(i+1)
  end if
end function string_val_opt

!----------------------------------------------------------------------
!! Return the integer value corresponding to the argument which follows
!! the command line option opt
!!
!! GPS 8/11/95 (CCN8 23)
!----------------------------------------------------------------------
function int_val_opt(opt,default_val)
  use io_utils, except => int_val_opt
  implicit none
  integer                      :: int_val_opt
  character(len=*), intent(in) :: opt
  integer, intent(in), optional :: default_val
  !----------------------------------------------------------------------
  integer :: i, n

  i = iargc_opt(opt)
  n = lcl_iargc()
  if (i >= n .or. i < 0) then
     if (present(default_val)) then
        int_val_opt = default_val
     else
        write(0,*) 'Numerical value for option ',trim(opt),' has been&
             & requested'
        write(0,*) 'but that option is not present, and no default value&
             & was provided'
        stop
     end if
  else
     int_val_opt = int_val_arg(i+1,default_val)
  end if
end function int_val_opt
!----------------------------------------------------------------------
!! Similar to int_val_opt, but for dble value
!----------------------------------------------------------------------
function dble_val_opt(opt,default_val)
  use io_utils, except => dble_val_opt
  implicit none
  real(kind(1d0))                      :: dble_val_opt
  character(len=*), intent(in) :: opt
  real(kind(1d0)), intent(in), optional :: default_val
  !----------------------------------------------------------------------
  integer :: i, n

  i = iargc_opt(opt)
  n = lcl_iargc()
  if (i >= n .or. i < 0) then
     if (present(default_val)) then
        dble_val_opt = default_val
     else
        write(0,*) 'Numerical value for option ',trim(opt),' has been&
             & requested'
        write(0,*) 'but that option is not present, and no default value&
             & was provided'
        stop
     end if
  else
     dble_val_opt = dble_val_arg(i+1,default_val)
  end if
end function dble_val_opt
!----------------------------------------------------------------------
!! Say opt is -xxx: if -xxx is present on the command line, then
!! return true; if -noxxx is present on the command line, then return false.
!! If neither is present on the command line, then return default_val,
!! which therefore is not optional. If both arguments are present, then
!! return default val.
!!
!! GPS 8/11/95 (CCN8 23)
!----------------------------------------------------------------------
function log_val_opt(opt,default_val)
  use io_utils, except => log_val_opt
  implicit none
  logical                       :: log_val_opt
  character(len=*), intent(in)  :: opt
  logical, intent(in), optional :: default_val
  !----------------------------------------------------------------------
  character(len=30) :: noopt
  integer :: i, j

  i = len_trim(opt)
  noopt = opt(1:1)//'no'//opt(2:i)

  i = iargc_opt(trim(opt))
  j = iargc_opt(trim(noopt))
  
  if (i > 0 .neqv. j > 0) then
     log_val_opt = (i>0)
  else
     if (present(default_val)) then
        log_val_opt = default_val
     else
        log_val_opt = .false.
     end if
  end if
end function log_val_opt

  
!----------------------------------------------------------------------
!! Return the integer value corresponding to the argk^th command line
!! argument. If # args < argk then use default if it is present, otherwise
!! output an error message and stop
!!
!! GPS 8/11/95 (CCN8 23)
!----------------------------------------------------------------------
integer function int_val_arg(argk, default_val)
  use track_args
  use io_utils, except => int_val_arg
  implicit none
  integer, intent(in)           :: argk
  integer, intent(in), optional :: default_val
  !------------------------------------------------------------
  integer             :: i
  character(len=max_arg_len) string

  i = lcl_iargc()
  if (i < argk .or. argk < 0) then
     if (present(default_val)) then
        int_val_arg = default_val
        return
     else
        write(0,*) 'Numerical value of arg',argk,' has been requested'
        write(0,*) 'but only',i,' args present, and no default value&
             & was provided'
        stop
     end if
  end if

  call lcl_getarg(argk, string)
  call ta_RegisterUsed(argk)
  if (trim(string) == '-' .and. present(default_val)) then
     int_val_arg = default_val
  else
     int_val_arg = value(string)
  end if

end function int_val_arg

      
!======================================================================
!! Routine to convert string into an integer
!! Added ability to cope with negative numbers
!! GPS 30/11/94+13/11/96
!======================================================================

integer function value(string)
      implicit none
      character(*),  intent(in)  ::   string
      !----------------------------------------------------------------
      character(11), parameter   ::  template = '0123456789-'
      character  ::  current
      integer    ::  i, j, sgn
      logical    ::  number_started

      value          = 0
      sgn = 1
      number_started = .false.

loop: do i = 1, len(string)
         current = string(i:i)
         j = index(template, current)
         if (j == 11) then
            if (number_started) then
               write(0,*) string(1:len(string)), ' is an invalid number'
               write(0,*) 'Stop.'
               stop
            end if
            sgn = -1
            number_started = .true.
            cycle
         end if
         if (j /= 0) number_started = .true.
         if (number_started) then
            if (j == 0) exit
            value = value * 10 + (j-1)
         endif
      end do loop
      value = value * sgn
end function value

!----------------------------------------------------------------------
!! Return the dble value corresponding to the argk^th command line
!! argument. If # args < argk then use default if it is present, otherwise
!! output an error message and stop
!!
!! GPS 8/11/95 (CCN8 23)
!----------------------------------------------------------------------
real(kind(1d0)) function dble_val_arg(argk, default_val)
  use track_args
  use io_utils, except => dble_val_arg
  implicit none
  integer,         intent(in)           :: argk
  real(kind(1d0)), intent(in), optional :: default_val
  !------------------------------------------------------------
  integer             :: i
  character(80) string

  i = lcl_iargc()
  if (i < argk .or. argk < 0) then
     if (present(default_val)) then
        dble_val_arg = default_val
        return
     else
        write(0,*) 'Numerical value of arg',argk,' has been requested'
        write(0,*) 'but only',i,' args present, and no default value&
             & was provided'
        stop
     end if
  end if

  call lcl_getarg(argk, string)
  call ta_RegisterUsed(argk)
  if (trim(string) == '-' .and. present(default_val)) then
     dble_val_arg = default_val
  else
     dble_val_arg = dble_value(string)
  end if
end function dble_val_arg


!---------------------------------------------------------------
!! returns the string corresponding to argument argk.
!! If that argument is absent, return the default_val (if provided)
function string_val_arg(argk, default_val)
  use track_args
  use io_utils, except => string_val_arg
  implicit none
  character(len=max_arg_len)             :: string_val_arg
  integer,          intent(in)           :: argk
  character(len=*), intent(in), optional :: default_val
  integer             :: i

  i = lcl_iargc()
  if (i < argk .or. argk < 0) then
     if (present(default_val)) then
        string_val_arg = trim(default_val)
        return
     else
        write(0,*) 'String value of arg',argk,' has been requested'
        write(0,*) 'but only',i,' args present, and no default value&
             & was provided'
        stop
     end if
  end if
  
  call lcl_getarg(argk, string_val_arg)
  call ta_RegisterUsed(argk)
end function string_val_arg
     

!======================================================================
!! Routine to convert string into an float
!! Added ability to cope with negative numbers. Cannot cope with
!! scientific notation or other such fancy things.
!!
!! Actually: current version just does a read on the string...
!!
!! GPS 30/11/94+13/11/96+18/11/96
!======================================================================
real(kind(1d0)) function dble_value(string)
      implicit none
      character(*),  intent(in)  ::   string
      !----------------------------------------------------------------
      character(12), parameter   ::  template = '0123456789-.'
      character  ::  current
      integer    ::  i, j, sgn
      logical    ::  number_started, dpoint
      real(kind(1d0)) :: jfact, pfact

      read(string,*) dble_value
      return
      !-- default sign is positive
      sgn = 1

      number_started = .false.
      dpoint = .false.
      dble_value          = 0
      jfact = 1d0
      pfact = 10d0

loop: do i = 1, len(string)
         current = string(i:i)
         j = index(template, current)
         if (j == 11) then
            if (number_started) call invalid_number
             sgn = -1
            number_started = .true.
            cycle
         end if
         if (j /= 0) number_started = .true.
         if (number_started) then
            if (j == 0) exit
            if (j == 12) then 
               if (dpoint) call invalid_number
               dpoint = .true.
               pfact = 1d0
               cycle
            end if
            if (dpoint) jfact = jfact * 0.1d0
            dble_value = dble_value * pfact + (j-1) * jfact
         endif
      end do loop
      dble_value = dble_value * sgn
contains

  subroutine invalid_number
    write(0,*) string(1:len(string)), ' is an invalid number'
    write(0,*) 'Stop.'
    stop
  end subroutine invalid_number
  
end function dble_value

!----------------------------------------------------------------------
!! A set of routines for converting a number to a string, with optional
!! format specifier. 
function int2char(num,frmt)
  implicit none
  character(len=30) :: int2char
  integer, intent(in) :: num
  character(*), intent(in), optional :: frmt
  if (present(frmt)) then
     write(int2char,frmt) num
  else
     write(int2char,*) num
  end if
  int2char = adjustl(int2char)
end function int2char
function sp2char(num,frmt)
  implicit none
  character(len=30) :: sp2char
  real(kind(1.0)), intent(in) :: num
  character(*), intent(in), optional :: frmt
  if (present(frmt)) then
     write(sp2char,frmt) num
  else
     write(sp2char,*) num
  end if
  sp2char = adjustl(sp2char)
end function sp2char
function dp2char(num,frmt)
  implicit none
  character(len=30) :: dp2char
  real(kind(1d0)), intent(in) :: num
  character(*), intent(in), optional :: frmt
  if (present(frmt)) then
     write(dp2char,frmt) num
  else
     write(dp2char,*) num
  end if
  dp2char = adjustl(dp2char)
end function dp2char


!======================================================================
!! Returns a new device if any are available, or else causes an error
!! dump
!!
!! GPS 13/04/98
!======================================================================
function get_new_device() result(dev)
  use io_utils, except => get_new_device
  implicit none
  integer :: dev
  !--------------------------------------------------------------
  integer, parameter :: dev_low = 30
  logical :: exist, opened
  integer :: iostat
  character(len=*), parameter :: subname='get_new_device'

  dev = dev_low
  do
     inquire(unit=dev,iostat=iostat, exist=exist, opened=opened)
     if (iostat /= 0) call error_report(subname, 'iostat non zero')
     if (.not. exist) call error_report(subname, 'available devices exhausted')
     if (.not. opened) exit
     dev = dev + 1 
  end do

end function get_new_device


!==========================================================================
!! Routine to write `time stamp' to a given output device (idev). The stamp
!! also contains the name of the program that generates it (taken from arg0)
!!
!! GPS 4/12/96
!==========================================================================
subroutine time_stamp(idev,string)
  use io_utils, excepts => time_stamp
  implicit none
  integer,          intent(in),  optional :: idev
  character(len=*), intent(out), optional :: string
  !----------------------------------------------------------------------
  character(79) :: prog_name
  integer       :: time(8)
  
  call lcl_getarg(0,prog_name)
  call date_and_time(values=time)
  if (present(idev)) then
     !write(idev,15) prog_name(1:index(prog_name,' ')-1),&
     !     & time(3),time(2),time(1),time(5),time(6),time(7)
     write(idev,15) trim(prog_name),&
          & time(3),time(2),time(1),time(5),time(6),time(7)
  end if
  if (present(string)) then
     !write(string,15) prog_name(1:index(prog_name,' ')-1),&
     !     & time(3),time(2),time(1),time(5),time(6),time(7)
     write(string,15) trim(prog_name),&
          & time(3),time(2),time(1),time(5),time(6),time(7)
  end if
  
15 format('Stamped by ',a,' on ',i2,'/',i2.2,'/',i4.4,' at ',i2.2,':'&
        & ,i2.2 ,':',i2.2) 
  call lcl_flush(idev)
end subroutine time_stamp

!======================================================================
!! Standard error reporting routine. Writes error to
!! stderr and then does one of the following (specified by contents of
!! "action")
!!
!!  core
!!  stop
!!  cont
!!
!! Default action is currently defined below, but maybe its definition
!! should be moved to a globally accessible location.
!!
!! GPS 19/08/97
!======================================================================
subroutine error_report(subname,line1,line2,line3,line4,action)
  use io_utils, excepts => error_report; implicit none
  character(*), intent(in) :: subname
  character(*), intent(in), optional :: line1, line2, line3, line4
  character(*), intent(in), optional :: action
  !--------------------------------------------------------------------
  character(*), parameter :: default_action = 'core'
  character(10) :: laction
  real :: a, b
  
  if (present(action)) then
     laction = trim(action)
  else
     laction = default_action
  end if

  write(0,*)
  write(0,'(2a)') 'Error report from routine: ',subname
  write(0,*)
  if (present(line1)) then
     write(0,'(a)') 'Reason is:'
     write(0,'(a)') line1
  end if
  if (present(line2)) write(0,'(a)') line2
  if (present(line3)) write(0,'(a)') line3
  if (present(line4)) write(0,'(a)') line4
  write(0,*)
  call time_stamp(0)
  !------ allow for more information on exit.
  if (trim(laction) == 'core') then
     a = 1d0; b = 1d0
     write(0,'(a)') 'Error report routine is about to dump core'
     write(0,*)
     write(0,*) 1d0/sqrt(a-b)
     !-- just in case that did not work, do an explicit stop
     stop
  else if (trim(laction) /= 'cont') then
     write(0,'(a)') 'Program stopped'
     stop
  else
     write(0,'(a)') 'Execution continuing'
  end if
end subroutine error_report

!! alternative name to access to the sub_defs_io module,
!! so as to match this filename
module sub_defs_io
  use io_utils
  implicit none
end module sub_defs_io