/*
 * importPackage.js 2002.03.13 sdc
 *
 * 2005.03.18 sdc Completely rewritten to allow packageInit (finally).
 * 
 * The importPackage function allows javscript source files residing in a common
 * directory to be imported into the current document according to import
 * declarations in the document or source files.
 *
 * A package name <package> refers to an accessible source file
 * <base><package>.js that should bind the window-level property name <package>.
 *
 * The base directory for imported packages (with trailing slash) is accessible
 * as the property importPackage.base, which can be either a url or a path
 * relative to the current document. This value defaults to "./", which is
 * usually wrong.
 * 
 * The importPackage function takes a package name and, if necessary, writes a
 * script element into the current document to source the appropriate file. As a
 * consequence, code executed in the same script element as a call to
 * importPackage cannot rely on definitions from the imported package, since its
 * source has not yet been loaded.
 *
 * The importPackage.packageInit function takes a procedure to be called after
 * the source files for all previously-imported packages has been loaded.
 *
 * Typical use in an html page:
 * 
 *   <script type="text/javascript" src="./script/importPackage.js"></script>
 *   <script type="text/javascript">
 *       importPackage.base = "./script/";
 *       importPackage("Test");
 *   </script>
 *   <script type="text/javascript">
 *       Test.test();                         <!-- Test is available here -->
 *   </script>
 *
 * Typical use in a javascript source file Test.js:
 *
 *   try {
 *       importPackage("Prereq");
 *   } catch (e) {}
 *
 *   function Test () { ... }
 *
 *   importPackage.packageInit(function () {
 *       Test.aTest = Test();                 // Prereq is available here
 *   });
 *
 * A few notes:
 *
 * A package name can be passed to importPackage multiple times; a script
 * element will be written to source the file only once. Intended use is for a
 * package to import every other package it references by name.
 *
 * The importPackage function will silently opt not to import a package whose
 * name is already bound in the top-level window object. Consequently, a call to
 * importPackage after a coded script element sourcing the same package has no
 * effect.
 *
 * FIXME: Package init procedures are called in reverse of the order that they
 * are registered with importPackage.packageInit. importPackage would have to
 * track the package name being currently imported, attach it to registered init
 * procedures and juggle this queue to call these procedures in any sort of
 * reasonable order.
 *
 */

if (!window.importPackage) {

  importPackage = function (name) {
    if (!window[name] && !importPackage.allPackages.contains(name)) {
      importPackage.allPackages.push(name);
      importPackage.waitingPackages.push(name);

      document.write("<script type='text/javascript' src='" +
        importPackage.base + name + ".js'></script>");
      document.write("<script type='text/javascript'>" +
	"importPackage.signalImportComplete('" + name + "');</script>");
    }
  }

  importPackage.packageInit = function (proc) {
    if (importPackage.waitingPackages.isEmpty()) {
      proc();
    } else if (!importPackage.initProcs.contains(proc)) {
      importPackage.initProcs.push(proc);
    }
  }

  importPackage.base = "./";

  importPackage.signalImportComplete = function(name) {
    importPackage.waitingPackages.remove(name);

    if (importPackage.waitingPackages.isEmpty()) {
      while (!importPackage.initProcs.isEmpty()) {
	(importPackage.initProcs.pop())();
      }
    }
  }

  importPackage.Stack = function () {
    this.array = new Array();

    this.push = function (obj) {
      this.array[this.array.length] = obj;
    }

    this.pop = function () {
      var obj = this.array[this.array.length-1];
      this.array.length--; return obj;
    }

    this.isEmpty = function (obj) {
      return (this.array.length == 0);
    }

    this.contains = function (obj) {
      return (0 <= this.getIndex(obj));
    }

    this.remove = function (obj) {
      var i = this.getIndex(obj);
      this.array = this.array.slice(0,i).concat(this.array.slice(i+1));
    }

    this.getIndex = function (obj) {
      for (var i = 0; i < this.array.length; i++) {
	if (this.array[i] == obj) {
	  return i;
	}
      }
      return -1;
    }
  }

  importPackage.allPackages = new importPackage.Stack();
  importPackage.waitingPackages = new importPackage.Stack();
  importPackage.initProcs = new importPackage.Stack();
}
