pro progress,percentin,bigstep=bigstepin,smallstep=smallstepin, $
                 reset=reset,last=last,label=label,noeta=noeta, $
                 frequency=frequency,progstring=pstring, $
                 noprint=noprint,norecurse=norecurse, $
                 minval=minvalin,maxval=maxvalin

;+
;NAME:
;     PROGRESS
;PURPOSE:
;     Prints a progress summary in percent done and time remaining as
;     a process runs.  Call this routine multiple times from the
;     running process while updating the percent done parameter.
;CATEGORY:
;CALLING SEQUENCE:
;     progress,percent
;INPUTS:
;     percent = percent finished (in range 0 to 100, unless minval and
;               maxval are set)
;OPTIONAL INPUT PARAMETERS:
;KEYWORD PARAMETERS
;     /reset = this must be set for the first call to set up variables.
;     /last = the process is done and this is the last call.  Optional
;             for the last call.  Makes sure the printout goes all the
;             way to 100%, does a final carriage return, and resets
;             some variables.
;     label = string to print at the front of the progress line.  Only
;             used on the first call when /reset is set.
;     bigstep = percentage multiple to print the percent done as a
;               number (def = 25).  Only used on the first call when
;               /reset is set.  Integer.
;     smallstep = percentage multiple to print a dot (def = 5).  Only
;                 used on the first call when /reset is set.  Integer.
;     minval = percent value at process start (default = 0.0).  Only
;                 used on the first call when /reset is set.  Integer.
;     maxval = percent value at process end (default = 100.0).  Only
;                 used on the first call when /reset is set.  Integer.
;     /noeta = Do not print the estimated time to completion.  This
;              feature depends on your terminal accepting 8b as a
;              backspace character. If this does not work, the
;              formatting will be messed up.  So, if your formatting
;              is messed up, set this keyword to turn the feature off.
;     frequency = If set, update the estimated time to completion
;                 if at least 'frequency' seconds have passed since
;                 the last update.  The time is always printed when a
;                 dot or number is printed, as well. If set to 2, for
;                 example, update approximately every two seconds, etc. 
;                 If set to 0, update on every call to progress.
;                 The default (not set) is to update the time only
;                 when a dot or number is printed. 
;OUTPUTS:
;COMMON BLOCKS:
;SIDE EFFECTS:
;RESTRICTIONS:
;     Assumes that nothing else is printed between calls.  If it is,
;     the formatting will be messed up.  If you change the size of the
;     terminal during the process, the formatting may also be messed
;     up if it might have gone over the length of a line.  The
;     counting is done with integers, so you can't count, for example,
;     from 0. to 1. with dots printed at intervals of 0.1, regardless
;     of how you set minval and maxval.
;PROCEDURE:
;     Call the routine multiple times as progress is made.  It will
;     print numbers and dots to indicate the progress, e.g.
;     Label:  0 .... 25 .... 50 .... 75 .... 100  | Time=00:00:00
;     Also prints an estimated time to completion, HH:MM:SS
;EXAMPLE:
;     progress,0.0,/reset,label='Progress (%)'
;     for i=0,n-1 do begin
;        ; your processing goes here
;        progress,100.*float(i+1.0)/n
;     endfor
;     progress,100.,/last
;     Progress (%):  0 .... 25 .... 50 .... 75 .... 100  | Time=00:00:00
;
;     Or, you can use the minval and maxval keywords to change the
;     range of the counting:
;
;     progress,0.,/reset,label='test',bigstep=128,smallstep=32,maxval=512
;     for i=0,511 do begin
;        wait,0.1
;        progress,i+1
;     endfor
;     progress,512.,/last
;     test:  0 ... 128 ... 256 ... 384 ... 512  | Time=00:00:51
;MODIFICATION HISTORY:
;     T. Metcalf 2005-Jan-06
;     2005-Jan-10 Added frequency keyword.
;     2005-Jan-12 Move time to end of final string so that it does not
;                 move.
;     2005-Jan-13 If /last is set, the time printed is the total elapsed
;                 time.
;     2005-Jan-19 Added minval and maxval keywords.
;     2005-Feb-01 Check for rpercent eq 0 in eta calculation.
;     2005-Feb-11 Remove strcompress around user label.
;-

common progress_private,bigstep,smallstep,lastpercent,starttime,lasttime, $
                        ncharacters,progstring,ttysize,minval,maxval

if n_elements(frequency) GE 1 then freq = float(frequency[0])>0.0 else freq = 0.0

if keyword_set(reset) OR not keyword_set(bigstep) or not keyword_set(smallstep) or $
   n_elements(lastpercent) LE 0 then $
   doreset = 1 else doreset = 0
if n_elements(lastpercent) GT 0 and not keyword_set(doreset) then $
   if lastpercent LT minval then doreset = 1

