namespace eval ipsmag {
}

source drivers/magfield.tcl

proc stdConfig::ipsmag {{n_of_slaves 3}} {

  controller std
  prop write ipsmag::write
  prop read ipsmag::read
  prop update ipsmag::update
  prop startcmd *IDN?

  variable node
  set node $node/tasks
  prop complete ipsmag::completeStart

  variable name
  magfield_obj IPS_MAGFIELD "ipsmag::cmd /$name/ips"
  #magfield_obj IPS_MAGFIELD "node_cmd /$name/ips"

  kids "magnetic field control" {

    # 1: bipolar
    magfield_kids 1

    node ips rd
    prop adr DEV:GRPZ:PSU:SIG:PFLD
    prop label persistent field

    kids "IPS settings" {

      node ramp_slow wr
      prop adr DEV:GRPZ:PSU:SIG:RFST
      default 0.1
      prop help "ramp rate for coils Tesla/min."

      node ramp_fast upd
      default 100
      prop help "ramp rate for leads Tesla/min."

      node set_field wr
      prop adr DEV:GRPZ:PSU:SIG:FSET
      prop write ipsmag::write_set

      node heater wr -text
      prop enum 1
      prop adr DEV:GRPZ:PSU:SIG:SWHT
      prop cvt "ON=1 OFF=0"
      prop label "persistent switch heater"
      prop update ipsmag::update_heater

      node ramp_state wr -int
      prop enum hold=0,to_zero=1,to_set=2,clamp=3
      prop adr DEV:GRPZ:PSU:ACTN
      prop cvt "RTOZ=1 RTOS=2 CLMP=3 HOLD=0"
      prop read ipsmag::read_ramp_state

      node leads_set rd 1
      prop adr DEV:GRPZ:PSU:SIG:FLD
      prop help "calculated current in the leads, converted to Tesla"

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

      node leads_meas rd
      prop adr DEV:GRPZ:PSU:SIG:CURR
      prop help {measured current in the leads, converted to Tesla}
      prop update ipsmag::update_leads $n_of_slaves

      for {set i 1} {$i <= $n_of_slaves} {incr i} {
          node slave$i upd
      }

      node volt rd
      prop adr DEV:GRPZ:PSU:SIG:VOLT

      node symode -text upd
      default "correct"

      node engineering_password wr -text
      default ""
      prop adr SYS:USER:ENG
      prop write ipsmag::write_eng
      prop read ipsmag::read_eng
      prop update ipsmag::update_eng

      node atob wr
      prop help {Amp/Tesla}
      prop adr DEV:GRPZ:PSU:ATOB
      prop write ipsmag::write_config
      prop update ipsmag::update_config
      prop check ipsmag::check_config

      node inductance wr
      prop help {henries}
      prop adr DEV:GRPZ:PSU:IND
      prop write ipsmag::write_config
      prop update ipsmag::update_config
      prop check ipsmag::check_config

      node switch_heater_current wr
      prop help {switch heater current [mA]}
      prop adr DEV:GRPZ:PSU:SHTC
      prop write ipsmag::write_config
      prop update ipsmag::update_config
      prop check ipsmag::check_config

    }
  }
}

proc ipsmag::completeStart {{try 3}} {
  if {![string match "IDN:OXFORD INSTRUMENTS:MERCURY*" [sct result]]} {
     if {$try > 0} {
        sct send "*IDN?"
        incr try -1
        clientput "[sct sicsdev]: bad IDN: [sct result]"
        clientput "[sct sicsdev]: try again"
        return "ipsmag::completeStart $try"
     }
     clientput "[sct sicsdev]: bad IDN: [sct result]"
     return unpoll
  }
  return [stdSct::completeStart]
}

