namespace eval nvstep {
}

proc stdConfig::nvstep {} {
  variable name

  controller syncedprot
  pollperiod 1 1

  obj NvStep wr
  prop write nvstep::write
  prop read "nvstep::read [silent 0 result instconfig sensirion]"
  prop enum fixed=0,controlled=1,automatic=2,close=3,open=4
  prop write nvstep::write
  prop lastpulse 0
  prop filter 0
  prop closetest 0
  prop amplitude 0
  prop t_after_delay 0
  prop fixed_to_auto 0
  prop initialize 0
  prop maxflow 0
  default 0

  kids "needle value" {
    node flow upd

    node motstat -text upd

    node set out
    default 2.5
    prop check nvstep::checkset
    prop write stdSct::complete
    prop label "flow set"

    node flowmax par 20
    prop label "flow maximum"

    node flowp upd
    prop help "flow calculated from pressure before pump"

    node ctrl -none
    kids "control parameters" {
      node prop par 10
      prop help bigger means higher feedback

      node int par 15
      prop help bigger means reacting slower (unit: sec)

      node delay par 5
      prop help minimum time to wait before next motor step

      node tol par 0.1
    }

    node autoflow -none
    kids "autoflow control parameters" {
      flow::make flow::tmts {result tt set/reg}
    }

    node calib -none
    kids calib {
      node ln_per_min_per_mbar par 0.9
      node mbar_offset par 0.5
    }

    node initialize -int out
    default 0
    prop visible false
    prop enum check=1,adjust=2,stop=3
    prop label "limit switch"
    prop write nvstep::startinit

    node initstate -text upd
    prop visible false
    default ""
  }
}

proc nvstep::checkset {} {
  sct update [sct target]
  set s [hvali [sct parent]]
  if {[hvali /cc/fa] == 0 || ($s != 1 && $s != 2)} {
    if {$s != 2} {
      set s 1
    }
    hset [sct parent] $s
  }
}

proc nvstep::write {} {
  switch [sct target] {
    3 {
      sct rangeshift 0
      if {[hvali [sct]/nvmot] > [hvali [sct]/nvmot/lowerlimit]} {
        nvmot [hvali [sct]/nvmot/lowerlimit]
        updateval [sct]/motstat closing
      }
    }
    4 {
      nvmot [hvali [sct]/nvmot/upperlimit]
      updateval [sct]/motstat opening
    }
    2 {
      hupdate [sct]/autoflow/suspended 0
    }
  }
  sct fixed_to_auto 0
  if {[sct target] == 0 || [sct target] > 2} {
    logsetup [sct]/set clear
    logsetup [sct]/autoflow/flowtarget clear
  }
  sct update [sct target]
  return idle
}

proc nvstep::calcstep {from to prop} {
  # for the decreasing step (to < from), we use a linear function above 20
  foreach var {from to} {
    if {[set $var] > 20 && $to < $from} {
      set $var [expr 1.9 + 0.0025 * [set $var]]
    } elseif {[set $var] > 1} {
      set $var [expr 2 - 1.0 / [set $var]]
    }
  }
  set res [expr ($to - $from) * $prop]
  return $res
}

