#
# Copyright (c) 2012-2017,2019,2025-2026 LAAS/CNRS
# All rights reserved.
#
# Permission to use, copy, modify, and distribute this software for any purpose
# with or without   fee is hereby granted, provided   that the above  copyright
# notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS  SOFTWARE INCLUDING ALL  IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS. IN NO EVENT SHALL THE AUTHOR  BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR  ANY DAMAGES WHATSOEVER RESULTING  FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
# OTHER TORTIOUS ACTION,   ARISING OUT OF OR IN    CONNECTION WITH THE USE   OR
# PERFORMANCE OF THIS SOFTWARE.
#
#                                            Anthony Mallet on Tue Apr  3 2012
#
package require genomix

namespace eval genomix::server {
  upvar index index
  variable connections [dict create]


  # --- connect ------------------------------------------------------------
  #
  proc connect { {host localhost:8080} {session ""} } {
    variable index
    variable connections

    lassign [split $host :] server port
    if {$server == ""} { set server localhost }
    if {$port == ""} { set port 8080 }

    if {[catch {socket $server $port} s]} {
      error "$s\ngenomixd may not be running on $server:$port"
    }
    fconfigure $s -buffering full
    fconfigure $s -blocking 0
    fconfigure $s -encoding utf-8

    set r [genomix::http::transaction $s get /version]
    catch {dict get $r body} v
    switch -glob [dict get $r status] {
      2[0-9][0-9] { }
      default { error "unable to get genomixd server version: $v" }
    }
    if {[catch {package vcompare $v 1.9}]} {
      error "not a genomixd server"
    }
    if {[package vcompare $v 1.9] < 0} {
      error "unsupported genomixd server version $v, need >= 1.9"
    }

    set r [genomix::http::transaction $s get /session/start?s=$session]
    if {[catch {dict get $r body} m]} { set m "" }
    switch -glob [dict get $r status] {
      2[0-9][0-9] { set session $m }
      default { error $m }
    }

    set ev [socket $server $port]
    fconfigure $ev -buffering full
    fconfigure $ev -blocking 0
    fconfigure $ev -encoding utf-8

    set token genomix[incr index]
    set alias [interp alias {} $token {} genomix::server::ctl $token]

    dict set connections $token host "$server:$port"
    dict set connections $token session $session
    dict set connections $token sock $s
    dict set connections $token push $ev
    dict set connections $token cmd $alias

    init $token
    return $token
  }
  namespace export connect


  # --- reconnect ----------------------------------------------------------
  #
  proc reconnect { {token {}} } {
    variable index
    variable connections

    if {$token == ""} { set token genomix$index }
    if {![dict exists $connections $token]} {
      error "no such connection $token"
    }

    set host [dict get $connections $token host]
    lassign [split $host :] server port
    if {$server == ""} { set server localhost }
    if {$port == ""} { set port 8080 }

    set s [socket $server $port]
    fconfigure $s -buffering full
    fconfigure $s -blocking 0

    set session [dict get $connections $token session]
    set r [genomix::http::transaction $s get /session/start?s=$session]
    if {[catch {dict get $r body} m]} { set m "" }
    switch -glob [dict get $r status] {
      2[0-9][0-9] { }
      4[0-9][0-9] { }
      default { error $m }
    }

    set ev [socket $server $port]
    fconfigure $ev -buffering full
    fconfigure $ev -blocking 0

    ::close [dict get $connections $token sock]
    ::close [dict get $connections $token push]
    dict set connections $token sock $s
    dict set connections $token push $ev

    init $token
    return ""
  }
  namespace export reconnect


  # --- init ---------------------------------------------------------------
  #
  proc init { {token {}} } {
    variable index
    variable connections

    if {$token == ""} { set token genomix$index }
    if {![dict exists $connections $token]} {
      error "no such connection $token"
    }

    set s [dict get $connections $token sock]
    set ev [dict get $connections $token push]
    set session [dict get $connections $token session]

    set r [genomix::http::transaction $ev get /session/listen?s=$session]
    if {[catch {dict get $r body} m]} { set m "" }
    switch -glob [dict get $r status] {
      2[0-9][0-9] { }
      default { error $m }
    }

    fileevent $ev readable [namespace code [list event $token]]
    return
  }


