namespace eval smc {
}

source drivers/magfield.tcl

proc stdConfig::smc {} {

# GPIB through Prologix controller
  controller lsc timeout=5 writedelay=0.2
  prop write smc::write
  prop read smc::read

  variable node
  set node $node/tasks
  prop start smc::start

  pollperiod 2 2

  variable name
  magfield_obj SMC_MAGFIELD "smc::cmd /$name/smc"

  kids "SMC magnet power supply" {

    magfield_kids

    node smc rd
    prop read smc::read_gn
    prop label persistent field

    kids "SMC settings" {

      node ramp_slow out
      prop write smc::set_ramp_rate ramp_slow
      default 0.04
      prop help "ramp rate for coils Tesla/min."

      node ramp_fast out
      prop write smc::set_ramp_rate ramp_fast
      default 100
      prop help "ramp rate for leads Tesla/min."

# use MID as set_field
      node set_field out
      prop cmd L
      prop get S
      prop check smc::check_mid

      node at_target upd -int
      prop enum 1

      node heater wr -int
      prop write smc::set_ramp_rate heater
      prop enum 1
      prop cmd H
      prop get J
      prop "persistent switch heater"

      node ramp_state out -int
      prop check smc::chk_ramp_state
      prop write stdSct::complete
      prop enum hold,goto_zero,goto_set

      node leads_set upd
      prop help "calculated current in the leads, converted to Tesla"

      node leads_meas upd
      prop help {measured current in the leads, converted to Tesla}

      node show_internals -int par 1
      prop enum 1
      prop newline 1
      prop show_more 1

      node ramp_amp_sec wr
      prop cmd A
      prop get O

      node pause out -int
      prop enum 1
      prop cmd P
      prop get K

      node ramp_target wr -int
      prop enum go2zero,go2mid,go2max
      prop cmd R
      prop get K

# MAX is always kept as least as high as set_field
      node max wr
      prop cmd U
      prop get S

      node units wr -int
      prop enum amps,tesla
      prop cmd T
      prop get S
      default 1

      node calib out
      prop cmd C
      prop get O

      node heater_voltage out
      prop cmd W
      prop get O

      node volt_limit out
      prop cmd Y
      prop get S

      node xtrip out -int
      prop cmd X
      prop get K

      node volt upd

      node error_code upd -int

      node quench_field upd

      node v_at_lim upd -int
    }
  }
}

proc smc::cmd {node args} {
  set cmd [linsert $args 0 node_cmd $node]
  if {[llength $args] < 2} {
    return [eval $cmd]
  }
  lassign $args var val
  set old [eval [list node_cmd $node $var]]
  set scache [silent $old hgetpropval $node/$var cached_set]
  if {[silent 0 hgetpropval $node/$var cache_state] == 2} {
    set rcache [hgetpropval $node/$var cached_readback]
    if {$rcache != $old || abs($scache - $rcache) > 0.1 * $scache} {
      clientput "$node/$var: $old != $rcache || $scache far $rcache"
      set scache $old
    }
  } else {
    set scache $old
  }
  if {$val != $scache} {
#    clientput "$node: set $var to $val"
    hsetprop $node/$var cached_set $val
    return [eval [list node_cmd $node $var $val]]
  }
  return $old
}

proc smc::start {} {
  sct send  "++addr"
  return smc::start1
}

proc smc::start1 {} {
  sct send  "O\n++read"
  sct cnt 0
  return smc::start2
}

proc smc::start2 {} {
  set id 0
  regexp {A.{19}(C.*)} [sct result] -> id
  if {$id eq "0"} {
    sct cnt [expr [sct cnt] + 1]
    if {[sct cnt] < 5} {
      sct send  "O\n++read"
      return smc::start2
    }
    error "[sct] bad response from SMC: [sct result]"
  }
  sct send  "T1\n++ver"
  return "smc::start3 $id"
}

proc smc::start3 {id} {
  clientput [sct result]
  sct result $id
  return [stdSct::completeStart]
}

proc smc::write {} {
  update_field
  sct send "[sct cmd][sct target]\n++auto"
  sct update [sct target]
  update_field
  sct cache_state 1
  return "smc::read 1"
}

proc smc::complete {} {
  sct cache_state 1
clientput "complete [sct]"
  return idle
}

proc smc::check_mid {} {
  if {[sct target] > [hvali [sct parent]/max]} {
    hset [sct parent]/max [sct target]
  }
}

proc smc::read {{from_write 0}} {
  sct send "[sct get]\n++auto"
  sct from_write $from_write
  return smc::update0
}

proc smc::update0 {} {
  sct send "++read eoi"
  return smc::update
}

proc smc::read_gn {} {
  if {[hvali [sct]/units] == 0} {
    sct get G
  } else {
    sct get N
  }
  return [smc::read]
}

proc smc::eat_rubbish {} {
  sct send "++read"
  return stdSct::complete
}

proc smc::update_field {} {
  set op [sct objectPath]
  magfield::simleads [hgetpropval $op script] pf ls
  hupdate $op/smc/leads_set $ls
  if {[hval $op/smc/heater]} {
    hupdate $op/smc $ls
  }
}

