namespace eval k2450 {} {
  variable luaCode
  set luaCode 0
}

proc stdConfig::k2450 {parameter {path_res ""}} {
  # parameter: either current or voltage
  # respath: the path of a resitivity node / object
  variable base
  variable name

  controller std
  prop startcmd *IDN?
  prop control_parameter CURRENT
  prop changed_control 0
  prop path_$parameter $base$name
  prop vsum 0
  prop isum 0
  prop nsum 0
  prop path_res $path_res

  obj K2450 -drive wr
  poll 1
  prop checklimits k2450::checklimits
  prop halt k2450::on_halt
  prop write k2450::write_main
  prop read k2450::read_main
  prop laststep 0
  prop status idle
  prop @parameter [string toupper $parameter]
  prop @otherpath ""
  prop startrun 0

  kids $parameter {
    node target upd

    node output out -int
    prop check k2450::check_output
    prop write k2450::write_output
    prop label output_on
    prop enum 1

    node set out
    prop check k2450::check_set
    prop write k2450::write_set

    node limited upd
    default 0
    if {$parameter eq "current"} {
      prop label "voltage limited"
    } else {
      prop label "current limited"
    }

    node limit out
    prop check k2450::check_limit
    prop write stdSct::completeUpdate
    if {$parameter eq "current"} {
      default 1
    } else {
      default 20
    }

    node lastmsg rd -text
    prop read k2450::geterrors
    prop geterror clear
    prop errcnt 0

    node script -text par "linear_ramp.tcl"
    prop help "scriptfile containing 'while' and 'halt' scripts"
    prop width 32

    node delay par 1

    node step par 0

    node codeversion rd -text
    prop read k2450::load_code
    prop visible false

    # force load_code to be run first
    variable ctrl
    variable node
    $ctrl queue $node write read
  }
}

proc k2450::start {} {
  sct send *IDN?
  return complete
}

proc k2450::geterrors {{followup 0}} {
  if {[silent "" sct geterror] eq "clear"} {
    sct update ""
  }
  if {$followup} {
    if {[lindex [sct result] 0] == 0} {
      return [silent idle sct after_geterrors]
    }
    set txt [lrange [sct result] 1 end-4]
    if {[lindex [sct result] end-3] < 4} {
      clientput "K2450 message: $txt"
      if {[sct control_parameter] eq [sct @parameter]} {
        updateval [sct objectPath]/lastmsg $txt
      }
    } else {
      set errcnt [sct errcnt]
      incr errcnt
      if {[sct errcnt] > 10} {
        sct errcnt 0
        sct send print(eventlog.clear())
        return [silent stdSct::complete sct after_geterrors]
      }
      sct errcnt $errcnt
    }
  }
  sct send print(eventlog.next())
  return "k2450::geterrors 1"
}

proc k2450::check_limit {} {
  if {[hvali [sct @otherpath]/output]} {
    sct update [sct target]
    hset [sct @otherpath]/set [hvali [sct @otherpath]/set]
    return idle
  }
  if {![hvali [sct objectPath]/output]} {
    sct update [sct target]
    return idle
  }
  set s [hvali [sct objectPath]/set]
  set l [expr abs([sct target])]
  sct update $l
  if {$s > $l} {
    set s $l
  } elseif {$s < -$l} {
    set s -$l
  } else {
    return
  }
  clientput "changed [sct objectPath]/set to $s"
  hset [sct objectPath]/set $s
}

proc k2450::luacode {code} {
  variable luaCode
  variable luaCodeVersion
  if {$code ne [silent 0 set luaCode]} {
    set luaCode $code
    set luaCodeVersion [clock format [clock seconds] -format %Y-%m-%dT%H:%M:%S]
  }
}

