#---------------------------------------------------------------------------
# seaman scripts (connection bookkeeping)
#---------------------------------------------------------------------------

proc check_instrument {caller {hostport {}}} {
  if {[sicsdescriptor $caller] ne "sea"} {
    catch {removeobject $caller}
    catch {removeobject _$caller}
    if {$hostport eq ""} {
      set hostport "$caller:8641"
    }
    stdConfig::make seaclient $caller _$caller -port $hostport
  } elseif {[driverKind _$caller oldhostport] eq "sctcontroller"} {
    if {$oldhostport ne $hostport} {
      _$caller reconnect $hostport
    }
  }
}

proc book_request {caller hostport device args} {
  clientput "$caller ($hostport) wants to get: $args"
  check_instrument $caller $hostport
  
  # make a list of already booked items for caller
  foreach item [busy_list items] {
    set ins [result busy_list $item]
    if {$ins eq $caller} {
      set booked($item) 1
    }
  }
  # make a list of requested items from caller
  foreach item [request_list items] {
    set ins [result request_list $item]
    if {$ins eq $caller} {
      set requested($item) 1
    }
  }

  if {$device eq "PARTIAL"} {
    set all 0
  } else {
    set all 1
    device_list makeitem $caller $device
  }
  # book or add to the list of booked by other instruments
  set cmd [list reconnect_or_set_busy]
  foreach itm $args {
    if {[string match "sample-ts*" $itm] || [string match "localhost*"] $itm} {
      set item "${caller}/$itm"
    } else {
      set item $itm
    }
    if {[busy_list exists $item]} {
      set ins [result busy_list $item]
      set booked($item) 0
      if {$ins ne $caller} {
        request_list makeitem $item $caller
        set requested($item) 0
      }
    } else {
      busy_list_add $item $caller
      set booked($item) 0
      set ins $caller
    }
    if {$all && $ins eq $caller} {
      set arg [list $itm]
    } else {
      set arg [list $itm $ins [silent "" result device_list $ins]]
    }
    lappend cmd $arg
  }
  
  array set confirm_list {}
  if {$all} {
  # remove not requested bookings of caller
    set items ""
    foreach item [array names booked] {
      if {$booked($item)} {
        lappend items $item
        busy_list_add $item KILL
        if {[request_list exists $item]} {
          set ins [result request_list $item]
          clientput "confirm $ins"
          lappend confirm_list($ins) $item
        }
      }
    }
    foreach item [array names requested] {
      if {$requested($item)} {
        request_list deleteitem $item
      }
    }
    if {$items ne ""} {
      clientput "$caller does no longer need: $items"
    }
  }
  $caller / $cmd
  clientput "inform $caller about busy items: [lrange $cmd 1 end]"
  foreach i [array names confirm_list] {
    clientput "confirm_list $i : $confirm_list($i)"
    eval "book_confirm $i $confirm_list($i)"
  }
  return ""
}

proc busy_list_add {hostport caller} {
  if {$caller eq "KILL"} {
    busy_list deleteitem $hostport
  } else {
    busy_list makeitem $hostport $caller
  }
}

proc get_instruments {} {
  set ilist [split [unknown list match _*] ","]
  foreach instr $ilist {
    if {$instr ne "ENDLIST"} {
      set ins [string range $instr 1 end]
      if {[sicsdescriptor $ins] eq "sea"} {
        lappend instruments [string range $instr 1 end]
      }
    }
  }
  return $instruments
}

proc hostport_of {ctrl rack} {
  source config/rack.list
  set hostport ""
  array set racks $racklist
  catch {
    set cfglist $racks($rack)
    foreach {obj hostport} $cfglist {
      if {$obj eq $ctrl} {
        break
      }
    }
  }
  return $hostport
}

proc hostports_of {rack} {
  source config/rack.list
  set hostport ""
  array set racks $racklist
  set result [list]
  catch {
    set cfglist $racks($rack)
    foreach {obj hostport} $cfglist {
      lappend result $hostport
    }
  }
  return $result
}

