namespace eval ccu4flow {
}

proc stdConfig::ccu4flow {args} {
  variable node
  
  controller syncedprot

  obj ccu4flow wr
  prop write ccu4flow::setmode
  prop read "ccu4flow::read [silent 0 result instconfig sensirion]"
  prop dir 1
  prop enum fixed=0,controlled=1,automatic=2,close=3,open=4
  prop filter 0
  prop regstate reg
  prop measured_openpulse 0
  prop pulse 0
  prop olddif 0
  prop settime 0
  prop soll 0
  prop adjustdelay 10
  prop maxhysteresis 1
  prop hystlim 1
  prop period 5
  prop lastpulse 0
  prop totalmin 0
  prop minpulse 0
  prop lastmov 0
  prop hystpulse 0
  prop lastist 0
  foreach arg $args {
    if {$arg eq "release_blocked"} {
      prop release_blocked 1
    }
  }
  default 0
  kids "needle valve" {
    node motstat alias /cc/fm
    prop enum idle,opening,closing,opened,closed,no_motor

    node flow upd -secop=nvflow

    node set out
    default 1.0
    prop check ccu4flow::checkset
    prop write stdSct::complete
    prop label "flow set"

    node flowmax par 20
    prop label "flow maximum"
    
    node flowp upd
    node span upd
    
    node ctrl -none
    kids "control parameters" {
      node regtext upd -text
      default regulate
      prop label status
      prop width 40
      
      node prop_o par 0.1
      prop help {prop [sec/mbar] when opening. above 4 mbar a 10 times lower value is used}

      node prop_c par 0.05
      prop help {prop [sec/mbar] when closing. above 4 mbar a 10 times lower value is used}
    
      node deriv_o par 30
      prop help {convergence target time [sec] when opening}

      node deriv_c par 30
      prop help {convergence target time [sec] when closing}
    
      node minpulse_o out
      default 0.05
      prop help {minimum close pulse [sec]}
      prop check "ccu4flow::check_minpulse 1"
      prop write stdSct::complete

      node minpulse_c out
      default 0.05
      prop help {standard close pulse [sec]}
      prop check "ccu4flow::check_minpulse -1"
      prop write stdSct::complete

      node hystpulse_o par 0
      prop help {motor pulse to overcome hysteresis when opening}

      node hystpulse_c par 0
      prop help {motor pulse to overcome hysteresis when closing}

      node tol par 0.25
      prop label tolerance
      prop help {valid below 3 mbar}
    
      node tolhigh par 0.5
      prop label tol. above 4
      prop help {valid above 4 mbar}
    
      node openpulse par 60
      prop help {time to open from completely closed to a significant opening}

      node adjust_minpulse par 1
      prop enum 1
      prop help {adjust minpulse automatically}
    }
    
    node autoflow -none
    kids "autoflow control parameters" {
      flow::make ccu4flow::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
    }
  }
}

proc ccu4flow::tmts {} {
  global tt

  set tm [silent 1 result tt tm]
  set ts [silent $tm result tt ts]
  if {$ts < $tm} {
    return $ts
  } else {
    return $tm
  }
}

proc ccu4flow::checkset {} {
  set dir [hgetpropval [sct parent] dir]
#  if {([sct target] - [hvali [sct]]) * $dir < 0} {
#    # we probably initiate a dir change
#    if {$invcnt > 0} {
#      incr invcnt -1
#      hsetprop [sct parent] invcnt $invcnt
#      logtext invcnt -1 by set
#    }
#  }
  if {[hvali [sct]] != [sct target]} {
    hsetprop [sct parent] setchanged 1
    hsetprop [sct parent] settime [DoubleTime]
  }
  sct update [sct target]
  #hupdate /cc/fm 0
  set s [hvali [sct parent]]
  if {[hvali /cc/fa] == 0 || ($s != 1 && $s != 2)} {
    hset [sct parent] 1
  }
}

proc ccu4flow::check_minpulse {dir} {
  if {$dir == [hgetpropval [sct objectPath] dir]} {
    hsetprop [sct objectPath] minpulse [sct target]
  }
  sct update [sct target]
}

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

proc ccu4flow::setmode {} {
  sctsync {
    sct update [sct target]
    if {[sct target] == 3} {
      # close
      cc fa 0
      cc mp -[hvali /cc/mot]
      # set motstat to opening (until it gets updated)
      hupdate /cc/fm 2
    } elseif {[sct target] == 4} {
      # open
      cc fa 0
      cc mp [hvali /cc/mot]
      # set motstat to closing (until it gets updated)
      hupdate /cc/fm 1
    } else {
      if {[sct target] == 2} {
        hupdate [sct]/autoflow/suspended 0
      }
      cc fa [sct target]
      cc mp 0
    }
  }
  return stdSct::complete
}