k2450::luacode {{
  setSource = function(sfunc, mfunc, set, lim)
    if sfunc != smu.source.func then
      smu.source.output = smu.OFF
    end
    smu.source.func = sfunc
    smu.measure.func = mfunc
    smu.source.level = set
    if lim then
      if sfunc == smu.FUNC_DC_VOLTAGE then
        smu.source.ilimit.level = lim
      else
        smu.source.vlimit.level = lim
      end
    end
    smu.source.output = smu.ON
  end
} {
  setVoltage = function(set, lim)
    setSource(smu.FUNC_DC_VOLTAGE, smu.FUNC_DC_CURRENT, set, lim)
    print("setVoltage", smu.source.level)
  end
} {
  setCurrent = function(set, lim)
    setSource(smu.FUNC_DC_CURRENT, smu.FUNC_DC_VOLTAGE, set, lim)
    print("setCurrent", smu.source.level)
  end
} {
  viBuf = buffer.make(10)
  getValues = function()
    viBuf.clear()
    smu.measure.read(viBuf)
    if smu.source.func == smu.FUNC_DC_CURRENT then
      u = viBuf.readings[1]
      i = viBuf.sourcevalues[1]
      tripped = tonumber(smu.source.vlimit.tripped)
    else
      i = viBuf.readings[1]
      u = viBuf.sourcevalues[1]
      tripped = tonumber(smu.source.ilimit.tripped)
    end
    print(string.format("%.7g %.7g %.7g %d %d %d end", u, i, smu.source.level,
      tonumber(smu.source.func), tonumber(smu.source.output), tripped))
  end
}}


proc k2450::load_code {} {
  if {[sct @parameter] eq "CURRENT"} {
    sct @otherpath [sct path_voltage]
  } else {
    sct @otherpath [sct path_current]
  }
  sct send {print(codeVersion, "codeVersion")}
  return k2450::load_code_chk
}

proc k2450::load_code_chk {} {
  variable luaCodeVersion
  lassign [sct result] versionOnDevice chk
  if {$chk ne "codeVersion"} {
    return idle
  }
  sct update $versionOnDevice
  if {$versionOnDevice == $luaCodeVersion} {
    return idle
  }
  clientput "load $luaCodeVersion (!= [sct result])"
#  sct send "reset() print()"
  sct send "print()"
  return "k2450::load_code_do 0"
}

proc k2450::load_code_do {index} {
  variable luaCode
  sct send "[string map [list "\n" " "] [lindex $luaCode $index]] print()"
  incr index
  if {$index > [llength $luaCode]} {
    return k2450::load_code_end
  }
  return "k2450::load_code_do $index"
}

proc k2450::load_code_end {} {
  variable luaCodeVersion
  hupdate [sct parent]/codeversion $luaCodeVersion
  sct send "codeVersion = '$luaCodeVersion' print()"
  return k2450::geterrors
}

proc k2450::write_set {} {
  if {[sct control_parameter] ne [sct @parameter]} {
    return idle
  }
  set lim nil
  catch {
    set lim [sctval [sct @otherpath]/limit]
  }
  if {[sct @parameter] eq "CURRENT"} {
    sct send setCurrent([sct target], $lim)
  } else {
    sct send setVoltage([sct target], $lim)
  }
  sct changed_control 0
  updateval [sct @otherpath]/output 0
  updateval [sct parent]/output 1
  hupdate [sct @otherpath]/set 0
  logsetup [sct @otherpath]/set clear
  return complete
}

proc k2450::get_old {obj} {
  set old [hvali $obj/set]
  set meas [hvali $obj]
  clientput "old $old meas $meas"
  set new [silent $meas hgetpropval $obj/set requested]
  clientput "new $new"
  if {abs($new - $old) > 1e-2 * abs($new + $old)} {
    return $new
  }
  return $old
}

proc k2450::check_output {} {
  if {[sct target]} {
    hset [sct parent]/set [get_old [sct parent]]
  }
}

proc k2450::write_output {} {
  if {[sct target]} {
    return idle
  }
  # switch off
  sct send "smu.source.output = smu.OFF print()"
  sct update 0
  return complete
}

proc k2450::checklimits {} {
# checklimits is called twice! avoid that
  if {[sct startrun]} return
  hdelprop [sct @otherpath] target
  set script [hval [sct]/script]
  if {$script ne ""} {
    clientput "$script [pwd]"
    set file [result exe batchpath]/$script
    if {[file exists $file]} {
      source $file
    } elseif {[file exists $script]} {
      source $script
    }
  }
  first [get_old [sct]] [sct target]
  sct lastset [sctval [sct]/set]
  sct laststep [DoubleTime]
  updateval [sct]/target [sct target]
  sct startrun 1
}

proc k2450::check_set {} {
  sct control_parameter [sct @parameter]
  sct changed_control 2
  if {abs([sct target]) > [hval [sct objectPath]/limit]} {
    error "abs([sct]) must be <= [hval [sct objectPath]/limit]"
  }
}

