#----------------------------------------------------------------------------
# Scan command implementation for TOPSI
# Test version, Mark Koennecke, February 1997
#----------------------------------------------------------------------------
set home /data/koenneck/src/sics/tcl
set datapath /data/koenneck/src/sics/tmp
set recoverfil /data/koenneck/src/sics/recover.dat

bpOn

source $home/utils.tcl
source $home/base.tcl
source $home/inherit.tcl
source $home/obtcl.tcl
#-------------------------- some utility functions -------------------------
proc MC { t n } {
     set string $t
     for { set i 1 } { $i < $n } { incr i } {
       set string [format "%s%s" $string $t]
     }
     return $string
}
#--------------------------------------------------------------------------
proc GetNum { text } {
	set list [split $text =]
        return [lindex $list 1]
}
#-------------------------- String list for writing ------------------------  
class DataSet
DataSet method init { } {
   instvar N
   instvar Data
   next
   set Data(0) " Bla"
   set N 0
}

DataSet method add { text } {
  instvar N
  instvar Data
  set Data($N) $text
  incr N
}

DataSet method ins { text i } {
   instvar Data
   instvar N
   if { $i >= $N } {
     set N [expr $i + 1]
   } else {
      unset Data($i)
   }
   set Data($i) $text
}
DataSet method put { file } {
  instvar Data
  instvar N

  for { set i 0 } { $i < $N } { incr i } {
    puts $file $Data($i)
  }
} 

DataSet method clear { } {
   instvar Data
   instvar N
   unset Data
   set Data(0) "Bla"
   set N 0
}
DataSet method GetN { } {
  instvar N
  return $N
}
 
#---------------------------------------------------------------------------
# scan class initialization
class ScanCommand