proc smc::updateit {node val} {
  updateval $node $val
  switch [silent 0 hgetpropval $node cache_state] {
    1 {
      set t [hgetpropval $node target]
      if {$t != $val} {
        clientput "$node set to $val (target [hgetpropval $node target])"
      } else {
        clientput "$node set to $val"
      }
      hsetprop $node cached_readback $val
      hsetprop $node cache_state 2
      return
    }
    2 {
      if {[hgetpropval $node cached_readback] != $val} {
        hsetprop $node cache_state 0
      }
    }
  }
}

proc smc::update {} {
  set r [sct result]
  set op [sct objectPath]/smc
  if {[regexp {T(.)U(.{7})L(.{7})Y(.{4})} $r -> t u l y]} {
    set tst S
    updateit $op/units $t
    updateit $op/max $u
    updateit $op/set_field $l
    updateit $op/volt_limit $y
  } elseif {[regexp {(F|I)(.{8})H(.)} $r -> fi f h]} {
    set tst J
    set pf [hvali $op]
    if {$h == 0} {
      if {$pf != $f} {
        clientput "persistent $f"
      }
      updateit $op $f
    } elseif {[catch {hval $op}]} {
      updateit $op $pf
    }
    updateit $op/heater $h
  } elseif {[regexp {(F|I)(.{8})V(.{4})R(.)(A|V)} $r -> fi f v r av]} {
    set tst (G|N)
    updateit $op/leads_meas $f
    updateit $op/volt $v
    if {$av eq "A"} {
      updateit $op/v_at_lim 0
    } else {
      updateit $op/v_at_lim 1
    }
  } elseif {[regexp {R(.)M(.)P(.)X(.)H(.)Z0.00E(..)Q(.{8})} $r -> r m p x h e q]} {
    set tst K
    updateit $op/ramp_target $r
    updateit $op/pause $p
    if {$p} {
      updateit $op/ramp_state 0
    } else {
      updateit $op/at_target $m
      incr r
      if {$r > 2} {
        set r 2
      }
      updateit $op/ramp_state $r
    }
    updateit $op/xtrip $x
    updateit $op/error_code $e
    updateit $op/quench_field $q
  } elseif {[regexp {A(.{8})D.T(.)B0W(.{4})C(.{8})} $r -> a t w c]} {
    set tst O
    updateit $op/ramp_amp_sec $a
    set af [format %.3g [expr $a * $c * 60]]
    set rf [hvali $op/ramp_fast]
    set rs [hvali $op/ramp_slow]
    set h [hvali $op/heater]
    set am [expr sqrt($rf * $rs)]
    if {$af > $am && $rf > $rs && $h == 0} {
      updateit $op/ramp_fast $af
    }
    if {$af < $am && $rf > $rs && $h == 1} {
      updateit $op/ramp_slow $af
    }
    updateit $op/units $t
    updateit $op/heater_voltage $w
    updateit $op/calib $c
  } else {
    set tst [sct get]
    clientlog "unknown response: [sct get] $r"
  }
  catch {update_field}
  if {![regexp $tst [sct get]]} {
    [sct controller] queue [sct] write smc::eat_rubbish
  }
  return idle
}

proc smc::ramp_lim {field} {
  set oldf 0
  set oldr 99999
  set result 0
  foreach {r f} [hval [sct objectPath]/profile] {
    if {$r > $oldr} {
      error "ERROR: in ramp profile, ramps must be decreasing"
    }
    if {$r == 0} {
      error "ERROR: in ramp profile, ramps must be > 0"
    }
    set oldr $r
    if {$f < $oldf} {
      error "ERROR: in ramp profile, fields must be increasing"
    }
    set oldf $f
    if {$result == 0 && $f >= $field} {
      set result $r
    }
  }
  if {$result == 0} {
    error "ERROR: field too high"
  }
  return $result
}

proc smc::set_ramp_rate {target_name} {
  foreach var {heater ramp_slow ramp_fast} {
    if {$target_name eq $var} {
      set $var [sct target]
      sct update [sct target]
    } else {
      set $var [hval [sct parent]/$var]
    }
  }
  if {$heater} {
    set r $ramp_slow
  } else {
    set r $ramp_fast
  }
  set calib [hval [sct parent]/calib]
  if {$calib == 0} {
    set calib 1
  }
  set rr [expr $r / 60.0 / $calib]
  set rr [format %.3g $rr]
  hset [sct parent]/ramp_amp_sec $rr
  if {$target_name eq "heater"} {
    return smc::write
  }
  sct cache_state 1
  return idle
}

proc smc::chk_ramp_state {} {
  set pr [sct parent]
  switch -- [sct target] {
    0 {
      clientput "set pause to 1"
      clientput "hset $pr/pause 1"
      clientput "pause set to 1"
      hset $pr/pause 1
    }
    1 {
      hset $pr/pause 0
      hset $pr/ramp_target 0
    }
    2 {
      hset $pr/pause 0
      hset $pr/ramp_target 1
    }
    default {
      error "illegal ramp_target: [sct target]"
    }
  }
}