proc k2450::on_halt {{startpath ""} {startscript ""}} {
  hdelprop [sct] target
  sct status posfault
  clientput "HALT [sct]"
  if {$startpath ne ""} {
    sct status idle
    [sct controller] queue $startpath read $startscript
  } else {
    # do not call halt script when restarting
    if {[catch {halt} msg]} {
      clientput "ERROR: in halt: $msg"
    }
  }
  return idle
}

proc k2450::read_main {} {
  if {[sct control_parameter] ne [sct @parameter]} {
    if {[sct status] eq "run"} {
      clientput "[sct] stopped"
      hdelprop [sct] target
      sct status idle
    }
    return idle
  }
  sct send "getValues()"
  return k2450::update_main
}

proc k2450::update_main {} {
  lassign [sct result] voltage current set func output tripped end
  if {$end ne "end"} {
    clientput "bad result to getValues(): [sct result]"
    return idle
  }
  if {$func} {
    set func CURRENT
  } else {
    set func VOLTAGE
  }
  if {$func ne [sct control_parameter]} {
    if {[sct changed_control]} {
      # function to be changed, but not yet sent to the device
      sct changed_control [expr [sct changed_control] - 1]
    } else {
      # function was changed on the device
      sct control_parameter $func
    }
    return idle
  }
  if {!$output && [hvali [sct]/output]} {
    clientput "[sct] output switched off manually"
  }
  hupdate [sct]/output $output
  if {!$output || ![sctval [sct]/output]} {
    # output switched off
    sct update 0
    updateval [sct]/set 0
    updateval [sct @otherpath] 0
    updateval [sct @otherpath]/set 0
    logsetup [sct]/set clear
    logsetup [sct] clear
    logsetup [sct @otherpath] clear
    logsetup [sct @otherpath]/set clear
    hdelprop [sct] target
    hdelprop [sct @otherpath] target
    if {[sct status] eq "run"} {
      clientput "[sct] stopped"
      sct status idle
    }
    return idle
  }
  updateval [sct]/limited $tripped
  set now [DoubleTime]
  sct vsum [expr [sct vsum] + $voltage]
  sct isum [expr [sct isum] + $current]
  sct nsum [expr [sct nsum] + 1]
  if {[sct nsum] >= 5 + (int($now) % 5 != 0)} {
    updateval [sct path_voltage] [expr [sct vsum] / double([sct nsum])]
    updateval [sct path_current] [expr [sct isum] / double([sct nsum])]
    catch {
      updateval [sct path_res] [expr [sct vsum] / double([sct isum])]
    }
    updateval [sct]/set $set
    sct vsum 0
    sct isum 0
    sct nsum 0
  }
  if {[sct status] eq "run"} {
    get_values [sct] delay
    if {$delay <= 0} {
      set delay 1
    }
    if {[catch {set res [check $voltage $current]} msg]} {
      clientput "ERROR: in check: $msg"
      set res -1
    }
    while {$res > 0 && $now > [sct laststep] + $delay} {
      get_values [sct] set step target
      set lastset [silent $set sct lastset]
      if {abs($lastset - $set) > 1e-2 * abs($lastset + $set)} {
        clientput "lastset ($lastset) set to readback ($set)" 
        set lastset $set
      }
      if {[catch {set res [step $lastset $step $target]} msg]} {
        clientput "ERROR: in step: $msg"
        set res -1
      }
      catch {
        sct lastset [hgetpropval [sct]/set target]
      }
      sct laststep [expr [sct laststep] + $delay]
    }
    if {$res < 0} {
      hdelprop [sct] target
      if {[catch {halt} msg]} {
        clientput "ERROR: in halt: $msg"
      }
      sct status posfault
    } elseif {$res == 0} {
      sct last_step 0
      clientput "[sct] finished"
      sct status idle
    }
  }
  return idle
}

proc k2450::write_main {} {
  if {[sct startrun]} {
    if {[hgetpropval [sct @otherpath] status] eq "run"} {
      [sct controller] queue [sct @otherpath] start "k2450::on_halt [sct objectPath] k2450::write_main"
      return idle
    }
    sct startrun 0
    sct status run
    set set [silent [hval [sct]/set] sct lastset]
    sct print "[sct] running from $set to [hval [sct]/target]"
  } else {
    if {[sct status] eq "run"} {
      clientput "[sct] stopped"
      sct status idle
    }
    sct print "[sct] set to [sct target]"
    hset [sct]/set [sct target]
  }
  return idle
}