if keyword_set(doreset)  then begin
   if not keyword_set(bigstepin) then bigstep=25 $
   else bigstep = round(bigstepin)
   if not keyword_set(smallstepin) then smallstep=5 $
   else smallstep = round(smallstepin)
   if n_elements(minvalin) LE 0 then minval = 0.0 else minval = float(minvalin)
   if n_elements(maxvalin) LE 0 then maxval = 100.0 else maxval = float(maxvalin)
   if smallstep GT bigstep then smallstep=bigstep

   if NOT keyword_set(norecurse) then begin ; Get full output string for counting
      progress,100.0,/reset,/last,/noprint,progstring=ps,label=label, $
               /norecurse,/noeta,bigstep=bigstepin,smallstep=smallstepin, $
               minval=minvalin, maxval=maxvalin
      ncharacters = strlen(ps) ; the length of the full progress string
      test = ''
      ; maxch is the longest possible string computed from
      ; ncharacters and the longest time string we're likely need
      maxch =  ncharacters + strlen(' | Time=00000:00:00') 
      ttysize = maxch  ; Maximum required tty width.
      for i=1,maxch-1 do begin 
         ; Figure out how wide the tty is.  There has got to be
         ; a better way to do this!!!
         ; String switches to an array when the tty line would be full.
         test = string('a',test)  ; add one character
         if (size(test))[0] then begin ; scalar string or string array?
            ttysize = i ; if we never get here, the tty is bigger than we need
            break
         endif
      endfor
      ; Better way(?), but works only for some unix flavors,
      ; so do don't want to do this.
      ; The IDL ioctl command could also be used, but it is at least
      ; as system dependent.
      ; spawn,['stty','size'],result,/noshell 
      ; ttysize=splitstr(result,' ')
      ; ttysize = ttysize[n_elements(ttysize)-1]
   endif

   lastpercent = round(minval)-1.0   ; must be exactly round(minval)-1 at the start
   starttime = systime(1)
   lasttime = starttime
   progstring = ''
   if keyword_set(label) then begin
      ;s = strcompress(label+': ')
      s = label+': '
      if NOT keyword_set(noprint) then print,s,format='(a,$)'
      progstring = progstring + s
   endif
endif   ; end of initialization

percent = (float(percentin) < maxval) > minval

if percent LT lastpercent then return 

if keyword_set(last) then npercent = round(maxval) else npercent = round(percent)
nlastpercent = round(lastpercent)

; Print whatever dots and numbers we need to

nprinted = 0
if npercent GT nlastpercent then begin
   for ipct=nlastpercent+1,npercent do begin
      if ipct MOD bigstep EQ 0 then begin
         s = ' '+strcompress(string(ipct),/remove_all)+' '
         if NOT keyword_set(noprint) then print,s,format='(a,$)' 
         progstring = progstring + s
         nprinted = nprinted + strlen(s)
      endif else if ipct MOD smallstep EQ 0 then begin
         s = '.'
         if NOT keyword_set(noprint) then print,s,format='(a,$)'
         progstring = progstring + s
         nprinted = nprinted + strlen(s)
      endif
   endfor
endif

; Print the estimated time left

if (keyword_set(nprinted) OR $
    n_elements(frequency) GE 1 OR $
    keyword_set(last)) AND $
   NOT keyword_set(doreset) AND $ ; no time has passed, can't get eta
   NOT keyword_set(noeta) then begin
   ; Compute & print estimated time to completion
   thistime = systime(1)
   if (thistime - lasttime) GE freq OR $
      keyword_set(nprinted) OR $
      keyword_set(last) then begin
      rpercent = float(percent-minval)/float(maxval-minval)
      if keyword_set(last) OR rpercent EQ 0.0 then $
         eta = (thistime-starttime) $ ; /last -> total elapsed time
      else $
         eta = (1.0-rpercent)*(thistime-starttime)/rpercent
      h = fix(eta / 3600.0)
      eta = eta - h*3600.0
      m = fix(eta/60.0)
      eta = eta - m*60.0
      s = fix(eta)
      if h LT 10 then h = '0'+string(h) else h = string(h)
      if m LT 10 then m = '0'+string(m) else m = string(m)
      if s LT 10 then s = '0'+string(s) else s = string(s)
      eta = ' | Time='+strcompress(h+':'+m+':'+s,/remove_all)
      ; Get number of spaces needed to push time string out to
      ; where the end of the final string will be so it will
      ; not appear to move.
      if keyword_set(ncharacters) then $
         nspace = ncharacters - strlen(progstring) $
      else nspace=0
      if strlen(progstring)+nspace+strlen(eta) GE ttysize then begin
         ; We will eventually go over the end of the line so 
         ; reduce number of spaces.
         nspace = (ttysize-strlen(progstring)-strlen(eta))
      endif
      if nspace LT 0 then begin
         ; We will go over the end of the line with this call,
         ; so we need to erase the last time and move to a new 
         ; line.
         if NOT keyword_set(noprint) then begin
            ; erase the last time and write
            ; enough to get to the next line
            nerase = strlen(eta)+nspace
            if nerase GT 0 then $
              print,string(replicate(32b,nerase)),format='(a,$)'
            progstring = ''  ; starting a new line
         endif
      endif
      if nspace GT 0 then spacestr = string(replicate(32b,nspace)) $
      else spacestr = ''
      ; 8b is a backspace so the eta will be overwritten on next call
      eta = spacestr + eta + $
            string(replicate(8b,strlen(eta)+strlen(spacestr)))
      if NOT keyword_set(noprint) then print,eta,format='(a,$)'
      lasttime = thistime  ; the last time the ETA was printed
   endif
endif

if keyword_set(last) then begin
   if NOT keyword_set(noprint) then print
   lastpercent = minval-1.0  ; force a reset on the next call
endif

pstring = progstring
lastpercent = percent

end