! $Id$
!
!  This module takes care of code messages.
!
module Messages
!
  use Cdata
  use Mpicomm
!$ use OMP_lib
!
  implicit none
!
  private
!
  public :: svn_id, timing
  public :: initialize_messages
  public :: information, warning, error
  public :: fatal_error, inevitably_fatal_error, not_implemented
  public :: fatal_error_local, fatal_error_local_collect
  public :: life_support_on, life_support_off
  public :: outlog, set_caller, memory_usage
  public :: terminal_setfgbrightcolor, terminal_setfgcolor
!
  integer, public, parameter :: iterm_DEFAULT   = 0
  integer, public, parameter :: iterm_BRIGHT    = 1
  integer, public, parameter :: iterm_UNDERLINE = 4
  integer, public, parameter :: iterm_FLASH     = 5
  integer, public, parameter :: iterm_FG_BLACK  = 30
  integer, public, parameter :: iterm_FG_RED    = 31
  integer, public, parameter :: iterm_FG_GREEN  = 32
  integer, public, parameter :: iterm_FG_YELLOW = 33
  integer, public, parameter :: iterm_FG_BLUE   = 34
  integer, public, parameter :: iterm_FG_MAGENTA= 35
  integer, public, parameter :: iterm_FG_CYAN   = 36
  integer, public, parameter :: iterm_FG_WHITE  = 37
  integer, public, parameter :: iterm_BG_BLACK  = 40
  integer, public, parameter :: iterm_BG_RED    = 41
  integer, public, parameter :: iterm_BG_GREEN  = 42
  integer, public, parameter :: iterm_BG_YELLOW = 43
  integer, public, parameter :: iterm_BG_BLUE   = 44
  integer, public, parameter :: iterm_BG_MAGENTA= 45
  integer, public, parameter :: iterm_BG_CYAN   = 46
  integer, public, parameter :: iip_EVERYTHING  = 0
  integer, public, parameter :: iip_DEFAULT     = 0
  integer, parameter :: iinformation_ip = 1000
  integer :: errors=0
  integer :: fatal_errors=0, fatal_errors_total=0
  logical :: ldie_onwarning=.false.
  logical :: ldie_onerror=.true.
  logical :: ldie_onfatalerror=.true.
  logical :: llife_support=.false.
!
  logical :: ltermcap_color=.false.
!
  character(LEN=2*labellen) :: scaller=''
  character(LEN=linelen) :: message_stored=''
!
  contains
!***********************************************************************
    subroutine initialize_messages
!
! Set a flag if colored output has been requested.
! Also set a flag if fake_parallel_io is requested.
!
      use Syscalls, only: get_env_var

      inquire(FILE="COLOR", EXIST=ltermcap_color)
!
      if (mailaddress=='') call get_env_var('MAILADDR',mailaddress)

      if (mailaddress/='') then
        if (index(trim(mailaddress),'@')==0 .or. index(trim(mailaddress),'.')==0) then
          call warning('initialize_messages', 'invalid mail address')
          mailaddress=''
        endif
      endif

      if (mailcmd=='') call get_env_var('MAILCMD',mailcmd)
      if (mailcmd=='') mailcmd = 'mail'
      if (submithost=='') call get_env_var("SLURM_SUBMIT_HOST",submithost)
      if (submithost=='') call get_env_var("HOSTNAME",submithost)

    endsubroutine initialize_messages
!***********************************************************************
    subroutine set_caller(caller)

     character(LEN=*) :: caller

     scaller=caller

    endsubroutine set_caller
!***********************************************************************
    subroutine not_implemented(location,message,force)
!
      use General, only: loptest

      character(len=*), optional :: location, message
      logical, optional :: force
!
      if (present(location)) scaller=location
!
      if (.not.llife_support) then
        errors=errors+1
!
        if (lroot .or. (ncpus<=16 .and. (message/=''))) then
          call terminal_highlight_error()
          write (*,'(A18)',ADVANCE='NO') "NOT IMPLEMENTED: "
          call terminal_defaultcolor()
          if (present(message)) then
            write(*,*) trim(scaller)//": "//trim(message)//'!'
          else
            write(*,*) trim(scaller)//": "//"Some feature waits to get implemented -- by you?"
          endif
        endif
