# $Id: s.Agent.xotcl 1.10 01/03/23 21:55:32+01:00 neumann@somewhere.wu-wien.ac.at $
package provide xotcl::mos::agent 0.8

package require xotcl::trace
package require xotcl::comm::httpAccess
package require xotcl::mos::webObject
package require xotcl::store::persistence

#
# current response codes for agent requests:
#
# OK      -- content arrived (can be retrieved with sinks content method)
# ERROR   -- error on the place invocation
# FAILED  -- call itself failed, e.g. cancel
#
Class AgentMemSink \
  -superclass MemorySink \
  -parameter {{agent ""} responseCode}

AgentMemSink instproc startCb {r}   {
  [self] set d "" 
  next
}
AgentMemSink instproc notifyCb {r} {next}
AgentMemSink instproc incCb {r t c} {next}
AgentMemSink instproc endCb {r} {
  if {[Agent exists responseCodes([$r set responseCode])]} {
    [self] set responseCode OK
  } else {
    [self] set responseCode ERROR
  }
  next
}
AgentMemSink instproc cancelCb {r} {
  [self] set responseCode FAILED
  next
}
AgentMemSink instproc endReq {r} {
  [self] instvar agent 
  if {[Object isobject $agent]} {
    if {[$agent exists sinks($r)]} {
      $agent unset sinks($r)
    }
  }
}

# sink class that calls the agent's endcmd in async calls
# the sink is destroyed automatically after endCmd is called
Class AgentAsyncSink -superclass AgentMemSink
AgentAsyncSink instproc endCb {r} {
  next
  [self] instvar endCmd responseCode
  set result [[self] content]
  if {[info exists endCmd]} {
    eval [concat $endCmd $responseCode \"$result\"]
  }
  [self] endReq $r
}
AgentAsyncSink instproc cancelCb {r} {
  [self] instvar endCmd responseCode
  if {[info exists endCmd]} {
    eval [concat $endCmd $responseCode ""]
  }
  next
  [self] endReq $r
}

# sink type for sync request 
# has to be destroyed with endReq when content is 
# read (see createSyncRequest)
Class AgentSyncSink -superclass AgentMemSink


Class Agent -superclass WebObject
Agent array set responseCodes {
  200 {OK}
}

Agent instproc init args {
  #[self] showCall
  #[self] exportProcs invoke
  [self] array set endCmds {}
  [self] array set sinks {}
  next
}

#
# method to create async requests
#
# endCmd specifies the command (or object method or proc ...) that
# is to be called, when the request has ended, empty for sync requests
#
# args are to be given in the form -name value, like:
#   -contentType text/xml
#   -method PUT 
#   -data XXX
#
# returns the request object name
#
Agent instproc createRequest {endCmd url args} {
  puts stderr [self proc]----URL=$url
  puts stderr [self proc]----endCmd=$endCmd
  puts stderr [self proc]----args=$args
  [self] instvar place
  set s [AgentAsyncSink [$place autoname agentmemsink] -agent [self]]
  set cmd [list Access createRequest -caching 0 -url $url -informObject $s]
  foreach {n v} $args {lappend cmd $n $v}
  $s set endCmd $endCmd
  set t ::[string trimleft [::eval $cmd $args] :]
  [self] set sinks($t) $s
  return $t
}
#
# method to create sync reqs ... url and args identical to
# async req
#
# returns the result of sync request, if "OK" 
# otherwise: Raises an error
#
Agent instproc createSyncRequest {url args} {
  puts stderr [self proc]----URL=$url
  puts stderr [self proc]----args=$args
  [self] instvar place
  set s [AgentSyncSink [$place autoname agentmemsink] -agent [self]]
  set cmd [list Access createRequest \
	       -httpVersion 1.0 \
	       -caching 0 -url $url -informObject $s -blocking 1]
  foreach {n v} $args {lappend cmd $n $v}
  set t ::[string trimleft [::eval $cmd] :]
  #puts stderr "After SyncRequest t=$t [$s responseCode]"
  if {[$s responseCode] == "OK"} {
    set content [$s content]
    # kill the sink
    $s endReq $t
    return $content
  }
  $s endReq $t
  error "[self] -- Sync request failed: url=$url, responseCode=[$s responseCode]"
}
#
# invoke a remote method directly along the places' dispatcher 
#
Agent instproc invoke {endCmd host receiver args} {
  puts stderr [self proc]----host=$host
  #[self] showCall
  set url http://$host/${receiver}+[url encode $args]
  [self] createRequest $endCmd $url
}
Agent instproc syncInvoke {host receiver args} {
  puts stderr [self proc]----host=$host
  #[self] showCall
  set url http://$host/${receiver}+[url encode $args]
  [self] createSyncRequest $url
}

#
# invoke a cloning migration 
#
Agent instproc cloneImpl {async host startcmd {endCmd ""}} {
  #[self] showCall
  set ai [[self] agentInfo]
  
  set place [Place getInstance]

  # get the full name of the agent ns from the places's agent mgr
  #set ns [${place}::agentMgr::rdfNS searchPrefix agent]

  $ai set agentData(script) [${place}::scriptCreator makeScript [self]]
  $ai append agentData(script) [[self] makeVarScript]
  $ai set agentData(startcmd) $startcmd

  set data [$ai getXMLScript [$ai name]]
  puts $data

  #set data [[Place getInstance]::rdfCreator createFromTriples [$ai getTriples]]
  if {$async} {
    return [[self] createRequest $endCmd http://$host/[[self] selfName] \
	      -contentType text/xml \
	      -method PUT \
	      -data $data]
  } else {
    return [[self] createSyncRequest http://$host/[[self] selfName] \
	      -contentType text/xml \
	      -method PUT \
	      -data $data]
  }
}
Agent instproc clone {host startCmd endCmd} {
  [self] cloneImpl 1 $host $startCmd $endCmd
}
Agent instproc syncClone {host startCmd} {
  [self] cloneImpl 0 $host $startCmd
}

#
# invoke a migration that destroys the object in the current place 
#
Agent instproc migrateImpl {async host startcmd {endCmd ""}} {
  if {$async} {
    set r [[self] clone $host $startcmd $endCmd]
  } else {
    set r [[self] syncClone $host $startcmd]
  }
  [self] destroy
  return $r
}
Agent instproc migrate {host startCmd endCmd} {
  [self] migrateImpl 1 $host $startCmd $endCmd
}
Agent instproc syncMigrate {host startCmd} {
  [self] migrateImpl 0 $host $startCmd
}
#
# query a place with its hostname for its name
#
Agent instproc getPlaceFromHostName {endCb host} {
  set r [[self] autoname __result]
  [self] createRequest "[self]::$r set" http://$host/ 
  return [set [self]::$r]
}
