proc makeCCU4 {args} {

  if {[sicsdescriptor cc] ne "ccu4"} {
    if {[sicsdescriptor cc] ne "notfound"} {
      error "object cc must be CCU4 but is [sicsdescriptor cc]"
    }
    set ver [silent 0 result device ccu4version]
    if {$ver < 7.0 && $ver != 0} {
      clientput "WARNING: old ccu4 version $ver"
      makenv cc ccu4_6
      catch {cc hea 0}
    } else {
      makenv cc ccu4
      if {$ver < 7.3} {
        cc hea 0
      }
    }
  }
  device_layout makeitem /cc 99
  set badargs [list]
  set cfgdev "0"
  set hepump 0
  foreach arg $args {
    switch $arg {
      nv - nv_release_blocked {
        if {$arg eq "nv_release_blocked"} {
          makenv nv -driver ccu4flow release_blocked
        } else {
          makenv nv -driver ccu4flow
        }
#        GraphAdd cc.f mbar nv_flow red
        GraphAdd nv.flow ln/min nv_flow magenta
        GraphAdd nv.autoflow.flowtarget ln/min  nv_set green
        if {[silent 0 result instconfig sensirion]} {
          GraphAdd nv.flowp ln/min nv_pres gray
        }
#        GraphAdd cc.mp i pulse green
#        GraphAdd cc.mo i overrun blue
#        GraphAdd cc.mt i torque yellow
        set hepump 1
      }
      nvstep {
        makenv nv nvstep
        GraphAdd nv.flow ln/min nv_flow magenta
        GraphAdd nv.autoflow.flowtarget ln/min nv_set green
        if {[silent 0 result instconfig sensirion]} {
          GraphAdd nv.flowp ln/min nv_pres gray
        }

        makenv nvmot trinamic -base /nv/ hepump

        GraphAdd nv.nvmot.pos deg
        GraphAdd nv.nvmot.encoder deg

#        nvmot maxcurrent 128
#        nvmot maxspeed 200
#        nvmot acceleration 50
#        nvmot freewheeling 1
#        nvmot lowerlimit -110
#        nvmot upperlimit 110
#        nvmot zero -90
#        nvmot maxencdif 1.8
        nvmot precision 1.8
        set hepump 1

      }
      n2 {
        makenv ln2fill ccu4n2
        GraphAdd cc.nu K_2 N2_upper red
        GraphAdd cc.nl K_2 N2_lower green
      }
      he {
        makenv hefill ccu4he
        GraphAdd cc.h % He_Level brown
        set vessel [silent 0 result instconfig hevessel]
        if {$vessel eq "ami136"} {
          makenv vhe -driver ami136 cm 
          GraphAdd vhe % HeVessel blue
        } else {
          GraphAdd cc.h0 % HeVessel blue
          helium_register _cc /cc/h0 vessel
          if {$vessel ne "0"} {
            catch {cc hea 1}
            catch {cc hem0 475}
            catch {cc hfu0 0}
          }
        }
        helium_register _cc /cc/h
      }
      he_ilm {
        makenv hefill -driver ccu4ext he lev
        set vessel [silent 0 result instconfig hevessel]
        if {$vessel ne "0"} {
          catch {cc hea 1}
          catch {cc hem0 475}
          catch {cc hfu0 0}
        }
        GraphAdd lev % He_Level brown
        GraphAdd cc.h0 % HeVessel blue
        hsetprop /hefill fast_cmd "lev mode 1\ncc hf 1"
        hsetprop /hefill slow_cmd "lev mode 0\ncc hf 0"
      }
      default {
        if {$cfgdev eq "0"} {
          set cfgdev $arg
        } else {
          lappend badargs $arg
        }
      }
    }
# the following lines are to allow only new syntax
# makeCCU4 device module ... instead of ccu4_device
    if {$cfgdev eq "0"} {
      set cfgdev ""
    }
  }
  if {$hepump} {
    makeHePump
  }
  set cfg [ccu4_get_device_config $cfgdev codesfile]
  if {$cfg eq "" || [llength $badargs] > 0} {
    error "ERROR: bad args: $badargs
ERROR: Usage: makeCCU4 <device> <module> ...
ERROR:        <device> must be a device defined in $codesfile
ERROR:        <module> must be one of: nv n2 he he_ilm"
  }
  ccu4_config_device $cfg
}

proc ccu4_get_device_config {device codesfilename} {
  upvar $codesfilename codesfile
  set codesfile config/ccu4device.list
  catch {
    set fil [open $codesfile]
    set lines [list]
    catch {
      set lines [split [::read $fil] \n]
    }
    close $fil
  }
  set ldevice [string tolower $device]
  foreach line $lines {
    set lline [split $line "\t"]
    set fdevice [string tolower [lindex $lline 0]]
#    clientput "$ldevice $fdevice"
    if {$ldevice eq $fdevice} {
      return $line
    }
  }
  return ""
}

