#
# <20200429.1556.02>
#
# This code is ment to be attached to a "text" window
# as a menu option (or just plain called after the window is created,)
# It is assumed that that window contains the contents of a file.
# When called, the function "follow" will cause trailing lines 
# of the file to be added to the window as they are written to 
# the file (i.e. this is just like "tail -f")

# In MSW this wonder is done entirely in TCL using the
# twapi::begin_filesystem_monitor code. This code requires a threaded build
# of Tcl.  If this is not the case we fall back to polling the file every
# 5 seconds or so. If we then see a filesystem_monitor call we will stop
# the fall backcalls.

# To call
#
#  followFile w file po
#
#     where: w is the "text" window
#            file  is the file displayed in that window
#            endln is the number of characters in the original file.
#                  {lines chars}. (Note: on MSW we use chars and 'chan seek',
#                  while 'tail' wants lines. This is covered by the 'fr' setup.)
#            po    <optional> is a logical. If true position the window 
#                  such that the last line of text is visable. 
#                  If false, do not.
#
# If "po" is false the window will be positioned only if the cursor is
# at the end of the last line prior to any update that adds new line.  
# In such a case the cursor will remain at the end of the last line.
#
# If the window is empty, the whole file will be written to it as well
# as any new lines as they are written to the file.

# If the "file" is not readable the function will return with
# a string indicating the problem.  Other wise an empty string is returned.
#
proc followFile {w file endln {po 0}} {
  set r [followFile_ $w $file $endln $po]
  if {$r != {}} {
    PopError $r
  }
}

proc followFile_ {w file endln po} {
  global config glob
  # We have several ways to make this work for any number
  # different windows.  What we do is hang everything on the window name.
  # We will release all the globals when the window is closed.
  #
  if {[info exists glob($w,fid)]} {
    return [_ "Follow already active on window %s" $w]
  }
  if {![winfo exists $w]} {
    return [_ "Window %s does not exist." $w]
  }
  if {![file readable $file]} {
    return [_ "File %s is not readable." $file]
  }
  # if {[auto_execok [lindex $config(cmd,tail) 0]] == {} } {
  #   return [_ "Command '$config(cmd,tail)' (config(cmd,tail)) not found"]
  # }
  bind $w <Destroy> "+followFileCleanup $w"

  # set ex [format [FixFormatString $config(cmd,tail)] $endln \
  # 	      [FixFileName $file -s 1]] 
  # set cmd [ReSpaceString {} $ex]
  #
  # This is a real hack, but I want to keep the interface.
  # frECF wants to put quotes around all %s but we don't want that here
  # so we format the first %s here...
  #
  # set cpart [format "[subst -nob $config(cmd,tail)]" [incr endln] %s]
  # frputs cpart file
  # set cmd [frECF [list {*}$cpart] [list $file] -f 1]
  # set r [catch {open "|[list {*}$cmd 2>@1]" r} fid]
  # frputs  cmd endln
  #		      -patterns [list +*[file tail $file]]\
      #
  set r [catch {open $file r} fid]
  if {!$r} {
    chan seek $fid $endln
    frputs endln
    fconfigure $fid -buffering none
    fconfigure $fid -blocking 0
    fconfigure $fid -translation auto
    #chan event $fid readable "followFileCompletePipe $w $fid"
    if {$::MSW} {
      set r [catch {twapi::begin_filesystem_monitor\
			[file dir $file]\
			"followFileCompletePipe $w $fid fsm" \
		      } hand]
      if {$r != 0} {return -code \
			error "twapi begin_filesystem_monitor returns '$hand'"}
      set glob($w,hand) $hand
    } else {
      if {[catch {package require inotify}] == 0} {
	proc handler$w {w fid args} {
	  while {[watch$w queue] != 0 && [incr failsafe] < 10} {
	    watch$w read
	  }
	  followFileCompletePipe $w $fid fsm
	}
	inotify create watch$w [list handler$w $w $fid]
	watch$w add $file "w"
      }
    }
    set glob($w,after) [after 5000 [list followFileCompletePipe $w $fid tm]] 
    set glob($w,fid) $fid
    set glob($w,po) $po
    return {}
  } else {
    return -code error $fid
  }
}

proc followFileCompletePipe { w fid who args} {
  global glob 
  # Log [_ "%s called" followFileCompletePipe]
  set out ""
  set end [$w index "end-1 chars"] 
  set curs [$w index insert]
  set curserAtEnd [expr {$end == $curs}]
  set r [catch {set out [read -nonewline $fid]} err]
  set eof [eof $fid]
  frputs r out err eof fid end curs who args
  if {$who == "fsm"} {
    after cancel $glob($w,after)
  } else {
    set glob($w,after) [after 5000 [list followFileCompletePipe $w $fid tm]]
  }
  if {$out != ""} {
    set mod [$w edit modified]
    $w insert end "\n$out"
    if {$curserAtEnd} {
      $w mark set insert end
    }
    if {$curserAtEnd || $glob($w,po)} {
      $w see end
    }
    if {!$mod} {
      $w edit reset
      $w edit modified 0
    }
  }
  # if {$r || $eof } {
  #   # do we need this???
  #   followFileCleanup $w
  #   if {$eof} {
  #     Log [_ "Eof found on follow?? Is -f option coded?"]
  #   } else {
  #     Log [_ "followFile error %s, aborting follow" $err]
  #   }
  # }
}

proc followFileCleanup {w} {
  global glob
  if {[info exists glob($w,fid)]} {
    catch "close $glob($w,fid)"
    if {$::MSW} {
      catch "twapi::cancel_filesystem_monitor $glob($w,hand)"
      unset glob($w,hand)
    } else {
      catch {rename watch$w {}}
    }
    unset glob($w,fid)
    unset glob($w,po)
    after cancel $glob($w,after)
  }
  return
}