proc nvstep::poll {} {
  set now [DoubleTime]
  set mode [sctval [sct]]
  if {$mode != 0} {
    set encpos [hval [sct]/nvmot/encoder]
    if {$encpos < [hval [sct]/nvmot/lowerlimit] - 15 ||
        $encpos >  [hval [sct]/nvmot/upperlimit] + 15} {
      hsetprop [sct]/nvmot/encoder adjust_zero 1
      return idle
    }
  }
  # if {[silent 0 sct initialize] > 0} {
  #  return nvstep::init
  # }
  hset [sct]/nvmot/disablelimits 0
  set umsg "automatic needle valve not activated - set temperature undefined"
  switch -- $mode {
    1 - 2 {
      # pump_is_off is defined in startup/hepump.tcl
      if {[info command pump_is_off] ne "" && [pump_is_off]} {
        return idle
      }
      if {$mode == 1 || [sctval [sct]/autoflow/suspended]} {
        set soll [hvali [sct]/set]
        hupdate [sct]/autoflow/flowtarget $soll
      } else {
        if {$now > [silent 0 sct lastauto] + 4.7} {
          sct lastauto $now
          set script [hvali [sct]/autoflow/script]
          set flowmax [hvali [sct]/flowmax]
          if {$flowmax > 20} {
            set flowmax 20
            clientput "WARNING: reduced flowmax to $flowmax ln/min for less stress to the pump"
            hupdate [sct]/flowmax $flowmax
          }
          $script [sct]/autoflow [hvali [sct]/set] $flowmax
        }
        set soll [hvali [sct]/autoflow/flowset]
        if {[hgetpropval [sct]/autoflow/getTset t_set_undefined]} {
          if {[hvali [sct]/status] ne $umsg} {
            clientput "ERROR: $umsg"
          }
          hupdate [sct]/status $umsg
        }
      }
      set ist [hvali [sct]/flow]
      set tol [hvali [sct]/ctrl/tol]
      set a [sct amplitude]
      set t [sct t_after_delay]
      if {$t != 0} {
        set t [expr $now - $t]
      }
      set i [hvali [sct]/ctrl/int]
      # lim is an exponential curve converging to soll.
      # the range between lim and soll is a window shrinking with time
      # before t_after_delay, the last 'ist' value is between lim and soll
      # if the 'ist' value does not change it will reach lim at t_after_delay
      set lim [expr $soll + $a * exp(-$t/double($i))]
      # calculate expected limits for no further step needed
      if {$lim > $soll} {
        set uplim [expr $lim + $tol]
        set lolim [expr $soll - $tol]
      } else {
        set uplim [expr $soll + $tol]
        set lolim [expr $lim - $tol]
      }
      if {$ist > $uplim || $ist < $lolim} {
        # we have to do a next step
        set step [nvstep::calcstep $ist $soll [hvali [sct]/ctrl/prop]]
        sct t_after_delay [expr $now + [hvali [sct]/ctrl/delay]]
        sct amplitude [expr $ist - $soll]
        set v [format %.2f [expr [hvali [sct]/nvmot] + $step]]
        set u [hvali [sct]/nvmot/upperlimit]
        set ll [hvali [sct]/nvmot/lowerlimit]  ;# ll was 0 before!
        if {$v > $u} {
          set v $u
          updateval [sct]/motstat opened
        } elseif {$v < $ll} {
          set v $ll
          updateval [sct]/motstat closed
        } elseif {$step > 0} {
          updateval [sct]/motstat opening
        } else {
          updateval [sct]/motstat closing
        }
#clientput "$step $v"
        nvmot $v
      } else {
        # no step needed yet
        updateval [sct]/motstat idle
        if {$t > 0} {
          if {$ist < $uplim - $tol && $ist > $lolim + $tol} {
            # the ist value is making more progress then expected at worst
            # so we narrow the window accordingly
            sct amplitude [expr $ist - $soll]
            sct t_after_delay $now
          }
        }
      }
    }
    0 { # fixed
      set cnt [sct fixed_to_auto]
      if {[silent 0 hval [sct]/flow] > [hvali [sct]/flowmax] + 10} {
        incr cnt
        if {$cnt > 60} {
          clientput "pressure high - switch to automatic mode"
          hset [sct] 2
        }
      } else {
        if {$cnt > 0} {
          incr cnt -1
        }
      }
      sct fixed_to_auto $cnt
      switch [hvali [sct]/motstat] {
        opening - closing {
          if {[hgetpropval [sct]/nvmot status] ne "run"} {
            updateval [sct]/motstat idle
          }
        }
      }
    }
    3 { # close
      set stat [hvali [sct]/motstat]
      if {[hgetpropval [sct]/nvmot status] ne "run" && $stat eq "closing"} {
        updateval [sct]/motstat closed
      }
    }
    4 { # open
      set stat [hvali [sct]/motstat]
      if {[hgetpropval [sct]/nvmot status] ne "run" && $stat eq "opening"} {
        updateval [sct]/motstat opened
      }
    }
  }
  if {[hvali [sct]/status] eq $umsg && \
      ([sctval [sct]] != 2 || [hgetpropval [sct]/autoflow/getTset t_set_undefined] == 0)} {
    hsetprop [sct]/autoflow/getTset t_set_undefined 0
    hupdate [sct]/status ""
  }
  return idle
}