proc ccu4_config_device {devcfg} {
  set lline [split $devcfg "\t"]

  # send device name first
  cc cdv [lindex $lline 0]
  cc hav 0
  cc fav 0
  cc nav 0
  cc mav 0
  set opt [lindex $lline 3]
  foreach o [split $opt ""] {
    switch $o {
      f { cc fav 1
          cc mav 1
      }
      n { cc nav 1
      }
      N { cc nav 2
      }
      a { cc hav 1
          cc hd  75
          cc hwr 460
      }
      o { cc hav 1
          cc hd  130
          cc hwr 166
      }
      h { cc hav 1
          if {[catch {
            cc hd  [lindex $lline 6]
            cc hwr [lindex $lline 7]
          }]} {
            error "config/ccu4device.list: need current and resistivity of wire in column 6 and 7 (or use a/o instead of h)"
          }
      }
      H { cc hav 2
      }
    }
  }
  if {[sctval /cc/hav] == 1} {
    if {[catch {
      cc hem [lindex $lline 4]
      cc hfu [lindex $lline 5]
    }]} {
      error "config/ccu4device.list: need empty and full resistivity of wire in column 4 and 5"
    }
  }
  # set to remote
  cc cds 1
}

proc ccu4_device {device} {
  global device_name
  
  if {$device eq "0"} {
    device makeitem ccu4_device 0
    return
  }
  set c4d [silent 0 result device ccu4_device]
  if {$device eq "renew"} {
    if {$c4d eq "blank"} {
      ccu4_show_device $device_name
      return
    }
    set device $c4d
  } elseif {$device eq "default"} {
    if {$c4d eq "blank"} {
      ccu4_show_device $device_name
      return
    }
    if {$c4d ne "0"} {
      return
    }
    set device $device_name
  }
  device makeitem ccu4_device $device
  if {$device eq "blank" || $device eq "special"} {
    return
  }
  set cfgdev [ccu4_get_device_config [string tolower $device] codesfile]
  if {$cfgdev ne ""} {
    ccu4_config_device $cfgdev
  } else {
    error "$device not found in $codesfile"
  }
}

proc ccu4_show_device {device} {
  cc cdv $device
  cc hav 0
  cc fav 0
  cc nav 0
  cc mav 0
  cc cds 1
}

proc makeNone {} {
  if {[rack] ne "no"} {
    set ver [silent 0 result device ccu4version]
    if {$ver < 7.0} {
      makenv cc ccu4_6
    } else {
      makenv cc ccu4
    }
    ccu4_device none
    makeHePump
  } else {
    ccu4_device 0
  }
}

proc rackGroup args {
  # source config/rack.list
  Label "select rack (with T-controller and CCU):"
  RadioGroup rack
  Newline
  NarrowColumn 3
  set racks [get_rack]
  if {[lindex $racks 0] eq "!"} {
    Style warning
    Label "[lrange $racks 1 end]"
    Newline
    set racks [concat no other]
  } else {
    set racks [concat no $racks other]
  }
  foreach key $racks {
    RadioButton $key
  }
  Newline
  Label "a list of racks can be found as $::env(HOME)/sea/tcl/config/rack.list"
}

proc rack {{rack ""} {forced 0}} {
  global rack_in_use rack_was_active rack_request

  set oldrack [silent UNDEF result device rack]
  if {$oldrack eq "UNDEF"} {
    if {[cfg_env exists _cc]} {
      set oldrack other
    } else {
      set oldrack no
    }
  }
  set now [DoubleTime]
  if {$rack eq ""} {
    if {[info exists rack_request]} {
      lassign $rack_request rackset req_time
      if {$req_time < $now + 10} {
        return $rackset
      }
      unset rack_request
    } 
    return $oldrack
  }
  set rack_request [list $rack $now]
  set rack_was_active $now
  if {$rack eq "other"} {
    device rack other
    clientlog "other rack: define ports with cfgenv _cc / cfgenv _tt"
    return
  }
  source config/rack.list
  if {$rack eq "no"} {
    install_rack no $forced
    return
  }
  set cfglist no
  foreach {key val} $racklist {
    if {[string equal -nocase $rack $key]} {
      set rack $key
      set cfglist $val
      break
    }
  }
  if {$cfglist eq "no"} {
    error "illegal rack: $rack"
  }
  if {![info exists rack_in_use]} {
    install_rack $rack
    return
  }
  set racks [get_rack]
  if {$racks eq $rack} {
    install_rack $rack 1
  } else {
    clientlog "ask_for_rack $rack [result instrument]"
    seaman / ask_for_rack $rack [result instrument]
  }
}

