/**
 * @author vpavlyuk
 *
 * Depends on jQuery History plugin
 */


SF.ns('History', function(){
    
    var callbacks = {
            special: {},
            general: []
        },
        hObj = {
            keys: {},
            chunks: []
        },
        $ = jQuery,
        GS = '|', //Group separator
        KS = '=', //Key separator
        AS = ';', //Arguments separator
        NVS = ':';//Name-value separator

    
    /**
     * Split hash and store it as object.
     * 
     * @param {String} hash
     */
    function parseHash(hash) {
        //Sample: has is resource=tag:102,We-are-the-best;type:2,Tradeshow;region:4,North-America|gallery=album:34,California
        var ch = hash.split(GS),
            obj = {},
            chunks = [],
            i = 0,
            k = 0,
            a, b, c, d;
            
        //ch equals ['resource=tag:102,We-are-the-best;type:2,Tradeshow;region:4,North-America', 'gallery=album:34,California']
        for(; i < ch.length; i++) {
            a = ch[i].split(KS);
            //For the first iteration a = ['resource', 'tag:102,We-are-the-best;type:2,Tradeshow;region:4,North-America']
            if(a.length > 1) {
                obj[a[0]] = {};
                b = a[1].split(AS);
                // b = ['tag:102,We-are-the-best', 'type:2,Tradeshow', 'region:4,North-America']
                for(k = 0; k < b.length; k++) {
                    c = b[k].split(NVS);
                    if(c.length > 1) {
                    // c = ['tag', '102,We-are-the-best']
                        obj[a[0]][c[0]] = c[1];
                    } else {
                        obj[a[0]] = c[0];
                    }
                }
            } else { //If hash doesn't match key=arguments pattern
                chunks.push(a[0]);
            }
        }
        /*Result:
            hObj = {
                keys: {
                    resource: {
                        tag: '102, We-are-the-best',
                        type: '2, Tradeshow',
                        region: '4, North-America'
                    },
                    gallery: {
                        album: '34, California'
                    }
                },
                chunks: []
            }
        */
        
        return { keys: obj, chunks: chunks };
    }


    /**
     * Make string from hashObj
     * 
     * @param {Object} hashObj
     */
    function encodeHash(hashObj) {
        var keys = [], args = [];
        
        for(var key in hashObj.keys) {
            args = [];
            if($.isPlainObject(hashObj.keys[key])) {
                for(var arg in hashObj.keys[key]) {
                    args.push(arg + NVS + hashObj.keys[key][arg]);
                }
            } else {
                args.push(hashObj.keys[key] + '');
            }
            keys.push(key + KS + args.join(AS));
        }
        
        for(var i = 0; i < hashObj.chunks.length; i++) {
            keys.push(hashObj.chunks[i]);
        }
        
        return keys.join('|');
    }
    

    /**
     * This method is called when URL hash is chnaged. 
     * 
     * @param {String} hash
     */
    function processHash(hash) {
        hObj = parseHash(hash);
        
        // Execute callbacks according to the hash object
        for(var key in hObj.keys) {
            if(callbacks.special.hasOwnProperty(key) && $.isFunction(callbacks.special[key].m)) {
                callbacks.special[key].m.call(callbacks.special[key].c, hObj.keys[key], encodeHash(hObj));
            }
        }
        
        for(var i = 0; i < callbacks.general.length; i++) {
            callbacks.general[i].m.call(callbacks.general[i].c, hObj, encodeHash(hObj));
        }
    }
    
    
    SF.addToDOMReady(function(){
        setTimeout(function(){$.historyInit(processHash)}, 10)
    }, 'last');
    
    return {
        /**
         * Flag indicates that history is active
         */
        ready: true,
        
        /**
         * This method registers callback which is called when URL hash is changed.
         * 
         * @param {String/Function} key - Key which must be unique OR callback method if key isn't needed
         * @param {Function/Object} method - Method to call OR callback context (this)
         * @param {Object} context - Object which is set as <b>this</b> in method
         */
        addCallback: function(key, method, context) {
            if($.isFunction(key)) {
                callbacks.general.push({m: key, c: method})
            } else {
                callbacks.special[key] = {m: method, c: context};
            }
        },
        
        /**
         * Update segment of URL hash related to specified key.
         * 
         * @param {Object/String/Function} hashObj - Sample:
         *                           {
         *                               resource: {
         *                                   tag: '102, We-are-the-best',
         *                                   type: '2, Tradeshow',
         *                                   region: '4, North-America'
         *                               }
         *                           }
         */
        load: function(hashObj) {
            //Add new values to current hash and execute callbacks
            if(typeof hashObj === 'string') {
                if($.inArray(hashObj, hObj.chunks) == -1) {
                    hObj.chunks.push(hashObj);
                }
            } else if($.isPlainObject(hashObj)) {
                SF.extend(hObj.keys, hashObj);
            } else if($.isFunction(hashObj)) {
                var r = hashObj.call();
                if($.isPlainObject(r)) {
                    SF.extend(true, hObj.keys, r);
                } else if(typeof r === 'string' && $.inArray(r, hObj.chunks) == -1) {
                    hObj.chunks.push(r);
                }
            }
            $.historyLoad(encodeHash(hObj));
        },
        
        /**
         * Check if there is callback registered with specified key.
         * 
         * @param {String} key
         */
        keyExist: function(key) {
            return callbacks.keys.hasOwnProperty(key) ? true : false;
        }
    }
    
}());


