﻿/**
 * @name Base namespace and utils
 * @author Tim savage
 * @copyright (c) 2009 Devnet
 * @fileoverview Defines the base Devnet namespace and several util functions
 */

// Create devnet namespace
if (window.devnet === undefined) {
    window.devnet = {};
}

/*global devnet, google, YAHOO */
(function () {

    /**
     * Do nothing placeholder
     * @method doNothing
     */
    devnet.doNothing = function () {};

    /**
     * Log info/warn/error message(s) to console
     * @method log
     * @param {String} message Message to log to console
     */
    if (window.DEBUG === true && typeof(console) !== 'undefined') {
        devnet.log = function (message) {
            console.log(message);
        };
        devnet.log.info = function (message) {
            console.info(message); 
        };
        devnet.log.warn = function (message) { 
            console.error(message); 
        };
        devnet.log.error = function (message) { 
            console.error(message); 
        };
    } else {
        // Impliment as empty functions
        devnet.log = devnet.doNothing;
        devnet.log.info = devnet.doNothing;
        devnet.log.warn = devnet.doNothing;
        devnet.log.error = devnet.doNothing;
    }

    
    /**
     * Util namespace
     */
    if (devnet.util === undefined) {
        devnet.util = {};
    }
    
    // Helper for isFunction/isArray
    var toString = Object.prototype.toString;
    
    /**
     * Check if an object is a Function
     * @method isFunction
     * @param {Object} obj Object to check
     */
    devnet.util.isFunction = function (obj) {
        return toString.call(obj) === "[object Function]";
    };

    /**
     * Check if an object is an Array
     * @method isArray
     * @param {Object} obj Object to check
     */
    devnet.util.isArray = function (obj) {
        return toString.call(obj) === "[object Array]";
    };
    
    /**
     * Loop over an Object or Array
     * @method forEach
     * @param {Object|Array} o Object to iterate over
     * @param {Function} callback Callback function
     * @param {Object} [scope] Scope of the callback method default is o
     */
    devnet.util.forEach = function (o, callback, scope) {
        scope = scope || o;

        if (devnet.util.isArray(o)) {
            // Iterate over array calling callback method
            for (var i = 0; i < o.length; i = i + 1) {
                callback.call(scope, o[i], i);
            }
        } else {
            // Iterate over object calling callback method
            for (var k in o) {
                if (o.hasOwnProperty(k)) {
                    callback.call(scope, o[k], k);
                }
            }
        }
    };

    /**
     * Return all the keys from an object
     * @method keys
     * @param {Object|Array} o Object to iterate over
     * @return {Array} Key array
     */
    devnet.util.keys = function (o) {
        var r = [];
        devnet.util.forEach(o, function (o, k) {
            r.push(k);
        });
        return r;
    };

    
    /**
     * Loader namespace
     */
    devnet.loader = {};
    
    /**
     * Loader configuration
     */
    devnet.loader.config = {
        // Base path for none external scripts
        local_base: 'assets/js/'
    };
    
    /**
     * Load style sheets (without callback due to FF lack of support)
     * @mathod _loadStyleSheet
     * @param (String) src Source URL of style sheet
     * @param (Object) optionalSettings Optional additonal settings
     * @private
     */
    devnet.loader._loadStyleSheet = function (src, optionalSettings) {

        // Create CSS element
        var s = document.createElement('link');
        s.type = 'text/css';
        s.rel = 'stylesheet';
        s.href = src;
        
        // Append to DOM
        document.getElementsByTagName("head")[0].appendChild(s);
        
        // Log final src URL
        devnet.log.info("devnet.load: Added - " + s.href);
        
    };
    
    /**
     * Load script with a callback
     * @method _loadScript
     * @param {String/Array} src Source URL(s) of script(s).
     * @param {Object} optionalSettings Optional additional settings.
     *      @param {Function} callback The function to call once the script has loaded.
     * @private
     */
    devnet.loader._loadScript = function (src, optionalSettings) {
        var c = devnet.loader.config;
        
        // Check for a local scripts
        if (src && src.charAt(0) === '@') {
            src = c.local_base + src.substr(1) + '.js';
        }
    
        // Create script element
        var s = document.createElement("script");
        s.type = "text/javascript";
        s.src = src;
        
        // Attach events
        s.onreadystatechange = function () {
            // For IE (cannot detect load errors in IE...)
            var rs = this.readyState;
            if ("loaded" === rs || "complete" === rs) {
                s.onreadystatechange = null;
                this.onload();
            }
        };
        s.onload = function () {
            devnet.log.info("devnet.load: Complete - " + this.src);
            if (optionalSettings.callback) {
                optionalSettings.callback.call(this, true);
            }
        };
        s.onerror = function () {
            devnet.log.error("devnet.load: Failed - " + this.src);
            if (optionalSettings.callback) {
                optionalSettings.callback.call(this, false);
            }
        };
        
        // Log final src URL
        devnet.log.info("devnet.load: Queued - " + s.src);
        
        // Append to DOM
        document.getElementsByTagName("head")[0].appendChild(s);    
    };

    /**
     * Load external script with a callback
     * @method load
     * @param {String} src Source URL(s) of script(s).
     * @param {Object} optionalSettings Optional additional settings.
     *      @param {Function} callback The function to call once the script has loaded.
     */
    devnet.load = function (src, optionalSettings) {
        optionalSettings = optionalSettings || {};
        return devnet.loader._loadScript(src, optionalSettings);
    };
    
    /**
     * Load external style sheet WITHOUT a callback (due to FF lack of support)
     * @method loadCss
     * @param (String) src Source URL of style sheet
     * @param (Object) optionalSettings Optional additonal settings
     *      @param (Function) callback The function to call once the script has loaded
     */
    devnet.loadCss = function (src, optionalSettings) {
        optionalSettings = optionalSettings || {};
        return devnet.loader._loadStyleSheet(src, optionalSettings);
    };

    /**
     * Load external scripts in dependancy order
     * @class DependancyLoader
     * @param {Object{Object}} src Source definition.
     * @param {Object} [optionalSettings] Optional additional settings.
     *      @param {Function} callback The function to call once the script has loaded.
     *      @param {Function} progress Callback method for progress of loading.
     */
    devnet.loader.DependancyLoader = function (sources, optionalSettings) {
        this.opts = optionalSettings || {};
        this.sources = sources;
        this.loaded = {};
        this.failed = {};
        this.loading = {};
    };
    devnet.loader.DependancyLoader.prototype = {
    
        /**
         * Load scripts
         * @method load
         */
        load: function () {
            // Starting is progress!
            if (this.opts.progress) {
                this.opts.progress.call(this);
            }
            
            this._loadNext();
        },
        
        getStatus: function () {
            var k = devnet.util.keys;
            return k(this.loaded).length / k(this.sources).length;
        },
    
        /**
         * Callback method
         * @method _oncomplete
         * @param {String} key Key used to identify package
         * @param {Boolean} status Load status pass / fail.
         * @private
         */
        _oncomplete: function (key, status) {
            var k = devnet.util.keys,
                o = this.opts;
            
            // Log status
            devnet.log.info('DependancyLoader: Complete ' + key);
            
            // Mark item loaded
            if (status === false) {
                this.failed[key] = true;
            } else {
                this.loaded[key] = true;
            }
            
            // Update progress
            if (o.progress) {
                o.progress.call(this);
            }
            
            // Check if we are done 
            if (k(this.failed).length) {
                // Failed to load
                if (o.error) {
                    o.error.call(this);
                }
            } else if (k(this.loaded).length === k(this.sources).length) {
                // We are done!
                if (o.callback) {
                    o.callback.call(this);
                }
            } else {
                // Load next package
                this._loadNext();
            }
        },
    
        /**
         * Check requirements are met
         * @method _reqsMet
         * @param {Array} reqs Required values
         * @private
         */
        _reqsMet: function (reqs) {
            // Check for underfined (ie no requirements) requirements
            if (!reqs) {
                return true;
            }
            
            // Check loaded list
            for (var i = 0; i < reqs.length; i = i + 1) {
                if (!this.loaded[reqs[i]]) {
                    return false;
                }
            }
            return true;
        },
        
        /**
         * Load files from YUI
         * @method _loadYui
         * @param {Object} source Source package
         * @param {Function} callback Completion callback
         * @private
         */
        _loadYui: function (source, callback) {
            var loader = new YAHOO.util.YUILoader(source.settings);
            loader.onSuccess = callback;
            loader.insert();
        },
        
        /**
         * Load files from Google
         * @method _loadGoogle
         * @param {Object} source Source package
         * @param {Function} callback Completion callback
         * @private
         */
        _loadGoogle: function (source, callback) {
            var other_params = source.other_params || '';
            google.load(source.name, source.version, { 
                'callback': callback,
                'other_params': other_params
            });
        },
        
        /**
         * Load plain JS files
         * @method _loadJs
         * @param {Object} source Source package
         * @param {Function} callback Completion callback
         * @private
         */
        _loadJs: function (source, callback) {
            devnet.loader._loadScript(source.src, {
                callback: callback
            });
        },
        
        /**
         * Load source factory method
         * @method _loadSource
         * @param {Object} source Source package
         * @param {Function} callback Completion callback
         * @private
         */
        _loadSource: function (source, callback) {
            switch (source.type) {
            case 'yui':
                this._loadYui(source, callback);
                break;
                
            case 'google':
                this._loadGoogle(source, callback);
                break;
                
            default:
                this._loadJs(source, callback);
                break;
            }
        },
        
        /**
         * Load the next valid package
         * @method _loadNext
         * @private
         */
        _loadNext: function () {
            var s = this.sources,
                self = this;
            // Loop through packages
            devnet.util.forEach(s, function (source, key) {
                // Is the item not loaded and the requirements are met 
                if (!this.loaded[key] && !this.loading[key] && this._reqsMet(source.requires)) {
                    // Report status
                    devnet.log.info('DependancyLoader: Queue<' + source.type + '> ' + key);
                    
                    // Mark this package as being loaded
                    this.loading[key] = true;
                    
                    // Call the loader factory (note use of closure here)
                    (function (key) {
                        self._loadSource(source, function (result) {
                            self._oncomplete(key, result);
                        });
                    }(key));
                }
            }, this);
        },
        
        /**
         * Generate a HTML representation of loading
         * @method toHtml
         * @return {String}
         */
        toHtml: function () {
            var result = [];
            
            // Loop through sources
            devnet.util.forEach(this.sources, function (source, key) {
                // Determine class
                var className = 'waiting';
                if (this.failed[key]) {
                    className = 'error';
                } else if (this.loaded[key]) {
                    className = 'loaded';
                } else if (this.loading[key]) {
                    className = 'loading';
                }
                
                // Generate HTML
                result.push('<li class="' + className + '">' + source.title + '</li>');
            }, this);
            return '<ul>' + result.join('') + '</ul>';
        }
    };

}());