!
        if (ldie_onfatalerror) then
          if (loptest(force)) call die_immediately
          call die_gracefully
        endif
!
      endif
!
    endsubroutine not_implemented
!***********************************************************************
   subroutine fatal_error(location,message,force)
!
      use General, only: loptest

      character(len=*), optional :: location
      character(len=*)           :: message
      logical,          optional :: force
!
      logical :: fatal
!
      if (present(location)) scaller=location
!
      if (.not.llife_support) then
!
        errors=errors+1
        fatal=loptest(force)
!
        if (lroot .or. (ncpus<=16 .and. (message/='')) .or. fatal) then
          call terminal_highlight_fatal_error()
          if (scaller=='') then
            write (*,*) "FATAL ERROR: "//trim(message)//'!!!'
          else
            write (*,*) "FATAL ERROR: "//trim(scaller)//": "//trim(message)//'!!!'
          endif
          call terminal_defaultcolor()
        endif
!
        if (ldie_onfatalerror) then
          if (fatal) call die_immediately
          call die_gracefully
        endif
!
      endif
!
    endsubroutine fatal_error
!***********************************************************************
    subroutine inevitably_fatal_error(location,message,force)
!
!  A fatal error that doesn't care for llife_support
!  Use (sparingly) in those cases where things should fail even during
!  pencil_consistency_test
!  07-26-2011: Julien\ Added forced exit if "force" is set to .true.
!
      use General, only: loptest

      character(len=*), optional :: location
      character(len=*)           :: message
      logical,          optional :: force
!
      logical :: fatal
!
      if (present(location)) scaller=location
!
      fatal=loptest(force)
      errors=errors+1
!
      if (lroot .or. (ncpus<=16 .and. (message/='')) .or. fatal) then
        call terminal_highlight_fatal_error()
        write (*,'(A13)',ADVANCE='NO') "FATAL ERROR: "
        call terminal_defaultcolor()
        write (*,*) trim(scaller) // ": " // trim(message)//'!!!'
      endif
!
      if (fatal) call die_immediately()
      call die_gracefully()
!
    endsubroutine inevitably_fatal_error
!***********************************************************************
    subroutine fatal_error_local(location,message)
!
!  Register a fatal error happening at one processor. The code will die
!  at the end of the time-step.
!
!  17-may-2006/anders: coded
!
      character(len=*), optional :: location
      character(len=*)           :: message
!
      if (present(location)) scaller=location
!
      if (.not.llife_support) then

        fatal_errors=fatal_errors+1
!
        if (lroot .or. (ncpus<=16 .and. (message/=''))) then
          call terminal_highlight_fatal_error()
          write (*,'(A13)',ADVANCE='NO') "FATAL ERROR: "
          call terminal_defaultcolor()
          write (*,*) trim(scaller)//": "//trim(message)//'!!!'
        endif