proc install_rack {rack {forced 0}} {
  global inside_samenv
  global rack_in_use

  if {$rack eq ""} {
    set rack no
  }
  clientlog "install_rack $rack"
  set oldrack [silent UNDEF result device rack]
  if {$rack eq "no"} {
    if {[silent 1 result device rack_used]} {
      if {$forced || $oldrack eq "no"} {
        samenv -q none
      } else {
        clientlog "ERROR: rack $oldrack is in use"
        clientlog "ERROR: use"
        clientlog "ERROR:    samenv none"
        clientlog "ERROR: to release it"
        error "above error"
      }
    } else {
      device makeitem rack no
      remove_these_objects {cc _cc}
      cfg_env makeitem _cc unconnected
      cfg_env makeitem _tt unconnected
      cfg_env makeitem _nvmot unconnected
      cfg_env makeitem _hemot unconnected
      # catch {_cc disconnect}
      # catch {_tt disconnect}
    }
    seaman / rack_owner $oldrack free
    return
  }
  source config/rack.list
  array set racks $racklist
  if {![info exists racks($rack)]} {
    error "undefined rack: $rack"
  }
  set cfglist $racks($rack)
  device makeitem rack $rack
  set reload 0
  foreach {dev hostport} $cfglist {
    if {[silent 0 result cfg_env $dev] ne $hostport} {
      cfg_env makeitem $dev $hostport
      set reload 1
    }
  }
  if {$rack ne "other"} {
    seaman / rack_owner $rack [result instrument] [result device rack_used]
  }
  if {$reload} {
    dolater 3 reload_device_later
  } elseif {[sicsdescriptor cc] eq "notfound"} {
    if {[info exists rack_in_use]} {
      do_as_manager {
        set inside_samenv 1
        makenv cc ccu4
        set inside_samenv 0
      }
    }
    catch {ccu4::getdevice [result device name]}
  } else {
    dolater 3 _cc reconnect
    catch {ccu4::getdevice [result device name]}
  }
}

proc reload_device_later {} {
  samenv -q reload
  if {[result device name] eq "none"} {
    # auto device:
    cc cdv *
  }
#  cc send cin=
}

proc set_free_instruments {cc fi} {
  $cc send cil=[string toupper $fi]
}

proc rack_check_connection {} {
  global rack_was_active

  set now [DoubleTime]
  if {[sicsdescriptor cc] eq "notfound" || ![string match "CC*" [silent no result device rack]]} {
    seaman_rack_in_use [result device rack] 0
    return
  }
  set last [silent 0 hgetpropval /cc read_time]
  set last2 [silent 0 hgetpropval /tt read_time]
  if {$last2 > $last} {
    # let's take the last read time of /tt, in case the ccu (/cc) is not working
    set last $last2
  }
  if {[info exists rack_was_active]} {
    if {$last > $rack_was_active} {
      set rack_was_active $last
    }
    if {[silent 0 result device rack_used] == 0} {
      set inuse 0
    } else {
      set inuse [expr $now < $rack_was_active + 10]
    }
    seaman_rack_in_use [result device rack] $inuse
  } else {
    set rack_was_active $now
  }
  if {$last == 0} {
    set rack_expires [expr $rack_was_active + 300]
  } elseif {[result device name] eq "none"}  {
    set rack_expires [expr $rack_was_active + 300]
  } else  {
    set rack_expires [expr $rack_was_active + 3600 * 12]
  }
  if {$now > $rack_expires} {
    clientlog "no rack connected - no device"
    rack no 1
  }
}

proc seaman_rack_in_use {rack inuse} {
  global rack_in_use

#  if {![info exists rack_in_use]} {
#    # no rack management
#    return
#  }
  if {$rack eq ""} {
    set rack no
  }
  if {$rack eq "no"} {
    # should not happen
    set inuse 0
  }
  set old [silent "" set rack_in_use]
  set rack_in_use "$inuse $rack"
  if {$rack_in_use ne $old} {
    seaman / rack_in_use_on $rack [result instrument] $inuse
  }
}

proc rack_disconnect args {
  install_rack no 1
  seaman / move_rack_to [result device rack] free
}

proc rack_in_use {rack} {
  global rack_in_use

  if {![info exists rack_in_use]} {
    # no rack management
    return 0
  }
  if {[silent 0 result device rack_used] == 0} {
    return 0
  }
  set inuse [lindex $rack_in_use 0]
  set r [lindex $rack_in_use 1]
  if {$r ne $rack} {
    set inuse 2
  }
  return $inuse
}

proc release_rack_for {rack instrument} {
  switch [rack_in_use $rack] {
    1 {
      seaman / rack_not_available_for $rack $instrument [result instrument]
      return
    }
    0 {
      rack no
      clientlog "rack no"
      seaman / rack_available_for $rack $instrument
    }
    2 {
      clientlog "rack $rack not in use here"
      seaman / rack_available_for $rack $instrument force
    }
  }
}

proc rack_not_available {rack old} {
  broadcast "ERROR: $rack not available (used on $old)"
}

publishLazy rack
publishLazy rack_disconnect
publishLazy rack_with_confirm
publishLazy seaman_rack_in_use
publishLazy reload_device_later
publishLazy set_free_instruments

publishLazy release_rack_for
publishLazy rack_not_available
publishLazy install_rack
publishLazy rack_in_use
