##
## This is file `api.tcl',
## generated with the docstrip utility.
##
## The original source files were:
##
## api.dtx  (with options: `tcl')
## 
##
## ===================================================================
## AlphaTcl - core Tcl engine
##                                   created: 2001-10-06 21:13:40
##                               last update: 2001-11-09 13:15:59
## Author: Frˇdˇric Boulanger
## E-mail: Frederic.Boulanger@supelec.fr
##   mail: Supˇlec - Service Informatique
##         Plateau de Moulon, 3 rue Joliot-Curie
##         F-91192 Gif-sur-Yvette cedex
##    www: http://wwwsi.supelec.fr/fb/
##
## Description:
##
##   The api package manages the declaration of APIs and of
##   implementations of these APIs with real applications in the
##   AlphaTcl library.
##
##   See api.dtx or its typeset form (api.pdf for instance) for the
##   documentation of this package.
##
## History
##
## modified   by  rev reason
## ---------- --- --- -----------
## 2001-10-06 FBO 1.0 original
## 2001-10-23 FBO 1.1 handle app paths as well as creators
## ===================================================================
namespace eval api {}

proc api::declare {apiname description args} {
  global api::description

  set api::description($apiname) $description

  set arglist ""
  foreach arg $args {
    append arglist " \$[lindex $arg 0]"
  }

  set proctext "\
    global api::currentApp;\
    set implementation \
     \"api::\[set api::currentApp($apiname)\]implementationOf$apiname\";\
    \$implementation $arglist;\
  "

  api::forget $apiname

  proc api::apiNamed$apiname $args $proctext
}

proc api::describe {apiname} {
  global api::applicationSigs
  global api::applicationPath
  global api::description

  set desc [list]
  if {[info commands api::apiNamed$apiname] == ""} {
    return $desc
  }

  lappend desc $apiname

  set sublist [list]
  foreach arg [info args api::apiNamed$apiname] {
    if {[info default api::apiNamed$apiname $arg defval]} {
      lappend sublist [list $arg $defval]
    } else {
      lappend sublist $arg
    }
  }
  lappend desc $sublist

  lappend desc [set api::description($apiname)]

  set implementations [info commands api::*implementationOf$apiname]
  foreach imp $implementations {
    regexp -- "api::(.*)implementationOf$apiname" $imp dummy appName
    set sublist $appName
    lappend sublist [set api::applicationSigs($appName)]
    if {[info exists api::applicationPath($appName)]} {
      lappend sublist [set api::applicationPath($appName)]
    } else {
      if {![catch {set appPath [nameFromAppl [set api::applicationSigs($appName)]]}]} {
        lappend sublist $appPath
      } else {
        lappend sublist {}
      }
    }
    lappend sublist [info body $imp]
    lappend desc $sublist
  }
  return $desc
}

proc api::getCurrentAppFor {apiname} {
  global api::currentApp

  if {[info commands api::apiNamed$apiname] == ""} {
    error "Undeclared API \"$apiname\""
  }
  if {[info exists api::currentApp($apiname)]} {
    return [set api::currentApp($apiname)]
  } else {
    return ""
  }
}

# Return the signature used by the APIs for an application
proc api::getSignatureFor {appname} {
  global api::applicationSigs

  if {[info exists api::applicationSigs($appname)]} {
    return [set api::applicationSigs($appname)]
  } else {
    return ""
  }
}