!
      endif

      if (message/='') then
        if (message_stored=='') then
          message_stored=trim(scaller)//": "//trim(message)
        elseif (index(message_stored,trim(scaller)//": "//trim(message))==0) then      
          message_stored=trim(message_stored)//'; '//trim(scaller)//": "//trim(message)
        endif
      endif
!
    endsubroutine fatal_error_local
!***********************************************************************
    subroutine fatal_error_local_collect
!
!  Collect fatal errors from processors and die if there are any.
!
!  17-may-2006/anders: coded
!
      use General, only: itoa

      character(LEN=linelen), dimension(ncpus) :: messages
      character(LEN=linelen) :: preceding
      integer :: i, istart, iend

      if (.not.llife_support) then

        call mpireduce_sum_int(fatal_errors,fatal_errors_total,MPI_COMM_WORLD)
        call mpibcast_int(fatal_errors_total,comm=MPI_COMM_WORLD)
!
        if (fatal_errors_total/=0) then
          call mpigather_scl_str(message_stored,messages)
          if (lroot) then
            print*, 'DYING - there were', fatal_errors_total, 'errors.'
            print*, 'This is probably due to one or more fatal errors that'
            print*, 'have occurred only on individual processors.'
            print*, 'Messages:'
            preceding=''; istart=0; iend=0
            do i=1,ncpus
              if (trim(messages(i))/=''.or.preceding/='') then
                if (trim(messages(i))/=preceding) then
                  if (istart>0) then
                    if (iend>istart) then
                      print'(a)', ' - '//trim(itoa(iend-1))//': '//trim(messages(iend))
                    else
                      print'(a)', ': '//trim(messages(iend))
                    endif
                  endif
                  if (trim(messages(i))/='') then
                    write(*,'(a)',advance='no') ' processor(s) '//trim(itoa(i-1))
                    istart=i
                  else
                    istart=0
                  endif
                  preceding=messages(i)
                endif
                iend=i
              endif
            enddo
            if (istart>0) then
              if (iend>istart) then
                print'(a)', ' - '//trim(itoa(iend-1))//': '//trim(messages(iend))
              else
                print'(a)', ': '//trim(messages(iend))
              endif
            endif
          endif
          if (ldie_onfatalerror) call die_gracefully
        endif
!
        fatal_errors=0
        fatal_errors_total=0
!
      endif
!
    endsubroutine fatal_error_local_collect
!***********************************************************************
    subroutine error(location,message)
!
      character(len=*), optional :: location
      character(len=*)           :: message
!
      if (present(location)) scaller=location

      if (.not.llife_support) then
        errors=errors+1
!
        if (lroot .or. (ncpus<=16 .and. (message/=''))) then
          call terminal_highlight_error()
          write (*,'(A7)',ADVANCE='NO') "ERROR: "
          call terminal_defaultcolor()
          write (*,*) trim(scaller) // ": " // trim(message)//'!!!'
        endif
!
        if (ldie_onerror) call die_gracefully
!
      endif
!
    endsubroutine error
!***********************************************************************
    subroutine warning(location,message,ipr)
!
!  Print out colored warning.
!
!  30-jun-05/tony: coded
!   2-apr-17/MR: optional parameter ip = processor number added
!
      use General, only: ioptest

      character (len=*), optional :: location
      character (len=*)           :: message
      integer, optional :: ipr
!
      if (present(location)) scaller=location

      if (.not.llife_support) then
        if ((iproc_world == ioptest(ipr,0)) .and. (message /= '')) then
          call terminal_highlight_warning()
          write (*,'(A9)',ADVANCE='NO') "WARNING: "
          call terminal_defaultcolor()
          write (*,*) trim(scaller) // ": " // trim(message)//'!'
!          call flush(6) ! has to wait until F2003
        endif
!
        if (ldie_onwarning) call die_gracefully
!
      endif
!
    endsubroutine warning
!***********************************************************************
    subroutine information(location,message,level,ipr)
!
!  Print out colored warning.
!
!  30-jun-05/tony: coded
!
      use General, only: ioptest

      character (len=*), optional :: location
      character (len=*)           :: message
      integer,           optional :: level,ipr

      integer :: level_ = iinformation_ip
!
      if (present(location)) scaller=location

      if (present(level)) level_=level
!
      if ((iproc_world == ioptest(ipr,0)) .and. (message /= '')) then
        if (ip<=level_) write (*,*) trim(scaller) // ": " // trim(message)//'.'
      endif
!
    endsubroutine information
!***********************************************************************
    subroutine svn_id(svnid)
!
!  Print SVN Revision info in a compact, yet structured form.
!  Expects the standard "SVN Id:" line as argument.
!
!  25-jun-02/wolf: coded
!
      use Syscalls, only: directory_exists
!
      character (len=*) :: svnid
!
      character (len=20) :: filename, revision, author, date
      character (len=200) :: fmt
      character (len=20) :: tmp1,tmp2,tmp3,tmp4
      integer :: if0,if1,iv0,iv1,iy0,iy1,it0,it1,ia0,ia1,iat
      integer, parameter :: wf=18, wv=7, wd=19 ! width of individual fields
      integer, parameter :: unit=1
      integer :: wd1
      logical, save :: lfirstcall = .true.
!
!  Write string to screen and to 'svnid.dat' file.
!
      if (lfirstcall) then
        !if (.not. file_exists (datadir)) call fatal_error ('svn_id','missing data directory "'//trim(datadir)//'"')
        if (.not. directory_exists (datadir)) &
          call fatal_error ('svn_id','missing data directory: "'//trim(datadir)//'"')
        if (lstart) then
          open(unit, file=trim(datadir)//'/svnid.dat', status='replace')
        else
          open(unit, file=trim(datadir)//'/svnid.dat', status='replace')
        endif
        lfirstcall = .false.
      else
        open(unit, file=trim(datadir)//'/svnid.dat', status='old', position='append')
      endif
!
!  Construct format
!  Need to set explicit format below, to avoid problems when the
!  -i8 compiler option is invoked. Hope that the format i5 is sufficient.
!
      write(tmp1,'(i5)') wf
      write(tmp2,'(i5)') 6+wf
      write(tmp3,'(i5)') 6+wf+4+wv
      write(tmp4,'(i5)') 6+wf+4+wv+2+wd
!      fmt = '(A, A' // trim(adjustl(tmp1)) &
      fmt = '(A, A' &
           // ', T' // trim(adjustl(tmp2)) &
           // ', " v. ", A, T' // trim(adjustl(tmp3)) &
           // ', " (", A, T' // trim(adjustl(tmp4)) &
           // ', ") ", A)'
      if ((svnid(1:1) == "$") .and. (svnid(2:4) == "Id:")) then
      ! starts with `$...' --> SVN Id: line
!
!  file name
!
        if0 = index(svnid, ": ") + 2
        if1 = if0 + index(svnid(if0+1:), " ") - 1
        call extract_substring(svnid, if0, if1, filename)
!
!  Revision number
!
        iv0 = if1 + 2
        iv1 = iv0 + index(svnid(iv0+1:), " ") - 1
        call extract_substring(svnid, iv0, iv1, revision)
!
!  Date
!
        iy0 = iv1 + 2             ! first char of year
        iy1 = iy0 + 10            ! last char of year
        it0 = iy1 + 2             ! first char of time-of-day
        it1 = it0 + index(svnid(it0+1:), " ") - 1
        if (svnid(it1:it1) == "Z") then
          call extract_substring(svnid, iy0, it1-1, date) ! strip trailing `Z'
        else
          call extract_substring(svnid, iy0, it1, date)
        endif
!
!  Author
!
        ia0 = it1 + 2
        ! strip @some.where part off some user names
        iat = index(svnid(ia0+1:), "@")
        if (iat > 0) then
          ia1 = ia0 + iat - 1
        else
          ia1 = ia0 + index(svnid(ia0+1:), " ") - 1
        endif
        call extract_substring(svnid, ia0, ia1, author)
!
        write(*,fmt) "SVN: ", &
            trim(filename), &
            revision(1:wv), &
            date(1:wd), &
            trim(author)
!
        write(unit,fmt) "SVN: ", &
            trim(filename), &
            revision(1:wv), &
            date(1:wd), &
            trim(author)
      else                      ! not a SVN line; maybe `[No ID given]'
        wd1 = min(wd, len(svnid))
        write(*,fmt) "SVN: ", &
            '-------', &
            '', &
            '', &
            svnid(1:wd1)
        write(unit,fmt) "SVN: ", &
            '-------', &
            '', &
            '', &
            svnid(1:wd1)
      endif
      !write(*,'(A)') '123456789|123456789|123456789|123456789|123456789|12345'
      !write(*,'(A)') '         1         2         3         4         5'
!
      close(unit)
!
    endsubroutine svn_id
!***********************************************************************
    subroutine timing(location,message,instruct,mnloop)
!
!  Timer: write the current systems time to standart output
!  provided it=it_timing.
!
      integer :: lun=9
      character(len=*), optional :: location
      character(len=*) :: message
      real(KIND=rkind8) :: time
      real(KIND=rkind8), save :: time_initial
      character(len=*), optional :: instruct
      logical, optional :: mnloop
      integer :: mul_fac
      logical, save :: opened = .false.
!
!$ if (omp_in_parallel()) return
!
      if (present(location)) scaller=location
!
!  work on the timing only when it == it_timing
!
      if (it /= it_timing) return
!
      if (lroot) then
!
!  initialize
!
        if (present(instruct)) then
          if (trim(instruct) == 'initialize') then
            open(lun, file=trim(datadir)//'/timing.dat', status='replace')
            opened = .true.
            time_initial = mpiwtime()
          endif
        endif
!
!  write current timing to the timing file
!
        if (lfirst) then
          if ((present(mnloop) .and. lfirstpoint) .or. .not. present(mnloop)) then
            time = mpiwtime() - time_initial
            if (present(mnloop)) then
              mul_fac = nyz
            else
              mul_fac = 1
            endif
            if (.not. opened) then
              open(lun, file=trim(datadir)//'/timing.dat', position='append')
              opened = .true.
            endif
            write(lun,*) time, mul_fac, trim(scaller)//": "//trim(message)//'.'
          endif
        endif
!
!  finalize
!
        if (present(instruct)) then
          if (opened .and. (trim(instruct) == 'finalize')) then
            close(lun)
            opened = .false.
          endif
        endif
!
      endif
!
    endsubroutine timing
!***********************************************************************
    subroutine extract_substring(string, idx0, idx1, substring)
!
!  Extract a substring after sanity check
!
      intent(in) :: string, idx0, idx1
      intent(out) :: substring
      character(len=*) :: string
      integer :: idx0, idx1
      character(len=*) substring
!
      if (1 <= idx0 .and. idx0 <= idx1 .and. idx1 <= len(string)) then
        substring = string(idx0:idx1)
      else
        substring = "???"
      endif
!
    endsubroutine extract_substring
!***********************************************************************
    subroutine life_support_off(message)
!
!  Allow code to die on errors
!
!  30-jun-05/tony: coded
!
      character(len=*) :: message
!
!  set llife_support
!
      llife_support=.false.
      call information('life_support_off',message,level=12)
!
    endsubroutine life_support_off
!***********************************************************************
    subroutine life_support_on(message)
!
!  Prevent the code from dying on errors
!
!  30-jun-05/tony: coded
!
      character(len=*) :: message
!
      call information('life_support_on',message,level=12)
      llife_support=.true.
!
    endsubroutine life_support_on
!***********************************************************************
    subroutine terminal_setfgcolor(col)
!
!  Set foreground color of terminal text
!
!  08-jun-05/tony: coded
!
      integer :: col
!
      if (ltermcap_color) write(*,fmt='(A1,A1,I2,A1)',ADVANCE='no') CHAR(27), '[', col, 'm'
!
    endsubroutine terminal_setfgcolor
!***********************************************************************
    subroutine terminal_setfgbrightcolor(col)
!
!  Set bright terminal colors
!
!  08-jun-05/tony: coded
!
      integer :: col
!
      if (ltermcap_color) &
        write(*,fmt='(A1,A1,I1,A1,I2,A1)',ADVANCE='no') CHAR(27), '[', iterm_BRIGHT, ';', col, 'm'
!
    endsubroutine terminal_setfgbrightcolor
!***********************************************************************
    subroutine terminal_defaultcolor
!
!  Set terminal color to default value
!
!  08-jun-05/tony: coded
!
      if (ltermcap_color) &
        write(*,fmt='(A1,A1,I1,A1)',ADVANCE='no') CHAR(27), '[', iterm_DEFAULT, 'm'
!
    endsubroutine terminal_defaultcolor
!***********************************************************************
    subroutine terminal_highlight_warning
!
!  Change to warning color
!
!  08-jun-05/tony: coded
!
      if (ltermcap_color) write(*,fmt='(A1,A1,I1,A1,I2,A1)',ADVANCE='no') &
                                  CHAR(27), '[', iterm_BRIGHT, ';', iterm_FG_MAGENTA, 'm'
!
    endsubroutine terminal_highlight_warning
!***********************************************************************
    subroutine terminal_highlight_error
!
!  Change to warning color
!
!  08-jun-05/tony: coded
!
      if (ltermcap_color) write(*,fmt='(A1,A1,I1,A1,I2,A1)',ADVANCE='no') &
                                  CHAR(27), '[', iterm_BRIGHT, ';', iterm_FG_RED, 'm'
!
    endsubroutine terminal_highlight_error
!***********************************************************************
    subroutine terminal_highlight_fatal_error
!
!  Change to warning color
!
!  08-jun-05/tony: coded
!
      if (ltermcap_color) write(*,fmt='(A1,A1,I1,A1,I2,A1)',ADVANCE='no') &
                                  CHAR(27), '[', iterm_BRIGHT, ';', iterm_FG_RED, 'm'
!
    endsubroutine terminal_highlight_fatal_error
!***********************************************************************
  logical function outlog(code,mode,file,dist,msg,lcont,location,iomsg)
!
!  Creates log entries for I/O errors in ioerrors.log.
!  Notifies user via e-mail if address mailaddress is given.
!  stops program if lstop_on_ioerror is set.
!  reverts incompletely written files to a defined state, in particular
!  distributed files in data/procN/ -> all sub-files in a coherent state
!
!  code(IN): errorcode from IOSTAT
!  mode(IN): describes failed action, starts with 'open', 'openr', 'openw', 'read', 'write' or 'close'
!            for 'read' and 'write': should contain the name of the relevant variable(s)
!  file(IN): file at which operation failed,
!            if omitted assumed to be the one saved in curfile
!            usually set by the call with mode='open'
!  dist(IN): indicator for distributed files (>0) and need of synchronization of file states across nodes
!            or simple backskipping (<0);|dist| = logical unit number
!            only considered in calls with mode='open'
!  msg(IN) : additional message text
!  lcont(IN): flag for continue despite of READ error
!  location(IN): name of program unit, in which error occurred
!                if omitted assumed to be the one saved in scaller
!                usually set by the call with mode='open'
!  iomsg(IN): Fortran runtime message text
!
!  return value: flag for 'I/O error has occurred'. If so execution should jump immediately after the 'close'
!                statement ending the present group of I/O operations as outlog closes (tries to close) the file.
!                It is in the responsibility of the programmer that by this jump no relevant statements are missed.
!
!  3-nov-11/MR: coded;
! 16-nov-11/MR: modified; experimental version which always stops program on I/O error
! 13-Dec-2011/Bourdin.KIS: added EOF sensing, which is not an error.
! 20-oct-13/MR: new options lcont,location introduced
! 28-oct-13/MR: handling of lcont modified: now only in effect when reading
! 26-mar-15/MR: mode now saved across calls, reset by calls with mode=open and mode=close
!               -> read or write needs not to be indicated in mode when set by call with mode=open
!
    use General, only: itoa,date_time_string,safe_character_append,safe_character_prepend,backskip,loptest
    use Mpicomm, only: report_clean_output
    use Syscalls, only: system_cmd
!
    integer,                     intent(IN) :: code
    character (LEN=*),           intent(IN) :: mode
    character (LEN=*), optional, intent(IN) :: file,msg,location,iomsg
    integer,           optional, intent(IN) :: dist
    logical,           optional, intent(IN) :: lcont
!
    character (LEN=fnlen), save :: curfile=''
    ! default: file not distributed, no backskipping
    integer, save :: curdist=0, curback=0
    logical, save :: lread=.false.
!
    integer, parameter :: unit=90
    integer :: iostat, ind, len_mode
    character (LEN=intlen) :: date, codestr
    character (LEN=fnlen), dimension(2) :: strarr
    character (LEN=fnlen) :: filename, submsg, message
    logical :: lopen, lclose, lwrite, lsync, lexists, lcontl, lscan
    character(LEN=4) :: modestr
    character(LEN=labellen) :: item
    character :: sepchar
!
    outlog = .false.; modestr=''
    len_mode=len_trim(mode)
!
    lopen = .false.; lclose=.false.; lscan=.true.
    if (mode(1:4)=='open') then
      lopen = .true.
    elseif (mode(1:4)=='read') then
      lread=.true.
      if (len_mode>5) item=mode(6:)
      lscan=.false.
    endif

    if (lscan.and.len_mode>=5) then
      if (mode(1:5)=='openr') then
        lread = .true.
      elseif (mode(1:5)=='openw') then
        lread = .false.
      elseif (mode(1:5)=='write') then
        lread = .false.
        if (len_mode>6) item=mode(7:)
      elseif (mode(1:5)/='read ') then
        item=mode
      endif

      lclose = mode(1:5)=='close'
    endif

    lwrite=.not.lread
    if (lclose) then
      modestr='clos'
    else
      if (lread ) modestr='read'
      if (lwrite) modestr='writ'
    endif
!
    if (present(file)) curfile = file
    filename = ''
    if (curfile /= '' ) filename = ' "'//trim(curfile)//'"'
    if (lclose) curfile = ''

    if (present(location)) scaller = location

    message = ""
    if (present (msg)) then
      message = ': '//trim (msg)
      sepchar = ';'
    else
      sepchar = ':'
    endif
    if (present (iomsg)) message = trim(message)//sepchar//' '//trim (iomsg)

    lcontl = loptest(lcont)
!
! Set the following expression to .false. to activate the experimental code
!
    if (.true.) then
      if (code < 0 .and. .not.(lread.and.lcontl)) then
        if (.not.lstop_on_ioerror) then
          call warning(scaller,'End-Of-File'//trim (filename)//trim (message))          !add mode?
        else
          outlog = .true.
          call fatal_error(scaller,'End-Of-File'//trim (filename)//trim (message))      !add mode?
        endif
      elseif (code > 0 .and. .not.(lread.and.lcontl)) then
        outlog = .true.
        call fatal_error(scaller,'I/O error (code '//trim (itoa (code))//')'// &
                         trim (filename)//trim (message), force=.true.)  !add mode?
      endif
      return
    endif
!
    ! EXPERIMENTAL CODE:
!
    if (lopen) then
!
      if (present(dist)) then
        curdist = dist
      else
        curdist = 0
      endif
!
      curback = 0                                      ! counter for successfully read records set back
!
    endif
!
    if (lwrite.and.code==0) curback = curback+1        ! number of succesfully written records after open
!
    if ( lroot ) then
      errormsg = ''; submsg = 'File'
    endif
!
    lsync = .false.
!
    if (.not.lopen.and.curdist/=0) then                        ! backskipping enabled
!
      if ( ncpus==1 .and. curdist>0 ) curdist = -curdist
!
      if ( ncpus>1 .and. curdist>0 .and. (lwrite.or.lclose.or.lread) ) then
                                                               ! read/write/close on distributed file failed (somewhere)
        lsync = report_clean_output(code/=0, errormsg)
        if (lsync.and..not.lread) then                         ! synchronization necessary as at least
                                                               ! one processor failed in writing
          if (lserial_io) then                                 ! no backskipping, needs to be checked
            submsg = ' not synchronized (lserial_io=T)!'
            lsync = .false.                                    ! should file be closed??? Is return value then correct?
                                                               ! better to stop immediately?
          else
!
            if (lclose.and.code==0) open(curdist,file=curfile,position='append') ! re-open successfully written and closed files
!
            if ( backskip(curdist,curback) ) then              ! try to set back file pointer by curback records
              if (lroot) submsg = trim(submsg)//' not'
            endif
            if (lroot) submsg = trim(submsg)//' synchronized.'
!
            close(curdist,IOSTAT=iostat)                       ! try to close file
            if (iostat/=0) call safe_character_append(submsg,'. File not closed!')
!
          endif
!
        endif
!
      else if ( curdist<0 .and. code/=0 ) then         ! undistributed file, operation failed
!
        lsync = .false.
        if (lwrite.or.lclose) then
!
          if ( backskip(abs(curdist),curback) ) submsg = trim(submsg)//' pointer not set back!'
!
          close(abs(curdist),IOSTAT=iostat)            ! try to close file
          if (iostat/=0) call safe_character_append(submsg,'. File not closed!')
!
        endif
!
      endif
    endif
!
    if ( lroot ) then
!
      if ( code==0 .and. .not.lsync ) return
!
      call safe_character_prepend( errormsg, 'ERROR' )
!
      if ( lopen.or.lread.or.lwrite.or.lclose ) then
!
        call safe_character_prepend( errormsg, ' when' )
!
        if (lopen) &
          call safe_character_append(errormsg,' opening ')

        if (lread.or.lwrite) &
          call safe_character_append(errormsg,' for ')
          
        call safe_character_append(errormsg,trim(modestr)//'ing ')

        if ( .not.(lopen.or.lclose).and.(lread .or. lwrite) ) then
          if (lread.or.lwrite) &
            call safe_character_append(errormsg,' '//trim(item))
          if (lread) then
            call safe_character_append(errormsg,' from')
          else
            call safe_character_append(errormsg,' to')
          endif
        endif
        call safe_character_append(errormsg,' file "')
!
      else
        call safe_character_append(errormsg,': file "')
      endif
!
      filename = curfile
      if ( ncpus>1 .and. curdist>=0 ) then         ! for synchronized file replace 'procN' by 'proc*'
        ind = index(curfile,'proc')+4
        filename(ind:ind) = '*'
      endif
!
      codestr = itoa(code)
      call safe_character_append(errormsg,trim(filename)//'". Code: '//trim(codestr))
      if (code<0) call safe_character_append(errormsg,' (EOF)')

      if ( submsg/='File' ) call safe_character_append(errormsg,'. '//trim(submsg))
      if ( present(msg) ) call safe_character_append(errormsg,'. '//trim(msg))
!
! scan of ioerrors.log to avoid multiple entries for the same file with same error code.
! When user eliminates cause of error, (s)he should also remove the corresponding line(s) in ioerrors.log.
!
      if (.not.lopen.and.lread) then
        lexists = .false.
      else
        strarr(1) = filename
        strarr(2) = codestr
        lexists = scanfile('ioerrors.log',2,strarr,'all')
      endif
!
      if ( .not.lexists ) then
!
        open(unit,file='ioerrors.log',position='append',iostat=IOSTAT)
!
        if (iostat==0) then
          call date_time_string(date)
          write(unit,'(a)',IOSTAT=iostat) date//' '//trim(errormsg)
          close(unit,IOSTAT=iostat)
        endif
!
        if (iostat/=0) write(*,'(a)',iostat=IOSTAT) date//' '//trim(errormsg)
!
        if ( .not.lopen.and..not.lread ) &        ! send mail to user
          call system_cmd( &
               'echo '//trim(errormsg)//'|'//trim(mailcmd)//"-s 'PencilCode Message' "//trim(mailaddress)//' >& /dev/null')
      endif
    endif
!
    if (code/=0.or.lsync) then
      outlog = .true.
      if (lstop_on_ioerror.or.(.not.lopen.and.lread.and..not.lcontl)) &      ! stop on error if requested by user or read operation
        call fatal_error(scaller, 'I/O error due to '//trim(errormsg)//' with '//trim(filename), force=.true.)  !add mode?
    endif
!
  end function outlog
!***********************************************************************
  logical function scanfile(file,nstr,strings,mode)
!
!  Scans a file for a line in which one or all strings in list strings occur.
!  Returns on first hit.
!
!  3-nov-11/MR: coded
!  15-Feb-2012/Bourdin: removed deprecated features
!
    character (LEN=*),                           intent(IN) :: file
    integer,                                     intent(IN) :: nstr
    character (LEN=*), dimension(nstr),          intent(IN) :: strings
    character (LEN=3),                 optional, intent(IN) :: mode
!
    character (LEN=3) :: model
    character (LEN=fnlen) :: line
    integer :: lun=90,i,count,io_err

    if ( .not.present(mode) ) then
      model='any'
    else
      model=mode
    endif
!
    scanfile = .false.
!
    open(lun,file=file,IOSTAT=io_err)
    if (io_err /= 0) return
!
    do
      read(lun,'(a)',IOSTAT=io_err) line
      if (io_err < 0) then
        exit
      else if (io_err > 0) then
        cycle
      endif
!
      count=0
      do i=1,nstr
        if (index(line,trim(strings(i))) /= 0) then
          if (mode=='any') then
            scanfile = .true.
            exit
          else
            count = count+1
          endif
        endif
      enddo
!
      if (count == nstr) then
        scanfile = .true.
        exit
      endif
!
    enddo
!
    close(lun)
!
  end function scanfile
!***********************************************************************
  subroutine memory_usage
!
! Prints total memory usage and per-process usage. 
!
! 20-apr-23/MR: coded
!
    use Mpicomm, only: mpireduce_max_int, mpireduce_sum_int
    use Syscalls, only: memusage

    integer :: memuse, memory, memcpu

    memuse=memusage()
    call mpireduce_max_int(memuse,memcpu)
    call mpireduce_sum_int(memuse,memory)

    if (lroot) then
      print'(1x,a,f9.3)', 'Maximum used memory per cpu [MBytes] = ', memcpu/1024.
      if (memory>1e6) then
        print'(1x,a,f12.3)', 'Maximum used memory [GBytes] = ', memory/1024.**2
      else
        print'(1x,a,f12.3)', 'Maximum used memory [MBytes] = ', memory/1024.
      endif
      print*
    endif

  endsubroutine memory_usage
!***********************************************************************
endmodule Messages
