#
# Copyright (c) 2012,2014-2015,2019 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
#

namespace eval genomix::http {
  variable pindex
  variable pending
  array set pindex {}
  array set pending {}


  # --- transaction --------------------------------------------------------
  #
  proc transaction { sock method uri {body {}} } {
    variable pindex
    variable pending

    # send request
    set len [string bytelength $body]
    set content "$method $uri HTTP/1.0\n"
    if {$len > 0} {
      append content "Content-Type: application/x-www-form-urlencoded\n"
      append content "Content-Length: $len\n"
    }
    append content "\n"
    if {$len > 0} {
      append content $body
    }
    puts -nonewline $sock $content
    flush $sock

    # push a new state and process events until done
    incr pindex($sock) 0
    set level [llength [lappend pending($sock) [dict create state status]]]
    set e [fileevent $sock readable]
    fileevent $sock readable [namespace code [list data $sock]]

    while { $pindex($sock) < $level } {
      genomix::pmt::mvwait 0 pindex($sock)
    }

    # pop results
    fileevent $sock readable $e
    set r [lindex $pending($sock) end]
    set pending($sock) [lrange $pending($sock) 0 end-1]
    incr pindex($sock) -1
    switch -- [dict get $r state] {
      ok	{ return $r }
      eof	{ error "broken connection" }
      default	{ error "transaction error" }
    }
  }


  # --- event --------------------------------------------------------------
  #
  proc event { sock } {
    set e [dict create]
    while 1 {
      while {[gets $sock line] >= 0} {
        if {$line == ""} {
          if {![catch {dict get $e data} data]} {
            dict set e data [join $data "\n"]
          }
          return $e
        }

        set value [join [lassign [split $line :] key] :]
        if {[string index $value 0] == " "} {
          set value [string range $value 1 end]
        }
        dict lappend e $key $value
      }
      if {[eof $sock]} { error "broken connection" }
    }
  }


  # --- data ---------------------------------------------------------------
  #
  proc data { sock } {
    variable pindex
    variable pending

    set s [lindex $pending($sock) $pindex($sock)]

    # check closed channel
    if {[catch {eof $sock}]} {
      dict set s state eof
      lset pending($sock) $pindex($sock) $s
      incr pindex($sock)
      return
    }

    # minimalistic http/1.0 state machine
    switch [dict get $s state] {
      status {
        if {[gets $sock line] < 0} {
          if {[eof $sock]} {
            dict set s state eof
            lset pending($sock) $pindex($sock) $s
            incr pindex($sock)
          }
          return
        }
        dict set s status [lindex $line 1]
        dict set s state headers
        lset pending($sock) $pindex($sock) $s
      }

      headers {
        if {[gets $sock line] < 0} {
          if {[eof $sock]} {
            dict set s state eof
            lset pending($sock) $pindex($sock) $s
            incr pindex($sock)
          }
          return
        }

        if {$line == ""} {
          if {![catch {dict get $s headers content-length} l] && $l > 0} {
            dict set s body {}
            dict set s state body
            lset pending($sock) $pindex($sock) $s
          } else {
            dict set s state ok
            lset pending($sock) $pindex($sock) $s
            incr pindex($sock)
            return
          }
        } else {
          set kv [string first : $line]
          set k [string tolower [string trim [string range $line 0 $kv-1]]]
          set v [string trim [string range $line $kv+1 end]]
          dict set s headers $k $v
          lset pending($sock) $pindex($sock) $s
        }
      }

      body {
        set body [dict get $s body]
        set got [string bytelength $body]
        set expect [dict get $s headers content-length]

        if {$got < $expect} {
          set data [read $sock [expr {$expect-$got}]]
          append body $data
          dict set s body $body
          lset pending($sock) $pindex($sock) $s
          set got [string bytelength $body]
        }

        if {$got >= $expect} {
          dict set s state ok
          lset pending($sock) $pindex($sock) $s
          incr pindex($sock)
          return
        }
      }
    }

    if {[eof $sock]} {
      dict set s state eof
      lset pending($sock) $pindex($sock) $s
      incr pindex($sock)
    }
  }
}