ScanCommand method init { counter } {
  instvar ScanData
  instvar [DataSet new Data]
  instvar Active
  instvar Recover 
  next
  set ScanData(Mode) Timer
  set ScanData(NP) 1
  set ScanData(counter) $counter
  set ScanData(NoVar) 0
  set ScanData(Preset) 10.
  set ScanData(File) Default.dat
  set ScanData(Counts) " "
  set ScanData(cinterest) " "
  set ScanData(pinterest) " "
  set Active 0
  set Recover 0
}
#-------------add scan variables---------------------------------------------
ScanCommand method var { name start step } {
	instvar ScanData
        instvar ScanVar
        instvar Active 
# check for activity
        if {$Active} {
          ClientPut "ERROR: cannot change parameters while scanning" error
          return 
        }
# check parameters
        set t [SICSType $name]
        if { [string compare $t DRIV] != 0 } {
          ClientPut [format "ERROR: %s is not drivable" $name] error
          return 0
        }
        set t [SICSType $start]
        if { [string compare $t NUM] != 0 } {
          ClientPut [format "ERROR: %s is no number!" $start] error
          return 0
        }
        set t [SICSType $step]
        if { [string compare $t NUM] != 0 } {
          ClientPut [format "ERROR: %s is no number!" $step] error
          return 0
        }
# install the variable
        set i $ScanData(NoVar)
        set ScanData(NoVar) [incr ScanData(NoVar)]
        set ScanVar($i,Var) $name
        set ScanVar($i,Start) $start
        set ScanVar($i,Step)  $step 
        set ScanVar($i,Value) " "
        $self SendInterest pinterest ScanVarChange
        ClientPut OK
}
#---------------------- getvars ------------------------------------------
ScanCommand method getvars {} {
      instvar ScanData
      instvar ScanVar
      set list ""
      for {set i 0} { $i < $ScanData(NoVar) } { incr i} {
       lappend list $ScanVar($i,Var)
      }
      return [format "scan.Vars = %s -END-"  $list]
}
#------------------------------------------------------------------------
ScanCommand method xaxis {} {
      instvar ScanData
      instvar ScanVar
      if { $ScanData(NoVar) <= 0} {
#---- default Answer
        set t [format "%s.xaxis = %f %f" $self 0 1]
      } else {
        set t [format "%s.xaxis = %f %f" $self $ScanVar(0,Start) \
              $ScanVar(0,Step)]
      }
      ClientPut $t
}
#--------------------- modvar --------------------------------------------
ScanCommand method modvar {name start step } {
      instvar ScanData
      instvar ScanVar
      for {set i 0} { $i < $ScanData(NoVar) } { incr i} {
        if { [string compare $name $ScanVar($i,Var)] == 0} {
           set t [SICSType $start]
           if { [string compare $t NUM] != 0 } {
             ClientPut [format "ERROR: %s is no number!" $start] error
             return 0
            }
            set t [SICSType $step]
            if { [string compare $t NUM] != 0 } {
                ClientPut [format "ERROR: %s is no number!" $step] error
                return 0
             }
#-------- do it
             set ScanVar($i,Start) $start
             set ScanVar($i,Step)  $step
             return OK           
        }
      }
      error [format "Scan Variable %s NOT found" $name]
}
#----------------- interests ----------------------------------------------
ScanCommand method cinterest {} {
    instvar ScanData
    set nam [GetNum [config MyName]]
    lappend ScanData(cinterest) $nam
}
#--------------------------------------------------------------------------
ScanCommand method pinterest {} {
    instvar ScanData
    set nam [GetNum [config MyName]]
    lappend ScanData(pinterest) $nam
}
#-------------------------------------------------------------------------
ScanCommand method SendInterest { type text } {
    instvar ScanData
#------ check list first
    set l1 $ScanData($type)
    set l2 ""
    foreach e $l1 {
      set b [string trim $e]
      set g [string trim $b "{}"]
      set ret [SICSType $g]
      if { [string first COM $ret] >= 0 } {
        lappend l2 $e
      } 
    }  
#-------- update scan data and write
    set ScanData($type) $l2
    foreach e $l2 {
      set b [string trim $e]
      $b put $text
    }  
}
#---------------- Change Mode ----------------------------------------------
ScanCommand method Mode { {NewVal NULL } } {
	instvar ScanData
        instvar Active 
        if { [string compare $NewVal NULL] == 0  } {
             set val [format "%.Mode = %s" $self $ScanData(Mode)]
             ClientPut $val
             return $val
        } else {
# check for activity
          if {$Active} {
            ClientPut "ERROR: cannot change parameters while scanning" error
            return 
          }
          if { ([string compare $NewVal "Timer"] == 0) || \
               ([string compare $NewVal Monitor] ==0) } {
               set ScanData(Mode) $NewVal
               ClientPut OK
          } else {
            ClientPut [format "ERROR: %s not recognized as ScanMode" $NewVal]
          }
        }  
} 
#----------------------------- NP -------------------------------------------
ScanCommand method NP { { NewVal NULL } } {
     instvar ScanData
     instvar Active
     if { [string compare $NewVal NULL] == 0 } {
       set val [format "%s.NP = %d" $self $ScanData(NP)]
       ClientPut $val
       return $val
     } else {
# check for activity
       if {$Active} {
            ClientPut "ERROR: cannot change parameters while scanning" error
            return 
       }
       set t [SICSType $NewVal]
       if { [string compare $t NUM] != 0 } {
         ClientPut [format "ERROR: %s is no number" $NewVal] error
         return
       }
       set ScanData(NP) $NewVal
       ClientPut OK
     }
}
#------------------------------ Preset ------------------------------------
ScanCommand method Preset { {NewVal NULL} } {
     instvar ScanData
     instvar Active
     if { [string compare $NewVal NULL] == 0 } {
       set val [format "%s.Preset = %f" $self $ScanData(Preset)]
       ClientPut $val
       return $val
     } else {
# check for activity
       if {$Active} {
            ClientPut "ERROR: cannot change parameters while scanning" error
            return 
       }
       set ScanData(Preset) $NewVal
       set t [SICSType $NewVal]
       if { [string compare $t NUM] != 0} {
         ClientPut [format "ERROR: %s is no number" $NewVal] error
         return
       }
       ClientPut OK
     }
}  
#------------------------------ File ------------------------------------
ScanCommand method File { {NewVal NULL} } {
     instvar ScanData
     if { [string compare $NewVal NULL] == 0 } {
       set val [format "%s.File = %s" $self $ScanData(File)]
       ClientPut $val
       return $val
     } else {
       set ScanData(File) $NewVal
       ClientPut OK
     }
}
#--------------------------- Count ---------------------------------------
# These and the commands below are for use in recovery only
ScanCommand method RecoCount { val } {
   instvar Recover
   instvar ScanData
   if { ! $Recover } {
     ClientPut \
     "ERROR: This command may only be used in Recovery Operations" \
     error 
     return
   }
   set ScanData(Counts) $val
}  
#--------------------------- monitor -------------------------------------
ScanCommand method RecoMonitor { val } {
   instvar Recover
   instvar ScanData
   if { ! $Recover } {
     ClientPut \
     "ERROR: This command may only be used in Recovery Operations" \
     error
     return
   }
   set ScanData(Monitor) $val
}  
#--------------------------- var  -------------------------------------
ScanCommand method RecoVar { var val } {
   instvar Recover
   instvar ScanData
   instvar ScanVar
   if { ! $Recover } {
     ClientPut \
     "ERROR: This command may only be used in Recovery Operations" \
     error
     return
   }
   set ScanVar($var,Value) $val
}  
#--------------------------- WriteRecover --------------------------------
ScanCommand method WriteRecover { } {
   instvar ScanData
   instvar ScanVar
   global recoverfil

   set fd [open $recoverfil w]
   puts $fd [format "%s Preset %s " $self $ScanData(Preset)]
   puts $fd [format "%s Mode  %s "  $self $ScanData(Mode)]
   puts $fd [format "%s NP    %s "  $self $ScanData(NP)]
   puts $fd [format "%s File  %s "  $self $ScanData(File)]
   for { set i 0 } { $i < $ScanData(NoVar) } { incr i } {
       puts $fd [format "%s var %s %s %s" $self $ScanVar($i,Var) \
               $ScanVar($i,Start) $ScanVar($i,Step)]
       puts $fd [format "%s RecoVar %d %s" $self $i [list $ScanVar($i,Value)]]
   } 
   puts $fd [format "%s RecoCount %s" $self [list $ScanData(Counts)]]
   puts $fd [format "%s RecoMonitor %s" $self [list $ScanData(Monitor)]]
   close $fd
}
#-------------------------- list ------------------------------------------
ScanCommand method list { } {
  instvar ScanData
  instvar ScanVar
  ClientPut [format "%s.Preset = %f" $self $ScanData(Preset)]
  ClientPut [format "%s.Mode = %s" $self $ScanData(Mode)]
  ClientPut [format "%s.File = %s" $self $ScanData(File)]
  ClientPut [format "%s.NP   = %d" $self $ScanData(NP)]
  ClientPut "ScanVariables:"
  for { set i 0 } {$i < $ScanData(NoVar) } { incr i } {
     ClientPut [format " %s %f %f" $ScanVar($i,Var) $ScanVar($i,Start) \
                $ScanVar($i,Step)]
  }
}
#--------------------------------- clear ---------------------------------
ScanCommand method clear { } {
   instvar ScanData
   instvar ScanVar
   instvar Data
   instvar Active
# check for activity
   if {$Active} {
      ClientPut "ERROR: cannot clear running scan" error
      return 
   }

   set ScanData(NP) 0
   set ScanData(NoVar) 0
   set ScanData(Counts) " "
   set ScanData(Monitor) " "
   Data clear
   $self SendInterest pinterest ScanVarChange
   ClientPut OK
}
#--------------------------- Store Initial data -----------------------------
ScanCommand method SaveHeader { } {
     instvar Data
     instvar ScanData 
     instvar ScanVar
     Data clear
# administrative header
     Data add [format "%s TOPSI Data File %s" [MC * 30] \
        [MC * 30]]
     Data add [Title]
     Data add [User]
     Data add [format "File created: %s" [sicstime]]
     Data add [MC * 75]
     Data add [format " %s Setting %s " [MC * 30] [MC * 30]]
# settings of instrument variables
     Data add [format "%s Monochromator %s" [MC - 30] [MC - 30]]
     Data add [lambda]
     Data add [MTL position]
     Data add [MTU position]
     Data add [MGU position]
# diaphragm should go here
# sample info
     Data add [format "%s Sample %s" [MC - 30] [MC - 30]]
     Data add [STL position]
     Data add [STU position]
     Data add [SGL position]
     Data add [SGU position]
     Data add [MC * 75]
# counter info
     Data add [format "CountMode = %s" $ScanData(Mode)]
     Data add [format "Count Preset = %s" $ScanData(Preset)]
     Data add [MC * 75]
     Data add [format "%s DATA %s" [MC * 30] [MC * 30]]
     set val "Variables scanned: "
     for  { set i 0 } { $i < $ScanData(NoVar) } { incr i} {
       append val " " $ScanVar($i,Var)
     }
     Data add "$val"
     append t [LeftAlign NP 5]
     append t [LeftAlign Counts 12]
     for { set i 0 } { $i < $ScanData(NoVar) } { incr i} {
          append t [LeftAlign $ScanVar($i,Var) 10]
     }
     Data add $t
     set ScanData(Ptr) [Data GetN]
}
#-----------------------------------------------------------------------------
ScanCommand method ConfigureDevices { } {
   instvar ScanData
   $ScanData(counter) SetMode $ScanData(Mode)
   $ScanData(counter) SetPreset $ScanData(Preset)   
}
#----------------------------------------------------------------------------
ScanCommand method StoreScanPoint { } {
   instvar ScanData
   instvar Data
   instvar ScanVar
   lappend ScanData(Counts)  [GetNum [$ScanData(counter) GetCounts]]
   lappend ScanData(Monitor) [GetNum [$ScanData(counter) GetMonitor 1]]
#------------ get Scan Var Values
   for { set i 0 } { $i < $ScanData(NoVar) } { incr i } {
     lappend ScanVar($i,Value) [GetNum [$ScanVar($i,Var) position]]
   }
   set iFile $ScanData(Ptr)
#------------ write it
   set length [llength $ScanData(Counts)]
   for { set i 0 } { $i < $length} { incr i} {
      set t " " 
      append t [LeftAlign $i 5]
      append t [LeftAlign [lindex $ScanData(Counts) $i ] 12]
      for { set ii 0 } { $ii < $ScanData(NoVar) } { incr ii} {
         append t [LeftAlign [lindex $ScanVar($ii,Value) $i] 10]
      }
      Data ins $t $iFile
      incr iFile
   }
   set fd [open $ScanData(File) w]   
   Data put $fd
   close $fd 
}
#--------------------------------------------------------------------------
ScanCommand method GetCounts { } {
    instvar ScanData
    #------- get data available
    set length [llength $ScanData(Counts)]
    for { set i 0 } { $i < $length } { incr i} {
        lappend result [lindex $ScanData(Counts) $i]
    }
    #------ put zero in those which are not yet measured
    if { $length < $ScanData(NP) } {
      for { set i $length } { $i < $ScanData(NP) } { incr i } {
        lappend result 0
      } 
    }
    return "scan.Counts= $result"
}
#---------------------------------------------------------------------------
ScanCommand method EndScan { } {
  instvar Data
  instvar ScanData
  instvar ScanVar
  Data add [format "%s End of Data %s" [MC * 30] [MC * 30]]
  set fd [open $ScanData(File) w]   
  Data put $fd
  close $fd 
}
#-------------------------------------------------------------------------
ScanCommand method EvalInt { } {
   set int [GetInt]
   ClientPut [format "Interrupt %s detected" $int]
   switch -exact $int {
      continue {
                return OK
               }
      abortop  {
               SetInt continue
               return SKIP
               }
      abortscan {
                  SetInt continue
                  return ABORT
               }
      default {
                 return ABORT
              }
   }
}
#--------------------------------------------------------------------------
ScanCommand method DriveTo { iNP } {
   instvar ScanData
   instvar ScanVar
   set command "drive "
   for { set i 0 } { $i < $ScanData(NoVar) } { incr i } {
     set ScanVar($i,NewVal) [expr $ScanVar($i,Start) + $iNP * \
                             $ScanVar($i,Step)]
#     append ScanVar($i,Value) " " $ScanVar($i,NewVal)
     append command " " $ScanVar($i,Var) " " $ScanVar($i,NewVal)
   }
   set ret [catch {eval $command } msg ]
   if { $ret != 0 } {
      ClientPut $msg error
      return [$self EvalInt]
   }
   return OK   
}
#------------------------------------------------------------------------
ScanCommand method CheckScanBounds { } {
   instvar ScanData
   instvar ScanVar
   for { set i 0} { $i < $ScanData(NP) } { incr i } {
       for { set ii 0 } { $ii < $ScanData(NoVar) } { incr ii } {
          set NewVal [expr $ScanVar($ii,Start) + $i*$ScanVar($ii,Step)]
          set iRet [catch {SICSBounds $ScanVar($ii,Var) $NewVal} msg]
          if { $iRet != 0 } {
            ClientPut $msg error
            return 0
          }
       }
   }
   return 1
}
#-------------------------------------------------------------------------
ScanCommand method Count { } {
  instvar ScanData
  set command $ScanData(counter)
  append command " Count "
  append command $ScanData(Preset)
  set ret [catch {eval $command } msg ]
  if { $ret != 0 } {
      ClientPut $msg  error
      return [$self EvalInt]
  }   
  return OK
}
#-------------------------------------------------------------------------
proc LeftAlign { text iField } {
   set item $text
   append item [MC " " $iField]
   return [string range $item 0 $iField]
}
#-------------------------------------------------------------------------
ScanCommand method ScanStatusHeader { } {
  instvar ScanData
  instvar ScanVar
  append t [LeftAlign NP 5]
  append t [LeftAlign Counts 12]
  for { set i 0 } { $i < $ScanData(NoVar) } { incr i} {
    append t [LeftAlign $ScanVar($i,Var) 10]
  }
  ClientPut $t status
}
#------------------------------------------------------------------------
ScanCommand method ProgressReport { i } {
   instvar ScanData
   instvar ScanVar
   $self ScanStatusHeader
   append t [LeftAlign $i 5]
   append t [LeftAlign [lindex $ScanData(Counts) $i ] 12]
  for { set i 0 } { $i < $ScanData(NoVar) } { incr i} {
    append t [LeftAlign $ScanVar($i,NewVal) 10]
  }
  ClientPut $t status
}
#-------------------------------------------------------------------------
ScanCommand method MakeFile { } {
   global datapath
   instvar ScanData
   SicsDataNumber incr
   set num1 [SicsDataNumber]
   set num [GetNum $num1]
   set fil [ format "%s/topsi%4.4d%2.2d.dat" $datapath  $num 97]
   set ScanData(File) $fil
}

