/**
 * CMinder.Module.js: module and namspace utilities
 * 
 * This is a module of module-related utility functions that are
 * compatible with JSAN-type modules.
 * This module defines the namespace CMinder.Module.
 */

// Make sure we haven't already been loaded
var CMinder
if (CMinder && (typeof CMinder != "object" || CMinder.NAME))
    throw new Error("Namespace 'CMinder' already exists");
// Create our namespace
CMinder = {};

if (CMinder.Module && (typeof CMinder.Module != "object" || CMinder.Module.NAME))
    throw new Error("Namespace 'CMinder.Module' already exists");

// Create our namespace
CMinder.Module = {};

// This is some metainformation about this namespace
CMinder.Module.NAME = "CMinder.Module";    // The name of this namespace
CMinder.Module.VERSION = 0.1;      // The version of this namespace

// This is the list of public symbols that we export from this namespace.
// These are of interest to programers who use modules
CMinder.Module.EXPORT = ["require", "importSymbols"];

// These are other symbols we are willing to export. They are ones normally
// used only by module authors and are not typically imported
CMinder.Module.EXPORT_OK = ["createNamespace", "isDefined",
                    "registerInitializationFunction",
                    "runInitializationFunctions",
                    "modules", "globalNamespace"];


// Now start adding symbols to the namespace
CMinder.Module.globalNamespace = this;  // So we can always refer to the global scope
CMinder.Module.modules = { "CMinder.Module": CMinder.Module };  // Module name->namespace map.

/**
 * This function creates and returns a namespace object for the
 * specified name and does useful error checking to ensure that the
 * name does not conflict with any previously loaded module.  It
 * throws an error if the namespace already exists or if any of the
 * property components of the namespace exist and are not objects.
 *
 * Sets a NAME property of the new namespace to its name.
 * If the version argument is specified, set the VERSION property
 * of the namespace.
 * 
 * A mapping for the new namespace is added to the CMinder.Module.modules object
 */
CMinder.Module.createNamespace = function(name, version) {
    // Check name for validity.  It must exist, and must not begin or
    // end with a period or contain two periods in a row.
    if (!name) throw new Error("CMinder.Module.createNamespace(): name required");
    if (name.charAt(0) == '.' ||
        name.charAt(name.length-1) == '.' ||
        name.indexOf("..") != -1) 
        throw new Error("CMinder.Module.createNamespace(): illegal name: " + name);

    // Break the name at periods and create the object hierarchy we need
    var parts = name.split('.');

    // For each namespace component, either create an object or ensure that
    // an object by that name already exists.
    var container = CMinder.Module.globalNamespace;
    for(var i = 0; i < parts.length; i++) {
        var part = parts[i];
        // If there is no property of container with this name, create
        // an empty object.
        if (!container[part]) container[part] = {};
        else if (typeof container[part] != "object") {
            // If there is already a property, make sure it is an object
            var n = parts.slice(0,i).join('.');
            throw new Error(n + " already exists and is not an object");
        }
        container = container[part];
    }
    
    // The last container traversed above is the namespace we need.
    var namespace = container;

    // It is an error to define a namespace twice.  It is okay if our
    // namespace object already exists, but it must not already have a 
    // NAME property defined.
    if (namespace.NAME) throw new Error("Module "+name+" is already defined");

    // Initialize name and version fields of the namespace
    namespace.NAME = name;
    if (version) namespace.VERSION = version;
    
    // Register this namespace in the map of all modules
    CMinder.Module.modules[name] = namespace;

    // Return the namespace object to the caller
    return namespace;
}


/**
 * Test whether the module with the specified name has been defined.
 * Returns true if it is defined and false otherwise.
 */
CMinder.Module.isDefined = function(name) {
    return name in CMinder.Module.modules;
};

/**
 * This function throws an error if the named module is not defined
 * or if it is defined but its version is less than the specified version.
 * If the namespace exists and has a suitable version, this function simply
 * returns without doing anything. Use this function to cause a fatal
 * error if the modules that your code requires are not present.
 */
CMinder.Module.require = function(name, version) {
    if (!(name in CMinder.Module.modules)) {
        throw new Error("Module " + name + " is not defined");
    }

    // If no version was specified, there is nothing to check
    if (!version) return;

    var n = CMinder.Module.modules[name];

    // If the defined version is less than the required version or of
    // the namespace does not declare any version, throw an error.
    if (!n.VERSION || n.VERSION < version)
    throw new Error("Module " + name + " has version " +
                    n.VERSION + " but version " + version +
                    " or greater is required.");
};

/**
 * This function imports symbols from a specified module.  By default, it
 * imports them into the global namespace, but you may specify a different
 * destination as the second argument.
 *
 * If no symbols are explicitly specified, the symbols in the EXPORT
 * array of the module will be imported.  If no such array is defined,
 * and no EXPORT_OK is defined, all symbols from the module will be imported. 
 *
 * To import an explicitly specified set of symbols, pass their names as
 * arguments after the module and the optional destination namespace. If the
 * modules defines an EXPORT or EXPORT_OK array, symbols will be imported
 * only if they are listed in one of those arrays.
 */
