/**
 * This is a portion of the adjacent file prototypeextensions.js.
 * I modified the functionality to be able to specify a forced key
 * and default value in the History.Observer.dispatch() function in
 * order to keep everything working when the user goes back to the 
 * no # page.
 */


Prototype.X = {
};
/**
 * Prototype.X.Browser
 *
 * @desc Used to retrieve the browser version
 */
(function() {
    var nav       = navigator;
    var userAgent = ua = navigator.userAgent;
    var v         = nav.appVersion;
    var version   = parseFloat(v);

    Prototype.X.Browser = {
        IE      : (Prototype.Browser.IE)    ? parseFloat(v.split("MSIE ")[1]) || 0 : 0,
        Firefox : (Prototype.Browser.Gecko) ? parseFloat(ua.split("Firefox/")[1]) || 0 : 0,
        Camino  : (Prototype.Browser.Gecko) ? parseFloat(ua.split("Camino/")[1]) || 0 : 0,
        Flock   : (Prototype.Browser.Gecko) ? parseFloat(ua.split("Flock/")[1]) || 0 : 0,
        Opera   : (Prototype.Browser.Opera) ? version : 0,
        AIR     : (ua.indexOf("AdobeAIR") >= 0) ? 1 : 0,
        Mozilla : (Prototype.Browser.Gecko || !this.Khtml) ? version : 0,
        Khtml   : (v.indexOf("Konqueror") >= 0 && this.safari) ? version : 0,
        Safari  : (function() {
            var safari = Math.max(v.indexOf("WebKit"), v.indexOf("Safari"), 0);
            return (safari) ? (
                parseFloat(v.split("Version/")[1]) || ( ( parseFloat(v.substr(safari+7)) >= 419.3 ) ? 3 : 2 ) || 2
            ) : 0;
        })()
    };
})();

/**
 * History
 *
 * @desc Provides basic methods to manage the history browsing.
 */
var History = {
    __altered: false,
    __currentHash: null,
    __previousHash: null,
    __iframe: false,
    __title: false,
    
    /**
     * init()
     * @desc Initialize the hash. Call this method in first
     */
    init: function() {
        var inst  = this;
        var hash  = location.hash.substring(1);
        this.hash = $H(hash.toQueryParams());
        this.__currentHash  = hash;
        this.__previousHash = hash;

        this.__title = document.title;
        
        if(Prototype.Browser.IE && Prototype.X.Browser.IE < 8) {
            document.observe('dom:loaded', function(e) {
                if(!$('px-historyframe')) {
                    History.__iframe = new Element('iframe', {
                        name   : 'px-historyframe',
                        id     : 'px-historyframe',
                        src    : '',
                        width  : '0',
                        height : '0',
                        style  : {
                            visibility: 'hidden'
                        }
                    });
                    
                    document.body.appendChild(History.__iframe);
                    
                    History.setHashOnIframe(inst.hash.toQueryString());
                }
            });
        }
    },
    
    /**
     * set( string $name, string $value )
     *
     * @desc Set new value $value for parameter $name
     */
    set: function($name, $value) {
        this.__previousHash = this.hash.toQueryString();
        this.hash.set($name, $value);
        this.apply();
    },
    
    /**
     * get( string $name )
     *
     * @desc Get value parameter $name
     */
    get: function($name) {
        return this.hash.get($name);
    },
    
    /**
     * unset( string $name )
     *
     * @desc Unset parameter $name
     */
    unset: function($name) {
        this.hash.unset($name);
        this.apply();
    },
    
    /**
     * update()
     *
     * @desc Updates this.hash with the current hash
     */
    update: function() {
        this.__previousHash = this.hash.toQueryString();
        var hash = window.location.hash.substring(1);

        // If IE, look in the iframe if the hash is updated
        if(Prototype.Browser.IE && Prototype.X.Browser.IE < 8 && this.__iframe ) {
            var hashInFrame = this.getHashOnIframe();
            
            if(hashInFrame != hash) {
                hash = hashInFrame;
            }
        }

        this.hash = $H(hash.toQueryParams());
        this.__currentHash = hash;
    },
    
    /**
     * apply()
     *
     * @desc Apply this.hash to location.hash
     */
    apply: function() {
        var newHash = this.hash.toQueryString();

        // set new hash
        window.location.hash = newHash;
        
        // If IE, apply new hash to frame for history    
        if(Prototype.Browser.IE && Prototype.X.Browser.IE < 8 && this.__iframe) {
            if(this.__currentHash != newHash) 
            {
                this.setHashOnIframe(newHash);   
            }
            else if(newHash != this.getHashOnIframe()) 
            {
                this.setHashOnIframe(newHash);    
            }
        }
    },

    /**
     * isAltered()
     *
     * @desc Return true if current hash is different of previous hash.
     * this.__altered allows to force the dispatch.
     */
    isAltered: function() {
        if(this.__altered == true) {
            return true;
        }
        this.__altered = false;

        return (History.__currentHash != History.__previousHash);
    },
    
    /**
     * setHashOnIframe()
     *
     * @use  For IE compatibility
     * @desc Set hash value on iframe
     */
    setHashOnIframe: function(hash) {
        try {
            var doc = History.__iframe.contentWindow.document;
            doc.open();
            doc.write('<html><body id="history">' + hash + '</body></html>');
            doc.close();
        } catch(e) {}
    },
    
    /**
     * getHashOnIframe()
     *
     * @use  For IE compatibility
     * @desc Get hash value on iframe
     */
    getHashOnIframe: function() {
        var doc = this.__iframe.contentWindow.document;
        if (doc && doc.body.id == 'history') {
            return doc.body.innerText;
        } else {
            return this.hash.toQueryString();
        }
    },
    
    /**
     * setTitle()
     *
     * @desc Set a new title for window
     */
    setTitle: function(title) {
        if(document.title) {
            document.title = title;
        }
    },
    
    /**
     * getTitle()
     *
     * @desc Return current window title
     */
    getTitle: function() {
        return this.__title;
    }
};