#--------------------------------------------------------------------------
ScanCommand method run { } {
  instvar ScanData
  instvar Data
  instvar ScanVar
  instvar Active
# start with error checking
  if { $ScanData(NP) < 1 } {
    ClientPut "ERROR: Insufficient Number of ScanPoints"
    return
  }
  if { $ScanData(NoVar) < 1 } {
     ClientPut "ERROR: No variables to scan given!"
     return
  }
#-------  check for activity
       if {$Active} {
            ClientPut "ERROR: Scan already in progress" error
            return 
       }
#------- check Bounds 
  if { [$self CheckScanBounds] != 1 } {
     return 
  }

# clean data space from relicts of previous scans
  Data clear
  set ScanData(Counts) " "
  set ScanData(Monitor) " "
  for {set i 0} { $i < $ScanData(NoVar) } { incr i } {
    set ScanVar($i,Value) " "
  }

# configure and save data header
  $self ConfigureDevices
  $self MakeFile
  $self SaveHeader
  ClientPut [format "Writing %s" $ScanData(File)]


# the actual scan loop
  SetStatus Scanning
  $self SendInterest cinterest NewScan
  set Active 1
  for { set i 0 } { $i < $ScanData(NP) } { incr i } {
#---- driving
      set ret [$self DriveTo $i]
      switch -exact $ret {
         OK { }
         SKIP { continue }
         ABORT { ClientPut "\nERROR: Scan Aborted at drive"
                 SetStatus Eager
                 set Active 0
                 error "Abort" 
         }
      }
#---- counting
      set ret [$self Count]
      switch -exact $ret {
         OK { }
         SKIP { continue }
         ABORT { ClientPut "\nERROR: Scan Aborted at counting"
                 SetStatus Eager
                 set Active 0
                 error "Abort"
         }
      }
#--- save data
      $self StoreScanPoint
      $self WriteRecover
#--- invoke interests
      $self SendInterest cinterest [$self GetCounts]
#--- Status Report
      $self ProgressReport $i
  }
#---- final processing
  $self EndScan
  ClientPut "OK"  
  SetStatus Eager
  set Active 0
}
#--------------------------------------------------------------------------
ScanCommand method Recover { } {
  instvar ScanData
  instvar Data
  instvar ScanVar
  instvar Active
  instvar Recover
  global recoverfil

# ---- read Recover Information
  set Recover 1
  $self clear
  source $recoverfil

# configure and save data header
  $self ConfigureDevices
  $self SaveHeader

# Write scan start info
  $self ScanStatusHeader

# --- figure out where we are
  set Recover 0
  set pos [llength $ScanData(Counts)]
 
# ----------------------the actual scan loop
  set OldStat [status]
  SetStatus Scanning
  set Active 1
  for { set i $pos } { $i < $ScanData(NP) } { incr i } {
#---- driving
      set ret [$self DriveTo $i]
      switch -exact $ret {
         OK { }
         SKIP { continue }
         ABORT { ClientPut "\nERROR: Scan Aborted"
                 SetStatus $OldStat
                 set Active 0
                 return
         }
      }
#---- counting
      set ret [$self Count]
      switch -exact $ret {
         OK { }
         SKIP { continue }
         ABORT { ClientPut "\nERROR: Scan Aborted"
                 SetStatus $OldStat
                 set Active 0
                 return
         }
      }
#--- save data
      $self StoreScanPoint
      $self WriteRecover
#--- Status Report
      $self ProgressReport $i
  }
#---- final processing
  $self EndScan
  ClientPut "OK"  
  SetStatus $OldStat
  set Active 0
  
}
#---------------------------------------------------------------------------
# finally initialise the scan command
ScanCommand new scan counter
#---------------------------------------------------------------------------
# a new user command which allows status clients to read the counts in a scan
# This is just to circumvent the user protection on scan
proc ScanCounts { } {
     set status [ catch {scan GetCounts} result]
     if { $status == 0 } {
       return $result
     } else {
       return "scan.Counts= 0"
     }
}
#---------------------------------------------------------------------------
# This is just another utilility function which helps in implementing the
# status display client
proc TextStatus { } {
   set text [status]
   return [format "Status = %s" $text]
}
#---------------------------------------------------------------------------
# Dumps time in a useful format
proc sftime {} {
    return [format "sicstime = %s" [sicstime]]
}