CMinder.Module.importSymbols = function(from) {
    // Make sure that the module is correctly specified.  We expect the
    // module's namespace object but will try with a string, too
    if (typeof from == "string") from = CMinder.Module.modules[from];
    if (!from || typeof from != "object")
        throw new Error("CMinder.Module.importSymbols(): " + 
                        "namespace object required");

    // The source namespace may be  followed by an optional destination
    // namespace and the names of one or more symbols to import;
    var to = CMinder.Module.globalNamespace; // Default destination
    var symbols = [];                // No symbols by default
    var firstsymbol = 1;             // Index in arguments of first symbol name

    // See if a destination namespace is specified
    if (arguments.length > 1 && typeof arguments[1] == "object") {
        if (arguments[1] != null) to = arguments[1]; 
        firstsymbol = 2;
    }

    // Now get the list of specified symbols
    for(var a = firstsymbol; a < arguments.length; a++)
        symbols.push(arguments[a]);
    
    // If we were not passed any symbols to import, import a set defined
    // by the module, or just import all of them.
    if (symbols.length == 0) {
        // If the module defines an EXPORT array, import
        // the symbols in that array.
        if (from.EXPORT) {
            for(var i = 0; i < from.EXPORT.length; i++) {
                var s = from.EXPORT[i];
                to[s] = from[s];
            }
            return;
        }
        // Otherwise if the modules does not define an EXPORT_OK array,
        // just import everything in the module's namespace
        else if (!from.EXPORT_OK) {
            for(s in from) to[s] = from[s];
            return;
        }
    }

    // If we get here, we have an explicitly specified array of symbols
    // to import. If the namespace defines EXPORT and/or EXPORT_OK arrays,
    // ensure that each symbol is listed before importing it.
    // Throw an error if a requested symbol does not exist or if 
    // it is not allowed to be exported
    var allowed;
    if (from.EXPORT || from.EXPORT_OK) {
        allowed = {};
        // Copy allowed symbols from arrays to properties of an object.
        // This allows us to test for an allowed symbol more efficiently.
        if (from.EXPORT) 
            for(var i = 0; i < from.EXPORT.length; i++)
                allowed[from.EXPORT[i]] = true;
        if (from.EXPORT_OK)
            for(var i = 0; i < from.EXPORT_OK.length; i++)
                allowed[from.EXPORT_OK[i]] = true;
    }
    
    // Import the symbols
    for(var i = 0; i < symbols.length; i++) {
        var s = symbols[i];              // The name of the symbol to import
        if (!(s in from))                // Make sure it exists
            throw new Error("CMinder.Module.importSymbols(): symbol " + s +
                            " is not defined");
        if (allowed && !(s in allowed))  // Make sure it is a public symbol
            throw new Error("CMinder.Module.importSymbols(): symbol " + s +
                            " is not public and cannot be imported.");
        to[s] = from[s];                 // Import it
    }
};


// Modules use this function to register one or more initialization functions
CMinder.Module.registerInitializationFunction = function(f) {
    // Store the function in the array of initialization functions
    CMinder.Module._initfuncs.push(f);
    // If we have not yet registered an onload event handler, do so now.
    CMinder.Module._registerEventHandler();
}

// A function to invoke all registered initialization functions.
// In client-side JavaScript, this will automatically be called in
// when the document finished loading.  In other contexts, you must
// call it explicitly.
CMinder.Module.runInitializationFunctions = function() {
    // Run each initialization function, catching and ignoring exceptions
    // so that a failure by one module does not prevent other modules
    // from being initialized.
    for(var i = 0; i < CMinder.Module._initfuncs.length; i++) {
        try { CMinder.Module._initfuncs[i](); }
        catch(e) { /* ignore exceptions */}
    }
    // Erase the array so the functions are never called more than once.
    CMinder.Module._initfuncs.length = 0;
}

// A private array holding initialization functions to invoke later
CMinder.Module._initfuncs = [];

// If we are loaded into a web browser, this private function registers an 
// onload event handler to run the initialization functions for all loaded 
// modules. It does not allow itself to be called more than once.
CMinder.Module._registerEventHandler = function() {
    var clientside =   // Check for well-known client-side properties
        "window" in CMinder.Module.globalNamespace &&
        "navigator" in window;

    if (clientside) {
        if (window.addEventListener) {  // W3C DOM standard event registration
            window.addEventListener("load", CMinder.Module.runInitializationFunctions, 
                                    false);
        }
        else if (window.attachEvent) {  // IE5+ event registration
            window.attachEvent("onload", CMinder.Module.runInitializationFunctions);
        }
        else {
            // IE4 and old browsers
            // If the <body> defines an onload tag, this event listener
            // will be overwritten and never get called.
            window.onload = CMinder.Module.runInitializationFunctions;
        }
    }

    // The function overwrites itself with an empty function so it never
    // gets called more than once.
    CMinder.Module._registerEventHandler = function() {};
}


