/*
---
description: A Class that provides a cross-browser history-management functionaility, using the browser hash to store the application's state

license: MIT-style

authors:
- Arieh Glazer

requires:
- Core/1.3 : JSON
- HistoryManager/1.2: HashListener

provides: [HistoryManager]

...
*/

(function ($, undef) {

    HistoryManager = new Class({

        Extends: HashListener,

        options: {
            delimiter: '',
            serializeHash: null,
            deserializeHash: null,
            compat: false
        },

        state: {},
        stateCache: {},

        initialize: function (options) {
            this.parent(options);

            this.serializeHash = this.options.serializeHash || this.serializeHash;
            this.deserializeHash = this.options.deserializeHash || this.deserializeHash;

            this.addEvent('hashChanged', this.updateState.bind(this));
        },

        serializeHash: function (d) {
            return JSON.encode(d);
        },

        deserializeHash: function (d) {
            return JSON.decode(decodeURIComponent(d));
        },

        updateState: function (hash) {
            
            var $this = this;

            if (this.options.delimiter) hash = hash.substr(this.options.delimiter.length);

            hash = this.deserializeHash(hash);

            Object.each(this.state, function (value, key) {
                var nvalue, comperable, h_type;

                if (!hash || hash[key] === undef) {
                    nvalue = $this.state[key];

                    if ($this.options.compat) {
                        $this.fireEvent(key + '-removed', [nvalue]);
                    }

                    $this.fireEvent(key + ':removed', [nvalue]);
                    $this.fireEvent(key, [nvalue]);
                    delete $this.state[key];
                    delete $this.stateCache[key];
                    if (hash && hash[key]) delete hash[key];
                    return;
                }

                h_type = typeOf(hash[key]);

                comperable = (h_type == 'string' || h_type == 'number' || h_type == 'boolean') ? hash[key] : JSON.encode(hash[key]);
                
                if (comperable != $this.stateCache[key]) {
                    nvalue = hash[key];
                    $this.state[key] = nvalue;
                    $this.stateCache[key] = comperable;

                    if ($this.options.compat) {
                        $this.fireEvent(key + '-updated', [nvalue]);
                        $this.fireEvent(key + '-changed', [nvalue]);
                    }

                    $this.fireEvent(key + ':updated', [nvalue]);
                    $this.fireEvent(key + ':changed', [nvalue]);
                    $this.fireEvent(key, [nvalue]);
                }

                delete hash[key];
            });

            Object.each(hash, function (value, key) {
                $this.state[key] = value;

                v_type = typeOf(hash[key]);
                $this.stateCache[key] = (v_type == 'string' || v_type == 'number' || v_type == 'boolean') ? value : JSON.encode(value);

                if ($this.options.compat) {
                    $this.fireEvent(key + '-added', [value]);
                    $this.fireEvent(key + '-changed', [value]);
                }

                $this.fireEvent(key + ':added', [value]);
                $this.fireEvent(key + ':changed', [value]);
                $this.fireEvent(key, [value]);
            });
        },

        set: function (key, value) {
            var newState = Object.clone(this.state);

            newState[key] = value;

            this.updateHash(this.options.delimiter + this.serializeHash(newState));

            return this;
        },

        remove: function (key) {
            var newState = Object.clone(this.state);

            delete newState[key];

            this.updateHash(this.options.delimiter + this.serializeHash(newState));

            return this;
        }
    });

})(document.id);