  # --- close --------------------------------------------------------------
  #
  proc close { {token {}} } {
    variable index
    variable connections

    if {$token == ""} { set token genomix$index }
    if {![dict exists $connections $token]} {
      error "no such connection $token"
    }

    set s [dict get $connections $token sock]
    set h [fileevent $s readable]
    set ev [dict get $connections $token push]
    set alias [dict get $connections $token cmd]
    dict unset connections $token

    interp alias {} $alias {}
    ::close $ev
    ::close $s

    # let any ongoing transaction finish
    if {$h != ""} { eval $h }

    return
  }
  namespace export close


  # --- ctl ----------------------------------------------------------------
  #
  proc ctl { token cmd args } {
    switch $cmd {
      read {
        if {[llength $args] != 2} {
          error "wrong # args: $token $cmd client port"
        }
        lassign $args c port
        return [genomix::json::json2dict [get $token /module/$c/read/$port]]
      }

      send {
        if {[llength $args] != 3} {
          error "wrong # args: $token $cmd client service inputdict"
        }
        lassign $args c service input
        return [get $token /module/$c/send/$service $input]
      }

      pub {
        if {[llength $args] < 4 || [llength $args] > 5} {
          error "wrong # args: $token $cmd client port op id argdict"
        }
        lassign $args c port op id args
        dict set args id $id
        return [get $token /module/$c/pub/$port/$op $args]
      }

      portconnect - portdisconnect {
        if {[llength $args] != 2} {
          error "wrong # args: $token $cmd client port"
        }
        lassign $args c port
        return [get $token /module/$c/$cmd/$port]
      }

      abort - clean {
        if {[llength $args] != 2} {
          error "wrong # args: $token $cmd client rid"
        }
        lassign $args c rid
        return [get $token /module/$c/$cmd/$rid]
      }

      result {
        if {[llength $args] != 1} {
          error "wrong # args: $token result id"
        }
        lassign [split {*}$args :] module sep r
        return [genomix::module::result $token $module $r]
      }

      close {
        if {[llength $args]} {
          error "too many args: $args"
        }
        close $token
        return
      }

      reconnect {
        if {[llength $args]} {
          error "too many args: $args"
        }
        reconnect $token
        return
      }

      rpath {
        if {[llength $args] != 1} {
          error "wrong # args: $token rpath path"
        }
        return [get $token /rpath/[lindex $args 0] {}]
      }

      load {
        if {![llength $args]} {
          error [concat "wrong # args: $token load client" \
                     "?-h? ?client options? ?as alias?"]
        }
        set args [lassign $args client]
        return [genomix::module::load $token $client {*}$args]
      }

      path - unload {
        if {[llength $args] != 1} {
          error "wrong # args: $token $cmd client"
        }
        return [get $token /module/[genomix::module::client $args]/$cmd]
      }

      options {
        if {[llength $args] != 1} {
          error "wrong # args: $token $cmd client"
        }
        return [genomix::json::json2dict [get $token /usage/$args]]
      }

      info {
        if {[llength $args] != 1} {
          error "wrong # args: $token $cmd client"
        }
        if {[catch {
          genomix::json::json2dict [get $token /module/$args/info]
        } info]} {
          error "invalid metadata for $client: $info"
        }
        return $info
      }

      dlopen {
        if {[llength $args] != 2} {
          error "wrong # args: $token $cmd client optlist"
        }
        return [get $token /load/[lindex $args 0] [list argv [lindex $args 1]]]
      }
    }

    error [concat "unknown command $cmd: should be close,"	\
               " reconnect, rpath, load, unload, path,"	\
               " result, options, info, send, read, pub"]
  }


  # --- get ----------------------------------------------------------------
  #
  proc get { token cmd {cmdargs {}} } {
    variable connections

    if {![dict exists $connections $token]} {
      error "no such connection $token"
    }
    set s [dict get $connections $token sock]
    set session [dict get $connections $token session]

    set uri [genomix::uri encode $cmd]
    append uri "?s=$session"
    dict for {k v} $cmdargs {
      append uri "&[genomix::uri encode $k]=[genomix::uri encode $v]"
    }

    set r [genomix::http::transaction $s get $uri]
    if {[catch {dict get $r body} m]} { set m "" }
    switch -glob [dict get $r status] {
      2[0-9][0-9] { return $m }
      default { error [genomix::json::json2dict $m] }
    }
  }


  # --- event --------------------------------------------------------------
  #
  proc event { token } {
    variable connections

    set s [dict get $connections $token push]
    set e [genomix::http::event $s]

    dict with e {}
    switch -- $event {
      quit { close $token }
      unload { genomix::module::forget $token $data }
      request { genomix::module::reply $token $data }
      default { puts $e }
    }
  }
}

namespace eval genomix {
  namespace import server::*
}