/**
 * defineClass() -- a utility function for defining JavaScript classes.
 *
 * This function expects a single object as its only argument.  It defines
 * a new JavaScript class based on the data in that object and returns the
 * constructor function of the new class.  This function handles the repetitive
 * tasks of defining classes: setting up the prototype object for correct
 * inheritance, copying methods from other types, and so on.
 * 
 * The object passed as an argument should have some or all of the
 * following properties:
 *
 *      name: the name of the class being defined.
 *            If specified, this value will be stored in the classname
 *            property of the prototype object.
 * 
 *    extend: The constructor of the class to be extended.  If omitted,
 *            the Object() constructor will be used.  This value will
 *            be stored in the superclass property of the prototype object.
 *
 * construct: The constructor function for the class. If omitted, a new
 *            empty function will be used.  This value becomes the return
 *            value of the function, and is also stored in the constructor
 *            property of the prototype object.
 *
 *   methods: An object that specifies the instance methods (and other shared
 *            properties) for the class.  The properties of this object are
 *            copied into the prototype object of the class.  If omitted,
 *            an empty object is used instead.  Properties named
 *            "classname", "superclass", and "constructor" are reserved
 *            and should not be used in this object.
 *
 *   statics: An object that specifies the static methods (and other static
 *            properties) for the class.  The properties of this object become
 *            properties of the constructor function.  If omitted, an empty
 *            object is used instead.
 *
 *   borrows: A constructor function or array of constructor functions.
 *            The instance methods of each of the specified classes are copied
 *            into the prototype object of this new class so that the 
 *            new class borrows the methods of each specified class.
 *            Constructors are processed in the order they are specified,
 *            so the methods of a class listed at the end of the array may
 *            overwrite the methods of those specified earlier. Note that
 *            borrowed methods are stored in the prototype object before
 *            the properties of the methods object above.  Therefore,
 *            methods specified in the methods object can overwrite borrowed
 *            methods. If this property is not specified, no methods are
 *            borrowed.
 *
 *  provides: A constructor function or array of constructor functions.
 *            After the prototype object is fully initialized, this function
 *            verifies that the prototype includes methods whose names and
 *            number of arguments match the instance methods defined by each
 *            of these classes.  No methods are copied; this is simply an
 *            assertion that this class "provides" the functionality of the
 *            specified classes.  If the assertion fails, this method will
 *            throw an exception.  If no exception is thrown, any
 *            instance of the new class can also be considered (using "duck
 *            typing") to be an instance of these other types.  If this
 *            property is not specified, no such verification is performed.
 **/
CMinder.Module.defineClass = function(data) {
    // Extract the fields we'll use from the argument object.
    // Set up default values.
    var classname = data.name;
    var superclass = data.extend || Object;
    var constructor = data.construct || function() {};
    var methods = data.methods || {};
    var statics = data.statics || {};
    var borrows;
    var provides;

    // Borrows may be a single constructor or an array of them.
    if (!data.borrows) borrows = [];
    else if (data.borrows instanceof Array) borrows = data.borrows;
    else borrows = [ data.borrows ];

    // Ditto for the provides property.
    if (!data.provides) provides = [];
    else if (data.provides instanceof Array) provides = data.provides;
    else provides = [ data.provides ];

    // Create the object that will become the prototype for our class.
    var proto = new superclass();

    // Delete any noninherited properties of this new prototype object.
    for(var p in proto) 
        if (proto.hasOwnProperty(p)) delete proto[p];

    // Borrow methods from "mixin" classes by copying to our prototype.
    for(var i = 0; i < borrows.length; i++) {
        var c = data.borrows[i];
        borrows[i] = c;
        // Copy method properties from prototype of c to our prototype
        for(var p in c.prototype) {
            if (typeof c.prototype[p] != "function") continue;
            proto[p] = c.prototype[p];
        }
    }

    // Copy instance methods to the prototype object
    // This may overwrite methods of the mixin classes
    for(var p in methods) proto[p] = methods[p];

    // Set up the reserved "constructor", "superclass", and "classname"
    // properties of the prototype.
    proto.constructor = constructor;
    proto.superclass = superclass;
    // classname is set only if a name was actually specified.
    if (classname) proto.classname = classname;

    // Verify that our prototype provides all of the methods it is supposed to.
    for(var i = 0; i < provides.length; i++) {  // for each class
        var c = provides[i];
        for(var p in c.prototype) {   // for each property
            if (typeof c.prototype[p] != "function") continue;  // methods only
            if (p == "constructor" || p == "superclass") continue;
            // Check that we have a method with the same name and that
            // it has the same number of declared arguments.  If so, move on
            if (p in proto &&
                typeof proto[p] == "function" &&
                proto[p].length == c.prototype[p].length) continue;
            // Otherwise, throw an exception
            throw new Error("Class " + classname + " does not provide method "+
                            c.classname + "." + p);
        }
    }

    // Associate the prototype object with the constructor function
    constructor.prototype = proto;

    // Copy static properties to the constructor
    for(var p in statics) constructor[p] = data.statics[p];

    // Finally, return the constructor function
    return constructor;
}




