/*global alert, console, hanoi */

/**
 * The olive global namespace object.
 * @module core
 */

/**
 *
 * @class olive
 * @global
 */
var olive = {};
olive.global = this;
 /**
  * Logs a message into the console (if available), or displays an alert box.
  * @method log
  * @param {String} message the message to display
  * @param {Boolean} displayAlert if supplied, displays the
  * message in an alert box
  * @return {Object} this for method chaining
  */
olive.log = function(message, displayAlert) {
    if (typeof console != "undefined") {
        console.log(message);
    }
    if (displayAlert) {
        alert(message);
    }

    return this;
};/**
 * @module core
 */

/**
 * Implements a method to define blocks of code as modules
 * @class modules
 * @namespace olive
 */
olive.modules = function() {

    return {

        /**
         * Loads the specificed modules and attaches them to the module namespace
         * @method init
         * @param {String} * Accepts unlimited module names as parameters.
         * @return {Object} this for method chaining
         */
        init: function() {
            var args = arguments,
                mod,
                i,
                len;
            for (i = 0, len = args.length; i < len; i += 1) {

                mod = args[i];

                /*
                * If the module is an object (literal) this means it should hold extra
                    parameters, that it needs to pass to module.init.
                * There are also checks to make sure the module has already been
                    evaluated (typeof module == "function").
                */
                if(typeof mod == "object") {
                    var key;
                    for (key in mod) {
                        if (mod.hasOwnProperty(key)) {
                            olive.modules[key].init(mod[key]);
                        }
                    }
                }
                else
                {
                    if (typeof olive.modules[mod] === "function") {
                        olive.modules[mod] = olive.modules[mod]();
                    }
                    olive.modules[mod].init();
                }
            }

            return this;
        },



        /**
         * Registers a module in olive.
         * @method register
         * @param {String} moduleName the name of the module to be acessible via
         * olive.modules.<moduleName>.*
         * @param {Object} module a singleton
         * @return {Object} this for method chaining
         */
        register: function(moduleName, module) {
            olive.modules[moduleName] = module;
            return this;
        }


    }
}();
/**
 * @module core
 */

/**
  * Implements cross-browser support for JavaScript 1.6 array functionalities
  * @class array
  * @namespace olive
  */
olive.array = function() {

    return {

        /**
        * Returns the array index of the searched item
        * @method indexOf
        * @return {Integer} the index where the search item is stored.
        * -1 if not found.
        */
        indexOf: function(arr, obj, opt_fromIndex) {

            if (arr.indexOf) {
                return arr.indexOf(obj);
            }

            // original from Google Closure Library
            var fromIndex = (opt_fromIndex === null || opt_fromIndex === undefined) ?
                0 : (opt_fromIndex < 0 ?
                    Math.max(0, arr.length + opt_fromIndex) : opt_fromIndex);
            for (var i = fromIndex; i < arr.length; i++) {
                if (i in arr && arr[i] === obj) {
                    return i;
                }
            }
            return -1;
        }

    };

}();/**
 * Provides utilities to deal with website/app urls. Provides functionality
 * for deep-linking Ajax calls, and a url mapper to generate dynamic urls.
 * @module urls
 */

/**
 * Utilities for  application urls
 *
 * @class urls
 * @namespace olive
 */