proc rack_owner {rack {owner ""} {used ""}} {
  if {$rack eq "no"} return
  set hostport [hostport_of _cc $rack]
  if {$hostport eq ""} {
    clientput "$rack not found"
    return
  }
  if {$owner eq ""} {
    return [silent undef result rack_list $rack]
  }
  clientlog "rack $rack owner $owner old [silent free result rack_list $rack]"
  if {$owner eq "free"} {
#    if {$hostport ne ""} {
#      dolater 3 rack_reconnect $rack $hostport
#    }
    foreach p [hostports_of $rack] {
      catch {busy_list deleteitem $p}
    }    
    rack_list makeitem $rack free
  } else {
    # release all racks attached to owner first
    foreach irack [rack_list items] {
      if {[result rack_list $irack] eq $owner} {
        rack_list $irack free
      }
    }
    # attach rack to owner
    rack_list makeitem $rack $owner
    if {$used ne ""} {
      catch {instr_list $owner $inuse}
    }
    # obsolete after change:
    if {[sicsdescriptor $rack] eq "ccu4"} {
      removeobject $rack
      if {[sicsdescriptor _$rack] eq "SctController"} {
        removeobject _$rack
      }
      clientput "release $rack"
    }
  }
}

# obsolete:
proc rack_reconnect {rack hostport} {
  if {[sicsdescriptor $rack] eq "notfound"} {
    stdConfig::make -driver ccu4 -name $rack -port $hostport
  } else {
    _$rack reconnect
  }
  clientput "reconnect $rack"
  rack_list makeitem $rack free
}

proc rack_in_use_on {rack instrument inuse} {
  global free_instr_cache

  instr_list makeitem $instrument $inuse
  if {$inuse} {
    if {[silent free result rack_list $rack] ne $instrument} {
      rack_owner $rack $instrument 1
    }
  }
  catch {unset free_instr_cache}
}

proc free_instruments {} {
  global free_instr_cache

  if {![info exists free_instr_cache]} {
    set ilist [instr_list items]
    set fi [list]
    foreach ins $ilist {
      if {[result instr_list $ins] == 0} {
        lappend fi $ins
      }
    }
    set free_instr_cache [join $fi ","]
  }
  return $free_instr_cache
}

proc ask_for_rack {rack instrument} {
  set ins [silent free result rack_list $rack]
  set hins [silent $ins result busy_list [hostport_of _cc $rack]]
  if {$hins ne $ins} {
    $instrument / rack_not_available $rack $hins
    return
  }
  clientlog "$rack: $ins -> $instrument"
  if {$ins eq $instrument} {
    $instrument / install_rack $rack
    return
  }
  if {$ins eq "free"} {
    dolater 1 rack_available_for $rack $instrument
    return
  }
  clientlog $ins / release_rack_for $rack $instrument
  $ins / release_rack_for $rack $instrument
}

proc move_rack_to {rack to} {
  if {$rack eq "no"} return
  if {$to eq "free"} {
    rack_owner $rack $to
    return
  }
  dolater 0 rack_available_for $rack $to
}

proc rack_available_for {rack instrument {tmo now}} {
  if {$tmo eq "force" || [silent 0 result rack_list $rack] eq "free"} {
    clientlog "- $instrument / install_rack $rack"
    dolater 3 $instrument / install_rack $rack
    return
  }
  set now [DoubleTime]
  if {$tmo eq "now"} {
    set tmo $now
  }
  if {$tmo <= $now + 30} {
    error "timeout in rack_available_for $rack $instrument"
  }
  dolater 1 rack_available_for $rack $instrument $tmo
}

proc rack_not_available_for {rack instrument old} {
  clientlog $instrument / rack_not_available $rack $old
  $instrument / rack_not_available $rack $old
}

proc book_this {caller args} {
  clientput "$caller wants to disconnect: $args"
  foreach {item instr} $args {
    if {[result busy_list $item] eq $instr} {
      lappend ilist($instr) $item
    }
  }
  foreach instr [array names ilist] {
    $instr / "disconnect_from $caller $ilist($instr)"
  }
}

proc book_confirm {caller args} {
  clientput "inform $caller about disconnected items: $args"
  set rlist ""
  foreach item $args {
    if {[silent "" result request_list $item] eq $caller} {
      request_list deleteitem $item
    }
    busy_list_add $item $caller
    lappend rlist [list $item $caller [silent "" result device_list $caller]]
  }
  $caller / "reconnect_or_set_busy $rlist"
}

