/** scriptLoader {singleton}
 *  Provides a means to asynchronously load javascript files.  The primary
 *  method, 'load', also accepts a second function argument that will
 *  defer execution until page load.  This ensures that all expected scripts
 *  and elements are present and accounted for before script execution begins.
 */

NNLM.ScriptLoader = function(){
    var googleAPIKeys = {
        "dev_nnlm_gov": 'ABQIAAAAJknBlaenKCe5l6Nj8UBD-BTUHTvS5PfItlKOWHmTZxhWMejmWxSq9w5T04Wu14swiXYZ-MkZiTfaJw',
        "nnlm_gov": 'ABQIAAAAJknBlaenKCe5l6Nj8UBD-BSuPkKs8fuY3jYx6VEyXVqoolvyFRTMAC6tqsKrWN3LknxxYst7CJU_gQ',
        "staff_nnlm_gov": 'ABQIAAAAJknBlaenKCe5l6Nj8UBD-BQJNgKKB9VP-O8ygSBP1qlR0E9ZVRTIFMM6Ab0Q3x5WxhiItblmy7T6pw',
        "dev_staff_nnlm_gov": 'ABQIAAAAJknBlaenKCe5l6Nj8UBD-BQADrzkbHCkz6e8dA6DBSwFcY6w7hSk20KWepxz4ckxmZwNDCWGewmZUg',
        "emergency_nnlm_gov": 'ABQIAAAAJknBlaenKCe5l6Nj8UBD-BSwQv2I8ihwK-I3Nevt6E0wdDnqxxRGJg0pwHPrQajBDB5BbLqtd1d4rA'
    };
    var activeGoogleAPIKey = googleAPIKeys[
        document.URL.replace(/^https?:\/\/([a-z.]+)\/.*/,"$1").replace(/\./g, '_')];
    var scripts = {
        "general": "/scripts/general.js",
        "mail": "/scripts/mail.js",
        "roundedcorners": "/scripts/rounded_corners_lite.inc.js",
        "wordpress": "/scripts/general_wordpress.js",
        "validator": "http://ajax.microsoft.com/ajax/jquery.validate/1.6/jquery.validate.min.js",
        "general_adv": "/scripts/GenericFunctions.js",
        "rgbcolor": "/scripts/rgbcolor.js",
        "wz_tooltip": "/scripts/wz_tooltip.js",
        "jquery": "http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js",
        "yuieditor": "/scripts/yuieditor.js",
        "urchin": "http://www.google-analytics.com/urchin.js",
        "addthis": "/scripts/addthis.js",
        "debugging" : '/scripts/debugging.js',
        'prototype': 'http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.2/prototype.js',
        'linkedlist': '/scripts/linkedList.js',
        'scriptaculous': 'http://ajax.googleapis.com/ajax/libs/scriptaculous/1.8.1/scriptaculous.js',
        'mootools': 'http://ajax.googleapis.com/ajax/libs/mootools/1.11/mootools-yui-compressed.js',
        'dojo': 'http://ajax.googleapis.com/ajax/libs/dojo/1.1.1/dojo/dojo.xd.js',
        'extcore': '/scripts/ext/ext-core.js',
        'extall': '/scripts/ext/ext-all.js',
        'googleloader': 'http://www.google.com/jsapi?key='+activeGoogleAPIKey,
        'googlefeeds':
            'http://www.google.com/uds/solutions/dynamicfeed/gfdynamicfeedcontrol.js',
        'feedmaker': '/scripts/feedMaker.js'
    };
    var included = {};
    var callbacks = {};
    /** loader
     *  nested innner class that serves as a handler for either a single
     *  script or an array of scripts, and a callback function to be
     *  executed when the scripts and the document are loaded.
     */
    var Loader = function(){
        var toBeLoaded = 0;
        var id = false;

        /** _loadscript
         *  a recursive function that loads all scripts before executing a
         *  final function.  The recursive nature is to accomodate for accepting
         *  either a string or an array as an argument, thus loading one or many
         *  prerequisite scripts before executing a dependent javascript
         *  function or functions.
         *  This function has a design guarantee that all scripts it is
         *  created to fetch are not yet available.  This guarantee is
         *  satisfied by the parent class.
         *
         *  @param mixed {mixed} the string or array of strings representing
         *  the src parameter for a javascript file dependency.  This string
         *  may be a literal reference, or one of the named references in the
         *  hash "scripts" within this class.
         *  @returns {void}
         *  */
        var _loadscript = function(mixed){
            if (typeof mixed == 'object' &&
                typeof mixed.splice !== undefined &&
                typeof mixed.splice === 'function'){ //array
                for (var s in mixed){
                    if (mixed.hasOwnProperty(s)){
                        _loadscript(mixed[s]);
                    }
                }
                return;
            }
            //guarantee that this point, we are dealing with a
            //string.

            mixed = mixed.toString();
            dump('\nDownloading script ' + mixed);
            //do a hash lookup if it's a named script (not a literal js source).
            var src = mixed.match(/.*js$/) ? mixed : scripts[mixed];

            //bind a new callback closure to each specific script name.
            var mycallback = function(name, _startTime){
                return function(){
                    toBeLoaded--;
                    dump('\nScript ' + name + ' loaded. toBeloaded: ' + toBeLoaded);
                    if (toBeLoaded <= 0){
                        dump('\nScriptLoader: All scripts are finished loading.');
                        NNLM.bindLoadFn(callbacks[id]);
                    }
                };
            }(mixed, new Date().getTime());
            var newscript = document.createElement("script");
            newscript.type="text/javascript";
            //one callback binding for IE...
            newscript.onreadystatechange = function() {
                if (this.readyState == 'loaded' || this.readyState == 'complete') {
                    mycallback();
                }
            };
            //one for everyone else.
            newscript.onload = function(){
                mycallback();
            };
            newscript.src = src;
            //begin download
            document.getElementsByTagName("head")[0].appendChild(newscript);
        };
        var result = {
            /** load
             * helper function designed to invoke the recursive
             * callback function.
             * @param arr {array} a list of string arguments denoting
             * either site relative script source paths, or predefined
             * names of scripts.  See _loadscript parameter mixed for more info.
             * @returns {null}
             */
            load: function (arr, _id){
                id = _id;
                toBeLoaded += arr.length;
                _loadscript(arr);
                //prevent use of the same loader more than once.
                this.load = undefined;
            }
        };
        return result;
    };

    /**
     * _loaded
     * checks whether a script has been previously loaded by a
     * separate invocation of the scriptloader.  Stores the script
     * passed as having been loaded.
     * @param str {string} the url for the script to be loaded
     * @returns {boolean} true if the script was previously loaded,
     * false otherwise.
     */
    var _loaded = function(str){
        if (included[str] === undefined){
            included[str] = true;
            return false;
        }
        return true;
    };

    /**
     * doLoad
     * creates a loader object to load a script.
     * @param mixed {mixed} either a string representing the script
     * source, or an array of the same
     * @param id {number} the hash id in the 'callback' hash of the
     * callback function to execute once
     * all scripts in param 1 are loaded.
     * @returns {void}
     */
    var doLoad = function (mixed, id){
        var myloader = new Loader();
        myloader.load(mixed, id);
    };

    var result = {
        /** load
         *  loads a named script if defined as one of the supported preloadable
         *  scripts.
         *
         *  @param mixed {array, string} the shorthand name of the script,
         *  a literal script name, or an array of either of those.
         *  @param _callback {function} a function to be executed once:
         *  1) all scripts listed as required by the function are loaded.
         *      a) a list of required scripts is usually passed as an array
         *      for the first argument
         *  2) the window onload() event has fired.
         *  @returns {void}
         */
        load: function (arr, _callback){
                /* eliminate already loaded scripts */
            var str = '\nNew scriptloader load.  Scripts to be loaded:';
            _callback = _callback === undefined ? function(){} : _callback;
            dump(str);
            var tmp = [];
            for (var s in arr){
                if (arr.hasOwnProperty(s)){
                    if (!_loaded(arr[s])){
                        tmp.push(arr[s]);
                        dump("\n* "+arr[s]);
                    }
                }
            }
            arr = tmp;
            if (arr.length === 0){
                dump('\nscript load already complete - callback function ' +
                     'shall be bound immediately to page load');
                NNLM.bindLoadFn(function(){
                    _callback();
                });
                return;
            }
            else{
                var id = new Date().getTime();
                while(callbacks[id] !== undefined){
                    id += 1;
                }
                callbacks[id] = _callback;
                var myloader = new Loader();

                myloader.load(arr, id);
                return;
            }
        },
        /** loadObj
         *  provides access to the "load" method, with a few more parameters
         *  permissible, along with room for parameter expansion in the future
         *  by making the argument an object.  Permissible object keys:
         *      fn: the callback function (if any)
         *      scripts: an array of scripts to be downloaded.  May be literal
         *          or preformatted
         *      debug: level of display for debugging messages on the command
         *          line.  Values are 0, 1, 2, and 3, with 0 being off, and 3
         *          being all.
         *      fnname: a name for the passed callback function.  This name
         *          is referenced in debug messages.
         *  @param obj {object} a hashkey of arguments.  permissible keys
         *  defined above.
         *  @returns [see load].
         */
        loadObj: function (obj){

        },
        /** require
         * alias for load
         */
        require: function (mixed, _callback){
            this.load(mixed, _callback);
        }
    };
    return result;
}();