proc ccu4flow::updatemode {value} {
  if {$value == 3} {
    set mode [hvali [sct nvpath]]
    clientput "[sct parent] was offline $mode"
    if {$mode >= 3} {
      set mode 0
    }
    hset [sct] $mode
  } elseif {[hvali [sct nvpath]] < 3 || $value > 0} {
    if {$value ne [hvali [sct nvpath]]} {
      clientput "[sct] changed to $value -> $nvpath"
    }
    hupdate [sct nvpath] $value
  }
}

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

  set source [silent pressure sct flowsource]
  set n [silent 9 sct filter_n]
  set filter [lrange "[silent $value sct filter] $value" end-$n end]
  sct filter $filter
  # filter out values which are within the m highest or m lowest values
  # out of n+1 last values (i.e. within 5 seconds) (m was 3)
  set m [silent 0 sct filter_m]
  set filter [lsort -real $filter]
  if {[llength $filter] < $n} {
    set flow $value
    set flow0 $value
    set span 0
  } else {
    set min [expr [lindex $filter $m] - 0.1]
    set max [expr [lindex $filter end-$m] + 0.1]
    set mean [expr double([join $filter +]) / [llength $filter]]
    set span [expr $max - $min - 0.2]
    set flow [silent 0 sct filtered]
    if {$max < $flow} {
      # if {[sct] ne "/cc/f"} {
      #   clientput "[sct] fast decrease $flow $max"
      # }
      set flow $max
    } elseif {$min > $flow} {
        # if {[sct] ne "/cc/f"} {
        #   clientput "[sct] fast increase $flow $min"
        # }
      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
    hupdate [sct nvpath]/span $span
  }
  [sct nvctrl] queue [sct nvpath] read ccu4flow::ctrl
}

proc ccu4flow::increase {val by min max} {
  if {$val < $min} {
    set val $min
  }
  set lg [expr round(log10($val) * 10 + $by) * 0.1]
  set dig [expr int(1.85 - $lg)]
  if {$dig < 0} {
    set dig 0
  }
  set v [format "%.${dig}f" [expr pow(10.0, $lg)]]
  if {$v > $max} {
    return $max
  } elseif {$v < $min} {
    return $min
  } else {
    return $v
  }
}

proc ccu4flow::trf {value} {
  if {$value <= 1.0} {
    return [expr $value - 1.0]
  }
  return [format %.2f [expr log($value)]]
}

proc ccu4flow::setstate {state {text ""}} {
  upvar regstate r  
  if {$text eq ""} {
    set text $state
  }
#  if {$text ne [hvali [sct]/ctrl/regtext]} {
#    clientput "R $text"
#  }
  set r $state
  hset [sct]/ctrl/regtext $text
}

proc ccu4flow::logtext args {
#  clientput [concat $args]
}