proc ipsmag::cmd {node args} {
  set cmd [linsert $args 0 node_cmd $node]
  if {[llength $args] < 2} {
    return [eval $cmd]
  }
  lassign $args var val
  if {[silent 0 hgetpropval $node/$var redo]} {
    hsetprop $node/$var redo 0
    set old -1
  } else {
    set old [eval [list node_cmd $node $var]]
  }
  set tar [silent $old hgetpropval $node/$var target]
  if {$val != $old || $val != $tar} {
    #clientlog "CMD $node $args ($old -> $val)"
    return [eval [list node_cmd $node $var $val]]
  }
  # clientlog "KEEP $node $args"
  return $old
}

proc ipsmag::write {} {
  set val [sct target]
  set cvt [silent "" sct cvt]
  if {$cvt ne ""} {
    set done 0
    foreach item $cvt {
      lassign [split $item =] key value
      if {$value eq $val} {
        set val $key
        set done 1
        break
      }
    }
    if {!$done} {
      set val $key
    }
  }
  sct send "SET:[sct adr]:$val"
  return "ipsmag::read 1"
}

proc ipsmag::write_set {} {
  if {abs([silent 99999 hval [sct]] - [sct target]) > 1e-6} {
    if {[hval [sct parent]/ramp_state] == 2} {
      # we are in ramp_state "to_set" and the target changed while running
      # to a setpoint. We have to switch the ramp_state to hold. This will
      # remind the IPS that the setpoint has changed really! 
      # ramp_state will be switched to "to_set" again by the magfield script
      sct send SET:DEV:GRPZ:PSU:ACTN:HOLD
      clientput "setpoint changed: stop and restart ramp"
      return ipsmag::write
    }
  }
  return [ipsmag::write]
}

proc ipsmag::write_eng {} {
  if {[sct target] eq ""} {
    sct send SET:SYS:USER:NORM
  } else {
    sct send "SET:SYS:USER:ENG:[sct target]"
    sct changed_password [DoubleTime]
  }
  return ipsmag::confirm_eng
}

proc ipsmag::confirm_eng {} {
  if {[string match {*:DENIED} [sct result]]} {
    sct print "ERROR: invalid password"
    return idle
  }
  sct send READ:SYS:USER
  return ipsmag::update_eng
}

proc ipsmag::read_eng {} {
  if {[silent 0 sct changed_password] ne "0"} {
    if {[DoubleTime] > [sct changed_password] + 60} {
      sct changed_password 0
      sct send SET:SYS:USER:NORM
      return ipsmag::confirm_eng
    }
  }
  sct send READ:SYS:USER
  return ipsmag::update_eng
}

proc ipsmag::update_eng {} {
  if {[ipsmag::cvt] eq "ENG"} {
    sct update "***"
  } else {
    sct update ""
  }
  return idle
}

proc ipsmag::read {{from_write 0}} {
  # TODO: split this into ipsmag::confirm and ipsmag::read
  if {$from_write} {
    if {![string match {*:VALID} [sct result]]} {
      # show invalid messsage only when target value is different than actual
      set tar [sct target]
      set val [silent $tar hval [sct]]
      if {abs($val - $tar) > abs($val + $tar) / 20000.} {
        hdelprop [sct] requested
        error "[sct]: [sct result]"
      }
    }
    sct update [sct target]  ;# do we need this? should be done in update script ...
  }
  sct send "READ:[sct adr]"
  return update
}

proc ipsmag::cvt {} {
  set val [lindex [split [sct result] :] end]
  set cvt [silent "" sct cvt] 
  if {$cvt ne ""} {
    set done 0
    foreach item $cvt {
      lassign [split $item =] key value
      if {$key eq $val} {
        set val $value
        set done 1
        break
      }
    }
    if {!$done} {
      clientput "[sct] can not parse '[sct result]', use '$value' instead"
      set val $value
    }
  } else {
    scan $val %f val
  }
  return $val
}

proc ipsmag::update {} {
  sct update [ipsmag::cvt]
  return idle
}

proc ipsmag::update_heater {} {
  sct update [ipsmag::cvt]
  if {[hval [sct]]} {
    hsetprop [sct parent] adr DEV:GRPZ:PSU:SIG:FLD
  } else {
    hsetprop [sct parent] adr DEV:GRPZ:PSU:SIG:PFLD
  }
  return idle
}