proc nvstep::read {{sensirion 0}} {
  hsetprop /cc/fa nvpath [sct]
  _cc updatescript /cc/fa nvstep::updatemode
  hsetprop /cc/f nvpath [sct]
  if {$sensirion} {
    hsetprop /nvflow flowsource flowmeter
    hsetprop /nvflow nvpath [sct]
    hsetprop /nvflow nvctrl [sct controller]
    _hemot updatescript /nvflow nvstep::updateflow
    hsetprop /cc/f flowsource pressref
  } else {
    hsetprop /cc/f nvctrl [sct controller]
    hsetprop /cc/f flowsource flowpress
  }
  _cc updatescript /cc/f nvstep::updateflow
  return unpoll
}

proc nvstep::updateflow {value} {
  # flowsource:
  #    pressref: just update nv/flowp
  #    flowmeter: update nv/flow
  #    flowpress: flow from pressure

  set source [silent pressure sct flowsource]
  set filter [lrange "[silent $value sct filter] $value" end-9 10]
  sct filter $filter
  # filter out values which are within the 3 highest or 3 lowest values
  # out of 10 last values (i.e. within 5 seconds)
  set m 3
  set filter [lsort -real $filter]
  if {[llength $filter] < 10} {
    set flow $value
  } else {
    set min [lindex $filter $m]
    set max [lindex $filter end-$m]
    set mean [expr double([join $filter +]) / [llength $filter]]
    set flow [silent 0 sct filtered]
    if {$max < $flow} {
      set flow $max
    } elseif {$min > $flow} {
      set flow $min
    } else {
      set flow [expr $flow * 0.98 + $mean * 0.02]
    }
  }
  sct filtered $flow
  if {$source eq "flowmeter"} {
    updateval [sct nvpath]/flow $flow
  } else {
    if {$flow < -50} {
      set flow -62.5
    } else {
      set off [hval [sct nvpath]/calib/mbar_offset]
      set fpm [hval [sct nvpath]/calib/ln_per_min_per_mbar]
      set flow [expr ($flow - $off) * $fpm]
    }
    if {$source eq "pressref"} {
      hupdate [sct nvpath]/flowp $flow
      return 
    }
    updateval_e [sct nvpath]/flow $flow -62.5 no_sensor
  }
  [sct nvctrl] queue [sct nvpath] read nvstep::poll
}

proc nvstep::startinit {} {
  set i [hgetpropval [sct parent] initialize]
  switch [sct target] {
    1 {
      if {$i != 0} {
        error "checking already"
      }
      # hsetprop [sct parent] initialize 10
      hsetprop [sct parent] initialize 13
    }
    2 {
      if {$i != 0} {
        error "adjusting already"
      }
      hsetprop [sct parent] initialize 20
    }
    3 {
      if {$i >= 10 && $i < 13} {
        hsetprop [sct parent] initialize 13
      } elseif {$i >= 20} {
        hsetprop [sct parent] initialize 28
      } else {
        hsetprop [sct parent] initialize 29
      }
    }
  }
  sct update [sct target]
  return idle
}

proc nvstep::ierror {msg} {
  updateerror [sct]/initstate "$msg"
  clientput "[sct]: $msg"
}

proc nvstep::imsg {msg} {
  if {$msg ne [hvali [sct]/initstate]} {
    hupdate [sct]/initstate $msg
    clientput "[sct]: $msg"
    catch {hdelprop [sct]/initstate geterror}
  }
}

proc nvstep::init {} {
  set now [DoubleTime]
  if {[hgetpropval [sct]/nvmot status] eq "run"} {
    return idle
  }
  set phase [sct initialize]
  set pos [hvali [sct]/nvmot/encoder]
  set goto ""
  switch $phase {
    10 { # start check
      ierror "limit switch no longer supported"
    }
    20 { # start adjust
      ierror "limit switch no longer supported"
    }
  }
  if {$goto ne ""} {
    sct goto $goto
    clientput "goto $goto"
    run [hgetpropval [sct]/nvmot sicsdev] $goto
  }
  sct initialize $phase 
  return idle
}