proc ccu4flow::ctrl {} {
  set umsg "automatic needle valve not activated - set temperature undefined"
  set mode [sctval [sct]]
  set oldmode $mode
  # pump_is_off is defined in startup/hepump.tcl
  if {[info command pump_is_off] ne "" && ($mode == 2 || $mode == 1)} {
    if {[pump_is_off]} {
      set mode 0
    }
  }
  if {$mode == 2 && [sctval [sct]/autoflow/suspended]} {
    set mode 1
  }
  switch -- $mode {
    0 - 3 - 4 { # fixed, close, open
      if {$oldmode == $mode} {
        switch -- [hvali /cc/fm] {
          2 - 4 { # closing or closed
            sct update 3
          }
          1 - 3 { # opening or opened
            sct update 4
          }
          default {
            # sct update 0
          }
        }
      }
      logsetup [sct]/set clear
      logsetup [sct]/autoflow/flowtarget clear
      return idle
    }
    1 { # controlled
      set soll [hvali [sct]/set]
      # the following line is for graphic (using flowtarget as nv_set curve)
      hupdate [sct]/autoflow/flowtarget $soll
    }
    2 { # automatic
      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
      }
      flow::task /nv/autoflow [hvali [sct]/set] $flowmax
      set soll [hvali [sct]/autoflow/flowset]
      if {[sct soll] != $soll} {
        sct soll $soll
        sct settime [DoubleTime]
      }
      if {[hgetpropval [sct]/autoflow/getTset t_set_undefined]} {
        if {[hvali [sct]/status] ne $umsg} {
          clientput "ERROR: $umsg"
        }
        hupdate [sct]/status $umsg
      }
    }
  }
  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 ""
  }
  set regstate [sct regstate]
  set now [DoubleTime]
  set ist [hvali [sct]/flow]
  set tol [hvali [sct]/ctrl/tol]
  set tolhigh [hvali [sct]/ctrl/tolhigh]
  set closestate 0
  if {[hvali /cc/fm] == 4} {
    # n.v. is closed
    if {$ist < $soll} {
      if {$regstate ne "open"} {
        set closestate 2
        set regstate 0
      }
    } else {
      if {[silent 0 sct release_blocked]} {
        # hack for releasing n.v. at MA7
        setstate closed
        set closecnt [silent 1 sct closecnt]
        incr closecnt
        if {$closecnt > 20} {
          cc mp 0.3
          clientput "open pulse for blocked n.v."
          set closecnt 0
        }
        sct closecnt $closecnt
      }
      return idle
    }
  } elseif {$ist > $soll + 80} {
    # n.v. to be closed
    if {$regstate ne "close" && $regstate ne "open"} {
      set closestate 1
      set regstate 0
    }
  } elseif {[hvali /cc/fm] == 3} {
    # n.v. opened
    if {$soll > $ist} {
      hset [sct]/ctrl/regtext "fully opened"
      return idle
    }
  }
  if {[silent 1 sct closecnt] == 0 && [silent 0 sct release_blocked]} {
    cc mp -0.3
    clientput "close pulse for blocked n.v."
  }
  sct closecnt 1
  if {$tol < 0.01} {
    set tol 0.01
    hupdate [sct]/ctrl/tol $tol
  }
  if {$tolhigh < $tol} {
    set tolhigh $tol
    hupdate [sct]/ctrl/tolhigh $tolhigh
  }
  set f 10.0
  if {$soll > 4} {
    set tol $tolhigh
    set fact $f
  } elseif {$soll > 3} {
    set tol [expr ($soll - 3) * ($tolhigh - $tol) + $tol]
    set fact [expr ($soll - 3.0) * ($f - 1.0) + 1.0]
  } else {
    set fact 1.0
  }
  if {$ist > 4} {
    set fi $f
  } elseif {$ist > 3} {
    set fi [expr ($ist - 3) * ($f - 1.0) + 1.0]
  } else {
    set fi 1.0
  }
  if {[sct dir] > 0} {
    if {$ist > [sct lastist] + 0.1 * $fi} {
      sct hystpulse -1
    } elseif {$ist < [sct lastist]} {
      sct lastist $ist
    }
  } else {
    if {$ist < [sct lastist] - 0.1 * $fi} {
      sct hystpulse 1
    } elseif {$ist > [sct lastist]} {
      sct lastist $ist
    }
  }
  set fact [expr sqrt($fi * $fact)]

  set dif [expr $soll - $ist]
  if {[sct dir] > 0} {
    set _dir _o
  } else {
    set _dir _c
  }
  set lastim [silent 0 sct lastim]
  set period [sct period]
  switch $regstate {
    close {
      set closestate 0
      if {[hvali /cc/fm] == 2} {
        # still closing
      } elseif {[hvali /cc/fm] != 4} {
        # stopped: reg again?
        setstate reg
      } elseif {$dif < 0} {
        # flow still too high
        if {[hvali /cc/mp] != 0} {
          cc mp 0
        }
      } elseif {[hvali [sct]/ctrl/openpulse] > 0} {
        setstate open
      } else {
        setstate reg
      }
    }
    open {
      set closestate 0
      if {$ist < [sct minflow]} {
        sct minflow $ist
      }
      if {[hvali /cc/fm] == 1} {
        # still opening
        sct lastmov $now
      } else {
        if {$now > [sct lastmov] + $period * 2} {
          set mp [silent [hvali /cc/mo] hvali /cc/mmp]
          if {$mp > 0.21} {
#clientput "ccu4flow: measured open pulse $mp"
            hupdate [sct]/ctrl/openpulse [expr $mp - 0.2]
          } else {
#clientput "ccu4flow: open pulse unchanged"
          }
          sct dir -1
          setstate reg
        }
        # stabilize
      }
    }
    within_tolerance {
      if {$dif * [sct dir] < -$tol} {
        setstate reg_hyst
      } elseif {$dif * [sct dir] > $tol} {
        setstate reg
      } elseif {$now > [silent $now sct last_out_of_tol] + 120} {
        set cm [silent 0 sct corr_minpulse]
        if {$cm > 0} {
#clientput "ccu4flow: save increased minpulse$_dir [sct minpulse]"
          if {[hvali [sct]/ctrl/adjust_minpulse]} {
            hupdate [sct]/ctrl/minpulse$_dir [sct minpulse]
          }
        } elseif {$cm < 0} {
#clientput "ccu4flow: clear corr_minpulse"
        }
        sct corr_minpulse 0
      }
    }
    reg - reg_hyst - reg_in_tol {
      if {$dif * [sct dir] < -$tol} {
        # force init of reg_hyst state
        sct regstate reg
        setstate reg_hyst
      } elseif {$dif * [sct dir] <= 0 || abs($dif) < $tol * 0.5} {
        setstate within_tolerance
      } elseif {$now < $lastim + $period} {
        # wait
      } else {
        set slope [expr 0.01 + abs($dif) * $period / [hvali [sct]/ctrl/deriv$_dir]]
        set totalmin ""
        set ddif [format %.2f [expr -($dif - [sct olddif]) * [sct dir]]]
        sct filtered_ddif [expr max(0,[silent 0 sct filtered_ddif] * 0.9 + $ddif * 0.1)]
        if {$ddif < 0} {
          set ddif 0
        }
        if {$ddif > 2 * $slope} {
          sct olddif [expr $dif + [sct dir] * $slope]
#clientput "outof olddif [sct olddif]"
        } else {
          sct olddif [expr [sct olddif] - [sct dir] * $slope]
        }
        set prop [expr [hvali [sct]/ctrl/prop$_dir] / $fact]
        set minpulse [sct minpulse]
        set minpulse_fact [silent 1 sct minpulse_fact]
        if {$minpulse_fact > 1.45} {
          set minpulse_fact 0.6
        }
        set minpulse [expr $minpulse * $minpulse_fact]
        sct minpulse_fact [expr $minpulse_fact + 0.2]
        if {$ddif != 0} {
#clientput "[expr abs($dif) * $prop] - [expr abs($dif) * $prop * $ddif / $slope] d [expr $period * $dif / $ddif]"
        }
        set mpabs [format %.3f [expr abs($dif) * $prop * (1 - $ddif / $slope) + $minpulse]]
        # hupdate [sct]/span [sct filtered_ddif]
        if {$mpabs < 0} {
          set mpabs 0
        } elseif {$mpabs > 0} {
          if {[sct filtered_ddif] > $slope} {
            set mpabs 0
          }
        }
#        if {[sct dir] > 0 && [sct olddir] <= 0} {
#        }
        set mp [expr $mpabs * [sct dir]]
        set progress [expr [silent 0 sct progress] - 0.01]
        if {$dif * [sct dir] > $progress} {
          set progress [expr $dif * [sct dir]]
        }
        sct progress $progress
        set pstep 0.2
        if {$slope < $pstep} {
          set pstep $slope
        }
#clientput "ccu4flow: $dif ?< $progress - $pstep"
        if {$dif * [sct dir] < $progress - $pstep} {
          if {$now > [silent 0 sct lastprogress] + 1.5 * $period} {
#clientput "ccu4flow: progress $progress dif $dif"
            sct progress [expr $dif * [sct dir]]
            # we got over hysteresis
            sct hstat ""
            sct totalmin 0
            sct hystlim 0.3
            if {$regstate eq "reg_hyst"} {
              setstate reg
            }
          } elseif {$regstate eq "reg_hyst"} {
            set mp 0
          }
        } else {
          sct lastprogress $now
        }
        if {[sct hystpulse] eq [sct dir]} {
          if {$regstate eq "reg_hyst"} {
            set hp [hvali [sct]/ctrl/hystpulse$_dir]
            set mp [expr $mp + [sct dir] * $hp]
          }
          sct hystpulse 0
        }
        if {$mp == 0} {
          cc mp 0
        } else {
          if {$ddif < 0} {
#clientput "worse $dif"
            sct olddif $dif
          }
          if {abs($dif) < $tol} {
            setstate reg_in_tol
            set mp [format %.3f [expr ($minpulse / $tol + $prop) * $dif]]
            if {$now > [silent $now sct last_out_of_tol] + 120} {
              set cm [silent 0 sct corr_minpulse]
              if {$cm > 0} {
#clientput "ccu4flow: save increased minpulse$_dir [sct minpulse]"
                if {[hvali [sct]/ctrl/adjust_minpulse]} {
                  hupdate [sct]/ctrl/minpulse$_dir [sct minpulse]
                }
              } elseif {$cm < 0} {
#clientput "ccu4flow: clear corr_minpulse $cm"
              }
              sct corr_minpulse 0
            }
          } else {
            sct last_out_of_tol $now
            if {$regstate eq "reg_in_tol"} {
              setstate reg
            }
#            set mpmax [sct dir]
            set totalmin [sct totalmin]
            set hstat [silent "" sct hstat]
            if {abs($mp) > $minpulse} {
              lappend hstat [expr abs($mp)]
              set totalmin [expr $totalmin + abs($mp) - $minpulse]
              if {$totalmin > [sct hystlim]} {
#                set minpulse [format %.3f [expr $minpulse * 1.25 + 0.0003]]
                set minpulse [format %.3f [expr $minpulse + 0.001]]
                sct minpulse $minpulse
                sct corr_minpulse 1
                set h $hstat
                set hstat [list]
                set totalmin 0
                foreach v $h {
                  if {$v > $minpulse} {
                    set totalmin [expr $totalmin + $v - $minpulse]
                    lappend hstat $v
                  }
                }
#                clientput "increase minpulse$_dir $minpulse totalmin $totalmin"
              }
              set totalmin [format %.3f $totalmin]
              sct totalmin $totalmin
              sct hstat $hstat
            }
          }
          cc mp $mp
          #logtext mp $mp
          sct olddif $dif
          sct lastpulse $now
        }
#clientput "mp $mp t $totalmin"
        set lastim $now
      }
    }
  }
  if {$closestate != 0} {
    if {$closestate == 1} {
      setstate close "close / flow too high"
    } elseif {[hvali [sct]/ctrl/openpulse] > 0} {
      setstate open "open / n.v. closed"
      #clientput "[sct regstate] $regstate"
    } else {
      setstate reg 
    }
  }
  if {$regstate != [sct regstate]} {
#clientput "goto $regstate"
    switch $regstate {
      close {   
        set mp -[hvali /cc/mot]
        cc mp $mp
        logtext mp $mp
        sct dir -1
      }
      open {
        sct dir 1
        set mft [expr [hvali /cc/f] + 1.0]
        cc mft $mft
        set mp [hvali [sct]/ctrl/openpulse]
        set pulse $mp
        set mw [expr $mp * 0.8]
        if {$mw > 1} {
          set mw 1
        }
        cc mw $mw
        cc mp $mp
        logtext mp $mp mft $mft
        sct lastmov $now
        sct minflow $ist
      }
      reg_hyst - reg {
        if {$regstate eq "reg_hyst"} {
          sct totalmin 0
          sct hystlim [sct maxhysteresis]
          if {$now < [sct settime] + 1} {
#clientput "ccu4flow: reg_hyst after set"
          } else {
            set corr [silent 0 sct corr_minpulse]
            if {$corr != 0} {
              set minpulse [sct minpulse]
              if {$minpulse > 0 && $corr < 0} {
                set minpulse [format %.3f [expr $minpulse * 0.8 - 0.0004]]
#clientput "ccu4flow: decrease minpulse$_dir $minpulse"
              } elseif {$corr == 1} {
#clientput "ccu4flow: save increased minpulse$_dir $minpulse"
              }
              if {[hvali [sct]/ctrl/adjust_minpulse]} {
                hupdate [sct]/ctrl/minpulse$_dir $minpulse
              }
            }
          }
          sct corr_minpulse -1
#        } elseif {[sct regstate] eq "reg_hyst"} {
#          sct hystpulse [expr -[sct dir]]
#          clientput "enable hystpulse [sct hystpulse]"
#        } else {
#          sct hystpulse 0
        }
        if {$dif > 0} {
          sct dir 1
          set _dir _o
        } else {
          sct dir -1
          set _dir _c
        }
        sct minpulse [hvali [sct]/ctrl/minpulse$_dir]
        set lastim $now
        sct hstat ""
        sct lastpulse $now
        sct progress 0
      }
      within_tolerance {
        cc mp 0
      }
    }
    sct regstate $regstate
  }
  sct setchanged 0
  sct lastim [expr int($lastim / $period + 0.5) * $period]
  hdelprop [sct] geterror
  return idle
}