proc show_item {item} {
  set req [silent "" result request_list $item]
  if {$req ne ""} {
    set item [format "%-15s %s" $item $req]
  }
  return [format "%45s %s\n" " " $item]
}

proc device_none_on {instrument} {
  $instrument / samenv -q none
}

proc show {{key ""}} {

  set ilist [split [unknown list match _*] ","]
  foreach instr $ilist {
    if {$instr ne "ENDLIST"} {
      set ins [string range $instr 1 end]
      if {[sicsdescriptor $ins] eq "sea"} {
        set hostport {}
        driverKind $instr hostport
#        clientlog "$instr $hostport"
        set table($ins) [list $hostport]
      }
    }
  }

  foreach item [result busy_list items] {
    set ins [result busy_list $item]
    if {![info exists table($ins)]} {
      set table($ins) "?"
    }
    lappend table($ins) $item
  }
  
  set result    "Instrument                    Device          Connections     Requests\n"
  append result "----------------------------------------------------------------------\n"
  foreach instr [lsort [array names table]] {
    set row $table($instr)
    set head [format "%-29s %s\n" "$instr ([lindex $row 0])" [silent "" result device_list $instr]]
    if {$key eq "" || $key eq $instr} {
      append result $head
      foreach item [lrange $row 1 end] {
        append result [show_item $item]
      }
      append result "\n"
    } elseif {[string match "*$key*" [join $row]]} {
      append result $head
      foreach item [lrange $row 1 end] {
        if {[string match "*$key*" $item]} {
          append result [show_item $item]
        }
      }
      append result "\n"
    }
  }
  return $result
}

proc get_devices {args} {
  set ilist [split $args ","]
  foreach ins $ilist {
    set dev [silent {} result samenv_list $ins]
    if {$dev eq ""} {
      set dev [silent {} result device_list $ins]
    }
    lappend result $dev
  }
  return [join $result ","]
}

proc samenv args {
  if {$args eq "name"} {
    return "_inst_select"
  }
  if {[llength $args] > 0} {
    error "forbidden on seaman"
  }
}

namespace eval inst_select {
  variable home HOME
  variable instrument ""

clientput "REORDER web_ports"

  foreach itm [web_ports items] {
    web_ports deleteitem $itm
  }

  proc instrument {instr} {
    variable instrument
    set instrument $instr
  }

  proc seaweb {instr port {host ""}} {
    web_ports makeitem $instr $port
  }

  proc sea {mode sea graph web} {
    variable instrument
    seaweb $instrument $web
  }

  proc schedule_cron args {}
  proc writeServer args {}
  source ../../monitconfig

  web_ports makeitem test TEST
  samenv_list makeitem test instrlist
  device_list makeitem test instrlist
}

proc _inst_selectGroup {} {
  foreach instr [web_ports items] {
    set dev [silent "" result samenv_list $instr]
    set dev1 [silent "" result device_list $instr]
    if {$dev eq "" || $dev1 ne [lindex [split $dev /] 0]} {
      set dev $dev1
    }
    if {$dev ne ""} {
      clientput -T[string toupper $instr]
      clientput -V$dev
      set link [result web_ports $instr]
      if {$link eq "TEST"} {
        clientput -l:8000/?sg=1&sm=0&sc=0
        clientput -i_link_test
      } else {
        clientput -l:[result web_ports $instr]
        clientput -i_link_[result web_ports $instr]
      }
    }
  }
}

proc getgroup_inst_select {} {
  Group _inst_select " "
}

proc rackGroup {} {
  foreach rack [lsort [rack_list items]] {
    Input "$rack attached to" "rack_owner $rack"
  }
}

proc portsGroup {} {
  foreach port [lsort [busy_list items]] {
    Input "$port attached to" "busy_list $port"
  }
}

proc mainGroup {} {
  Group rack "racks"
  Group ports "ports"
}

publish book_this User
publish book_request User
publish book_confirm User
publish device_none_on User
publish get_devices User
publish show Spy
publish update_rack_list Spy
publish get_instruments Spy
publish rack_owner Spy
publish rack_in_use_on Spy

publish ask_for_rack Spy
publish rack_available_for Spy
publish rack_not_available_for Spy
publish free_instruments Spy
publish move_rack_to Spy