# Return the application path used by the APIs for an application
proc api::getPathFor {appname} {
  global api::applicationPath
  global api::applicationSigs

  if {[info exists api::applicationPath($appname)]} {
    return [set api::applicationSigs($appname)]
  } else {
    if {![catch {
      set appPath [nameFromAppl [set api::applicationSigs($appname)]]
    }} {
      return $appPath
    } else {
      return ""
    }
  }
}

# Forget an API.
proc api::forget {apiname} {
  global api::currentApp

  # Forget any implementation of a possible previous version of the API
  set implementations [info commands api::*implementationOf$apiname]
  foreach imp $implementations {
    rename $imp api::unimplemented
  }

  # Forget the API proc
  if {[info commands api::apiNamed$apiname] != ""} {
    rename api::apiNamed$apiname api::undefined
  }

  # Forget the current app for this API
  if {[info exists api::currentApp($apiname)]} {
    unset api::currentApp($apiname)
  }
}

# Register an application for an API. This means that the
# application supports the API, and a script is given to
# turn the invocation of the API into a run of the application.
#
# For instance, if application CMacTeX of signature "*XeT" supports the
# "tex" API, it can be registered with the following call:
#
#  api::registerApp tex CMacTeX *XeT {
#    buildNewCMacTeXAE "tex $options &$format" $filename
#  }
#
# The last argument says that to implement the "tex" API with CMacTeX, one
# should execute "buildNewCMacTeXAE ...". The variable used in the script
# have the same name as the formal arguments of the API and their value
# will be the actual arguments when the script is executed.
proc api::registerApp {apiname appName appSig script} {
  global api::applicationSigs

  if {[info commands api::apiNamed$apiname] == ""} {
    error "Undeclared API \"$apiname\""
  }

  # Remember the signature of this application to be able to
  # launch it later.
  set api::applicationSigs($appName) $appSig

  # Declare the proc that uses "script" to implement the API
  proc api::${appName}implementationOf$apiname \
   [linsert [info args api::apiNamed$apiname] 0 apiTarget] \
   $script

}

# Register an application for an API. This means that the
# application supports the API, and a script is given to
# turn the invocation of the API into a run of the application.
#
# For instance, if application CMacTeX of signature "*XeT" supports the
# "tex" API, it can be registered with the following call:
#
#  api::registerAppPath tex CMacTeX *XeT "MyDisk:CMacTeX 4.0:CMacTeX" {
#    buildNewCMacTeXAE "tex $options &$format" $filename
#  }
#
# The last argument says that to implement the "tex" API with CMacTeX, one
# should execute "buildNewCMacTeXAE ...". The variable used in the script
# have the same name as the formal arguments of the API and their value
# will be the actual arguments when the script is executed.
proc api::registerAppPath {apiname appName appSig appPath script} {
  global api::applicationSigs
  global api::applicationPath

  if {[info commands api::apiNamed$apiname] == ""} {
    error "Undeclared API \"$apiname\""
  }

  # Remember the path to this application to be able to
  # launch it later.
  set api::applicationPath($appName) $appPath

  # Remember the signature of this application.
  set api::applicationSigs($appName) $appSig

  # Declare the proc that uses "script" to implement the API
  proc api::${appName}implementationOf$apiname \
   [linsert [info args api::apiNamed$apiname] 0 apiTarget \
   $script

}

# Choose an application to implement an API.
# Since several applications may support one API, one must choose an
# application before being able to invoke an API.
# Exemple:    api::chooseAppFor tex CMacTeX
proc api::chooseAppFor {apiname appName} {
  global api::currentApp

  if {[info commands api::apiNamed$apiname] == ""} {
    error "Undeclared API \"$apiname\""
  }
  if {[info commands api::${appName}implementationOf$apiname] == ""} {
    error "Application $appName does not support API \"$apiname\""
  }

  set api::currentApp($apiname) $appName
}

# Invoke an API, e.g. ask for something to be done using the current
# application for the API. The "interact" parameter should be set to
#   '-foreground'
# if the user is expected to interact with the application. It should be
#   '-background'
# if the user shouldn't interact with the application.
# The arguments must be in the form "-key value", where "key" is the
# name of a formal parameter of the API, and value is the value of this
# parameter:
#
#   api::invoke -foreground tex -filename hello.tex -format latex -options --src
#
# The '-' in front of the keys can be omitted, so:
#
#   api::invoke -foreground tex filename hello.tex format latex options --src
#
# is valid too.
#
# If a parameter is not set, its default value (as declared in the API) is
# used. If no default value is declared, this is an error (just like for a
# proc).
proc api::invoke {interact apiname args} {
  global api::currentApp
  global api::applicationSigs
  global api::applicationPath

  if {[info commands api::apiNamed$apiname] == ""} {
    error "Undeclared API \"$apiname\""
  }
  if {[info commands api::[set api::currentApp($apiname)]implementationOf$apiname] == ""} {
    error "Application \"$appName\" does not support API \"$apiname\""
  }

  switch -- "$interact" {
    "-foreground"  {set interaction 1}
    "-background"  {set interaction 0}
    default {
      error "Unknown value \"$interact\" for 'interact' parameter"
    }
  }

  # Get the list of formal parameters from the proc of the API
  set formalargs [info args api::apiNamed$apiname]

  # Find the value of the effective parameter by scanning the arguments
  set nargs [llength $args]
  for {set i 0} {$i < $nargs} {incr i} {
    # Ignore a leading '-' for convenience
    regexp -- {-?(.*)} [lindex $args $i] dummy argname
    # Search the name of the formal parameter in the list
    set argidx [lsearch $formalargs $argname]
    if {$argidx == -1} {
      error "Invalid argument name \"$argname\" for API \"$apiname\""
    }
    # Next argument is the value for $argname
    incr i
    if {$i < $nargs} {
      set effectiveargs($argname) [lindex $args $i]
    } else {
      error "Missing value after \"$argname\" key"
    }
  }

  # Use default value for unspecified parameters
  foreach arg $formalargs {
    if {![info exists effectiveargs($arg)]} {
      if {[info default api::apiNamed$apiname $arg defval]} {
        set effectiveargs($arg) $defval
      } else {
        error "No default value for unspecified parameter \"$arg\""
      }
    }
  }

  set arglist [list]
  foreach arg $formalargs {
    lappend arglist $effectiveargs($arg)
  }

  if {[info exists api::applicationPath([set api::currentApp($apiname)])]} {
    set appPath [set api::applicationPath([set api::currentApp($apiname)])]
    set apiTarget "[file tail $appPath]"
  } else {
    set appPath {}
    set apiTarget \
      "'[string trim [set api::applicationSigs([set api::currentApp($apiname)])] \']'"
  }
  api::executeApp \
    "[set api::applicationSigs([set api::currentApp($apiname)])]" \
    $appPath \
    $interaction \
    [eval list \
      api::[set api::currentApp($apiname)]implementationOf$apiname \
      $apiTarget $arglist\
    ]
}

# Execute an application.
# On MacOS, appsig is the creator of the application to launch.
# On other systems, I use it to give the name of the shell that should be
# used to interpret the commands. I hope Unix and Windows users will be
# able to adapt this to their needs.
#
# interact says if the user should interact with the application.
# If so, the application is brought to the foreground.
#
# command is the command to execute. It may refer to the formal
# parameters of the API which have been evaluated by api::invoke.
proc api::executeApp {appsig appPath interact command} {
  global alpha::platform

  if {${alpha::platform} == "alpha"} {
    if {$appPath == ""} {
      # We only have a signature for this application.
      # Check if it is running.
      app::ensureRunning $appsig $interact
    } else {
      # We have a path, so we must launch *this* application
      # even if another with the same signature is running.
      set cmd "launch"
      if {$interact} {
        lappend cmd "-f"
      }
      lappend cmd $appPath
      if {[catch {eval $cmd}]} {
        error "Could not launch $appPath"
      }
    }
    # Interpret command, which should send an AppleEvent
    eval $command
  } else {
    # Interpret command which is a Unix like command line.
    # Exact role of appSig and appPath on Unix-like systems?
    if {$interact} {
      eval exec $appsig $command
    } else {
      eval exec $appsig $command &
    }
  }
}
## 
##
## End of file `api.tcl'.