History.init();
 
/**
 * History.Registry
 *
 * @desc Used to register a callback for a parameter
 */
History.Registry = 
{
    /**
     * @desc Hash
     */
    hash : new Hash(),
    
    /**
     * set( string $config )
     *
     * @desc Set new value $historyId for parameter $config
     */
    set: function($config) {
        if(typeof($config) != 'object') {
            throw('History.Registry.set : $config must be an javascript object');
        } 
        
        // id
        if(!$config.id || !Object.isString($config.id)) {
            throw('History.Registry.set : $config.id must be an string');
        } 
        
        // onChange
        if(!$config.onStateChange || !Object.isFunction($config.onStateChange)) {
            throw('History.Registry.set : $config.onStateChange '
                + 'must be an javascript callback function');
        }
    
        // defaultValue
        if(!$config.defaultValue || !Object.isString($config.defaultValue)) {
            $config.defaultValue = '';
        }
                
        this.hash.set($config.id, $config);
    },
    
    /**
     * get( string $id )
     *
     * @desc Get value parameter $id
     */
    get: function($id) {
        return this.hash.get($id);
    },
    
    /**
     * unset( string $id )
     *
     * @desc Unset parameter $id
     */
    unset: function($id) {
        this.hash.unset($id);
    }
}

/**
 * History.Observer
 *
 * @desc Used to perform actions defined in the registry, 
 * according to the hash of the url.
 */
History.Observer = {

    /**
     * @desc Interval delay in seconds
     */
    delay : 0.2,
    
    /**
     * @desc Interval timer instance
     */
    interval : null,
    
    /**
     * @desc If interval is started : true, else false
     */
    started : false,
    
    required : new Hash(),
    
    /**
     * start()
     *
     * @desc Start a interval timer
     */
    start: function() {
        if(this.started) return;
        this.interval = new PeriodicalExecuter(History.Observer.dispatch, this.delay);
        this.started = true;
    },
    
    /**
     * stop()
     *
     * @desc Stop the interval timer
     */
    stop: function() {
        if(!this.started) return;
        this.interval.stop();
        this.started = false;
    },
    
    setRequired: function(key, defaultValue) {
      this.required.set(key, defaultValue);
    },
    
    /**
     * dispatch()
     *
     * @desc This method is called each time interval, 
     * the dispatch of the registry is implemented only if 
     * the hash has been amended (optimisiation)
     */
    dispatch: function() {
    
        // Update the hash, filling History.hash with location.hash, hashed.
        History.update();
            
        // Dispatch only if location.hash has been altered
        if(History.isAltered()) {
        
            //if(console) console.log('pass');
            //console.log("previous:");
            //console.dir($H(History.__previousHash.toQueryParams()));
            //console.log("current:");
            //console.dir($H(History.hash));
            History.hash.each(function(pair)  {
                var registry = History.Registry.get(pair.key);
                if(registry) {
                   registry.onStateChange.bind(History)( pair.value );
                }
            });
            
            History.Observer.required.each(function(pair) {
              if(!History.hash.get(pair.key)) {
                var registry = History.Registry.get(pair.key);
                if(registry) {
                   registry.onStateChange.bind(History)( pair.value );
                }
              }
            });
        }
    }
};
