/* Because form validator is heavily OO, Class is carried over */

mdp.isSimilar = function(array1,array2) {
    return(array1.toString() == array2.toString())
};

mdp.complement = function(array1,array2) {
    for (var i = 0,j = array1.length; i < j; i++)array1[i] = (array1[i] || array2[i]) || null;
    return array1;
};

mdp.HistoryManager = {
    options:{
        observeDelay:100,
        stateSeparator:';',
        iframeSrc:'blank.html',
        onStart:function(){},
        onRegister:function(){},
        onUnregister:function(){},
        onUpdate:function(){},
        onStateChange:function(){},
        onObserverChange:function(){}
    },
    dataOptions:{
        skipDefaultMatch:true,
        defaults:[],
        regexpParams:''
    },
    initialize:function(options) {
        if (this.modules)return this;
        $.extend(this.options,options);
        var self = this;
        $.each(this.options,function(key,val){
            if(typeof(val) == "function"){
                $(self).bind(key,val);    
            }
        });
        this.modules = {};
        this.count = history.length;
        this.states = [];
        this.states[this.count] = this.getHash();
        this.state = null;
        return this;
    },
    start:function() {
        var self = this;
        setInterval(function(){self.observe.call(self)},self.options.observeDelay);
        this.started = true;
        this.observe();
        this.update();
        $(this).trigger('onStart', [this.state]);
        return this;
    },
    register:function(key, defaults, onMatch, onGenerate, regexp, options) {
        if (!this.modules)this.initialize();
        var data = $.extend({},this.dataOptions, options || {}, {defaults:defaults,onMatch:onMatch,onGenerate:onGenerate,regexp:regexp});
        data.regexp = data.regexp || key + '-([\\w_-]*)';
        if (typeof data.regexp == 'string')data.regexp = new RegExp(data.regexp, data.regexpParams);
        data.onGenerate = data.onGenerate || function(values) {
            return key + '-' + values[0]
        };
        data.values = $.merge([],data.defaults);
        this.modules[key] = data;
        $(this).trigger('onUnregister', [key,data]);
        return{
            setValues:function(values) {
                return this.setValues(key, values)
            },
            setValue:function(index, value) {
                return this.setValue(key, index, value)
            },
            generate:function(values) {
                return this.generate(key, values)
            },
            unregister:function() {
                return this.unregister(key)
            }
        }
    },
    unregister:function(key) {
        $(this).trigger('onRegister', [key]);
        delete this.modules[key];
    },
    setValues:function(key, values) {
        var data = this.modules[key];
        if (!data || mdp.isSimilar(data.values,values))return this;
        data.values = values;
        this.update();
        return this
    },
    setValue:function(key, index, value) {
        var data = this.modules[key];
        if (!data || data.values[index] == value)return this;
        data.values[index] = value;
        this.update();
        return this
    },
    generate:function(key, values) {
        var data = this.modules[key];
        var current = $.merge([],data.values);
        data.values = values;
        var state = this.generateState();
        data.values = current;
        return'#' + state
    },
    observe:function() {
        if (this.timeout)return;
        var state = this.getState();
        if (this.state == state)return;
        if ((window.ie || window.webkit419) && (this.state !== null))this.setState(state, true); else this.state = state;
        $.each(this.modules,function(key, data) {
            var bits = state.match(data.regexp);
            if (bits) {
                bits.splice(0, 1);
                mdp.complement(bits,data.defaults);
                if (!mdp.isSimilar(bits,data.defaults))data.values = bits
            } else data.values = $.merge([],data.defaults);
            data.onMatch(data.values, data.defaults)
        });
        $(this).trigger('onStateChange', [state]).trigger('onObserverChange', [state])
    },
    generateState:function() {
        var state = [];
        $.each(this.modules,function(key, data) {
            if (data.skipDefaultMatch && mdp.isSimilar(data.values,data.defaults))return;
            state.push(data.onGenerate(data.values))
        });
        return state.join(this.options.stateSeparator)
    },
    update:function() {
        if (!this.started)return this;
        var state = this.generateState();
        if ((!this.state && !state) || (this.state == state))return this;
        this.setState(state);
        $(this).trigger('onStateChange', [state]).trigger('onUpdate', [state]);
        return this
    },
    observeTimeout:function() {
        if (this.timeout)this.timeout = clearTimeout(this.timeout); else this.timeout = this.observeTimeout.delay(200, this)
    },getHash:function() {

        var href = top.location.href;
        var pos = href.indexOf('#') + 1;
        return(pos) ? href.substr(pos) : ''
    },
    getState:function() {
        var state = this.getHash();
        if (this.iframe) {
            var doc = this.iframe.contentWindow.document;
            if (doc && doc.body.id == 'state') {
                var istate = doc.body.innerText;
                if (this.state == state)return istate;
                this.istateOld = true
            } else return this.istate
        }
        if (window.webkit419 && history.length != this.count) {
            this.count = history.length;
            return this.states[this.count - 1] || state; }
        return state
    },
    setState:function(state, fix) {
        state = state || '';
        if (window.webkit419) {
            if (!this.form)this.form = $('<form>').attr({method:'get'}).appendTo(document.body);
            this.count = history.length;
            this.states[this.count] = state;
            this.observeTimeout();
            this.form.attr('action', '#' + state).trigger("submit");
        } else top.location.hash = state || '#';
        if (window.ie && (!fix || this.istateOld)) {
            if (!this.iframe) {
                this.iframe = $('<iframe>').attr({src:this.options.iframeSrc,styles:'visibility: hidden;'}).appendTo(document.body);
                this.istate = this.state
            }
            try {
                var doc = this.iframe.contentWindow.document;
                doc.open();
                doc.write('<html><body id="state">' + state + '</body></html>');
                doc.close();
                this.istateOld = false
            } catch(e) {
            }
        }
        this.state = state
    }
};