proc ipsmag::update_leads {{n_of_slaves 3}} {
  set current [ipsmag::cvt]
  catch {
    sct update [format %.6f [expr $current / [hval [sct parent]/atob]]]
  } msg
  if {$n_of_slaves} {
    set currents [string trim [silent "" sct currents]]
    if {$currents ne ""} {
      # check currents
      set tol [expr abs($current - [sct last_current]) * 1.2 + 0.06]
      set mincur [format %.2f [expr ($current - $tol) / $n_of_slaves]]
      set maxcur [format %.2f [expr ($current + $tol) / $n_of_slaves]]
      set bad 0
      foreach cur $currents {
        if {$cur < $mincur || $cur > $maxcur} {
          set bad 1
        }
      }
      set status [hval [sct objectPath]/status]
      if {$bad} {
        set msg "slave currents ($currents) are not within $mincur .. $maxcur"
        hupdate [sct objectPath]/status $msg
        if {$status eq ""} {
          clientlog "WARNING: $msg"
        }
      } elseif {[string match {slave currents *} $status]} {
        hupdate [sct objectPath]/status ""
      }
    }
    sct last_current $current
    sct currents ""
    sct send READ:DEV:PSU.M${n_of_slaves}:PSU:SIG:CURR
    return "ipsmag::slave_currents $n_of_slaves"
  }
  return idle
}

proc ipsmag::slave_currents {index} {
  set value [format %.2f [ipsmag::cvt]]
  sct currents "$value [sct currents]"
  updateval [sct parent]/slave$index $value
  incr index -1
  if {$index > 0} {
    sct send READ:DEV:PSU.M${index}:PSU:SIG:CURR
    return "ipsmag::slave_currents $index"
  }
  return idle
}

proc ipsmag::write_config {} {
  if {[hvali [sct parent]/engineering_password] eq ""} {
    sct update [sct target]
    sct send READ:[sct adr]
    return ipsmag::confirm_config
  }
  return [ipsmag::write]
}

proc ipsmag::confirm_config {} {
  set val [ipsmag::cvt]
  if {[hvali [sct parent]/engineering_password] eq ""} {
    # check only
    if {$val != [format %.7g [silent $val sct target]]} {
      hdelprop [sct] requested
      error "can not change [sct] with empty engineering_password"
    }
  }
  sct update $val
  return idle
}

proc ipsmag::update_config {} {
  set val [ipsmag::cvt]
  if {[hvali [sct parent]/engineering_password] eq ""} {
    # check only
    set msghead "make sure cables are connected for"
    if {$val != [format %.7g [silent $val sct target]]} {
      set txt "$msghead [hval [sct parent]/symode] mode, enter eng. pwd. and select [result device name] again"
      if {[hvali [sct objectPath]/status] ne $txt} {
        hupdate [sct objectPath]/status $txt
        clientput "ERROR: $txt"
      }
      hsetprop [sct] geterror "$val does not match configured value [sct target]"
      return idle
    } else {
      if {[string match "$msghead*" [hvali [sct objectPath]/status]]} {
        hupdate [sct objectPath]/status ""
        catch {hdelprop [sct] geterror}
      }
    }
  }
  sct update $val
  return idle
}

proc ipsmag::check_config {} {
  set val [silent [sct target] hval [sct]]
  if {[hvali [sct parent]/engineering_password] eq ""} {
    # check only
    if {$val != [format %.7g [silent $val sct target]]} {
      error "can not change [sct] with empty engineering_password"
    }
  }
}

proc ipsmag::read_ramp_state {{from_write 0}} {
  if {$from_write} {
    if {[string match {*:DENIED} [sct result]]} {
      if {[hval [sct]] == 3} {
        error "[sct]: power supply is clamped - please check reason"
      }
      error "[sct]: access not allowed: IPS in local mode?"
    }
    sct update [sct target]  ;# do we need this? should be done in update
  }
  sct send "READ:[sct adr]"
  return update
}