olive.urls = function() {

    var _urls = {},
        _compiled = {},
        _lastHash = '',
        _nameMatcher = new RegExp('<([a-zA-Z0-9-_%]{1,})>', 'g');

    function _getArgs(urlName, path) {

        var args = {},
            name_matches = _urls[urlName].match(_nameMatcher),
            value_matches = path.match(_compiled[urlName]);

        if (name_matches) {
            var i, len, arg;
            for (i=0, len=name_matches.length; i<len; i+=1) {
                arg = name_matches[i].substring(1, name_matches[i].length-1);
                args[arg] = value_matches[i+1];;
            }
        }

        return args;
    };


    function _getName(path) {
        if (!path) {
            return;
        }
        for (url in _compiled) {
            if (path.match(_compiled[url])) {
                return url;
            }
        }
        return;
    };

    /*Private: See if the browser has changed location. This is the primary history mechanism for Firefox. For IE, we use this to
    handle an important edge case: if a user manually types in a new hash value into their IE location bar and press enter, we want to
    to intercept this and notify any history listener.*/
    function _checkHash() {
        var currentHash = olive.urls.getHash();

        if (_lastHash !== currentHash) {
            _lastHash = currentHash;
            var url = olive.urls.resolve(currentHash);
            if (url) {
                olive.signals.trigger('url.' + url.name, url.kwargs);
            }
        }
    };


    return {

        /**
         * Checks if there is an initial url Anchor, and if so, triggers a listner
         * for the matching url
         * @method init
         * @static
         * @return {Object} this for method chaining
         */
        init: function(checkHashChanges) {
            if (!checkHashChanges) {
                var path = this.getHash(olive.global.location.href),
                    url;
                if (path) {
                    url = this.resolve(path);
                    if (url) {
                        olive.signals.trigger('url.' + url.name, url.kwargs);
                    }
                }
            } else {
                setInterval(_checkHash, 100);
            }
            return this;
        },

        /**
         * Changes the url URL Anchor to #<url>
         * @method set
         * @static
         * @param {String/Object} url a String url path like "/task/edit/5/" or an url object literal
         * like {'taskEdit': {'taskId': 5}
         * @return {Object} this for method chaining
         */
        set: function(url) {
            var path, urlName, args = {};

            if (!url) {
                throw('URL cannot be empty');
            }

            if (typeof url == 'object') {
                urlName = url[0];
                args = url[1]
                path = this.get(urlName, args);
            } else {
                path = url;
                urlName = _getName(url);

                if (urlName) {
                    args = _getArgs(urlName, path);
                }
            }

            _lastHash = encodeURI(path);
            olive.global.location.href = "#" + path;
            if (urlName) {
                olive.signals.trigger('url.' + urlName, args);
            }

            return this;
        },


        /**
         * Loads a set of urls names and paths
         * @method load
         * @static
         * @param {Object} urls an Object literal with names and paths like
         * {'taskEdit': '/task/edit/<taskId>/', 'taskCreate': '/task/create/'}
         * @return {Object} this for method chaining
         */
        load: function(urls) {

            for (url in urls) {
                if (urls.hasOwnProperty(url)) {
                    _compiled[url] = new RegExp('^' + urls[url].replace(_nameMatcher, "([a-zA-Z0-9-_%]{0,})") + '$');
                }
            }
            _urls = urls;
            return this;
        },

        /**
        * Returns a url from the list that matches the specified parameters.
        * A non-existant url will raise an exception.
        * @method get
        * @static
        * @param {string} name: url name to call
        * @param {string} kwargs: an option object literal with key/value
            that can be used to get urls that require parameters
        * @return {String} url path
        */
        get: function(name, kwargs) {

            var path = _urls[name];
            if (!path) {
                throw('URL not found: ' + name);
            }

            var _path = path;

            var key;
            for (key in kwargs) {
                if (kwargs.hasOwnProperty(key)) {
                    if (!path.match('<' + key +'>')) {
                        throw('Invalid parameter ('+ key +') for '+ name);
                    }
                    path = path.replace('<' + key +'>', kwargs[key]);
                }
            }

            var missing_args = path.match(_nameMatcher);
            if (missing_args) {
                throw('Missing arguments (' + missing_args.join(", ") + ') for url ' + _path);
            }

            return path;
        },

        /**
         * Recieves a url path, and returns a url object with the name and
         * variables if there is match in the url list
         * @method resolve
         * @static
         * @param {String} path the url path
         * @return {Object/undefined} url object or undefined
         */
        resolve: function(path) {
            var url = {},
                urlName,
                kwargs;
            urlName = _getName(path);
            //olive.log(url + ' => ' + urlName);
            if (urlName) {
                kwargs = _getArgs(urlName, path);
                url['name'] = urlName;
                url['kwargs'] = kwargs;
                return url;
            }

            return;
        },



        /**
         * Returns the Anchor path in a url
         * @method getHash
         * @static
         * @param path {String} optional location path
         * @return {String/undefined} url path or undefined
         */
        getHash: function (string) {
            var url;

            if (string) {
                url = string;
            } else {
                url = olive.global.location.href;
            }
            if (url.match(/#(.+)/)) {
                return url.replace(/([^#]+)#(.*)/, "$2");
            }
            return;
        }

    };

}();
/**
 * @module core
 */

/**
 * A "signal dispatcher" which helps to keep applications decoupled.
 * Signals allow certain senders to notify a set of receivers that
 * some action has taken place.
 * Signals in olive represent a variation of what is best known as the Observer Pattern.
 * @class signals
 * @namespace olive
 */

olive.signals = function(){

    var _listners = {};

    return {

        /**
         * Registers a signal listner
         * @method register
         * @static
         * @param {String} event the name of the event to listen to
         * @param {Function} callback the function to be called when
         * the event is triggered (with a "response object" as parameter)
         * @return {Object} this for method chaining
         */
        register: function(event, callback) {
            if (!_listners[event]) {
                _listners[event] = [];
            }
            var eventListner = _listners[event];
            if (olive.array.indexOf(eventListner, callback) === -1) {
                eventListner.push(callback);
            }

            return this;
        },

        /**
         * Removes a listner for the specified event.
         * @method unregister
         * @static
         * @param {String} event the name of the event to unregister
         * @param {Function} callback the function that should not be executed
         * @return {Object} this for method chaining
         */
        unregister: function(event, callback) {
            var eventListner = _listners[event];
            var id = olive.array.indexOf(eventListner, callback);
            eventListner.splice(id, id+1);
            return this;
        },


        /**
         * Triggers a signal/ calls the appropriate listners.
         * @method trigger
         * @static
         * @param {String} event the name of the event to trigger
         * @param {Object} data the "response object" that should be passed as
         * a parameter to the callbacks
         * @return {Object} this for method chaining
         */
        trigger: function(event, data) {
            var eventListner = _listners[event];
            if (!eventListner || !eventListner.length) {
                return;
            }

            var i, len;
            for (i = 0, len = eventListner.length; i < len; i += 1) {
                eventListner[i](data);
            }

            return this;
        }

    };

}();
