// {{{ICINGA_LICENSE_CODE}}}
// -----------------------------------------------------------------------------
// This file is part of icinga-web.
// 
// Copyright (c) 2009-2013 Icinga Developer Team.
// All rights reserved.
// 
// icinga-web is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// icinga-web is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with icinga-web.  If not, see <http://www.gnu.org/licenses/>.
// -----------------------------------------------------------------------------
// {{{ICINGA_LICENSE_CODE}}}

Ext.ns('Ext.ux.util');

/**
 * Clone Function
 * @param {Object/Array} o Object or array to clone
 * @return {Object/Array} Deep clone of an object or an array
 * @author Ing. Jozef Sakáloš
 */
Ext.ux.util.clone = function(o) {
    if(!o || 'object' !== typeof o) {
        return o;
    }
    if('function' === typeof o.clone) {
        return o.clone();
    }
    var c = '[object Array]' === Object.prototype.toString.call(o) ? [] : {};
    var p, v;
    for(p in o) {
        if(o.hasOwnProperty(p)) {
            v = o[p];
            if(v && 'object' === typeof v) {
                c[p] = Ext.ux.util.clone(v);
            }
            else {
                c[p] = v;
            }
        }
    }
    return c;
}; // eo function clone 

// vim: ts=4:sw=4:nu:fdc=2:nospell
/*global Ext, console */
/**
 * @class Ext.ux.state.HttpProvider
 * @extends Ext.state.Provider
 *
 * Buffering state provider that sends and receives state information to/from server
 *
 * @author    Ing. Jozef SakÃ¡loÅ¡
 * @copyright (c) 2008, Ing. Jozef SakÃ¡loÅ¡
 * @version   1.2
 * @revision  $Id: Ext.ux.state.HttpProvider.js 589 2009-02-21 23:30:18Z jozo $
 * @depends   Ext.ux.util
 *
 * @license Ext.ux.state.HttpProvider is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 * 
 * <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
 * target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
 *
 * @forum     24970
 * @demo      http://cellactions.extjs.eu
 *
 * @donate
 * <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
 * <input type="hidden" name="cmd" value="_s-xclick">
 * <input type="hidden" name="hosted_button_id" value="3430419">
 * <input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-butcc-donate.gif" 
 * border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">
 * <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
 * </form>
 */

Ext.ns('Ext.ux.state');

/**
 * Creates new HttpProvider
 * @constructor
 * @param {Object} config Configuration object
 */
// {{{
Ext.ux.state.HttpProvider = function(config) {

    this.addEvents(
        /**
         * @event readsuccess
         * Fires after state has been successfully received from server and restored
         * @param {HttpProvider} this
         */
         'readsuccess'
        /**
         * @event readfailure
         * Fires in the case of an error when attempting to read state from server
         * @param {HttpProvider} this
         */
        ,'readfailure'
        /**
         * @event savesuccess
         * Fires after the state has been successfully saved to server
         * @param {HttpProvider} this
         */
        ,'savesuccess'
        /**
         * @event savefailure
         * Fires in the case of an error when attempting to save state to the server
         * @param {HttpProvider} this
         */
        ,'savefailure'
    );

    // call parent 
    Ext.ux.state.HttpProvider.superclass.constructor.call(this);

    Ext.apply(this, config, {
        // defaults
         delay:750 // buffer changes for 750 ms
        ,dirty:false
        ,started:false
        ,autoStart:true
        ,autoRead:true
        ,user:'user'
        ,id:1
        ,session:'session'
        ,logFailure:false
        ,logSuccess:false
        ,queue:[]
        ,url:'.'
        ,readUrl:undefined
        ,saveUrl:undefined
        ,method:'post'
        ,saveBaseParams:{}
        ,readBaseParams:{}
        ,paramNames:{
             id:'id'
            ,name:'name'
            ,value:'value'
            ,user:'user'
            ,session:'session'
            ,data:'data'
        }
    }); // eo apply

    if(this.autoRead) {
        this.readState();
    }

    this.dt = new Ext.util.DelayedTask(this.submitState, this);
    if(this.autoStart) {
        this.start();
    }
}; // eo constructor
// }}}

Ext.extend(Ext.ux.state.HttpProvider, Ext.state.Provider, {

    // localizable texts
     saveSuccessText:'Save Success'
    ,saveFailureText:'Save Failure'
    ,readSuccessText:'Read Success'
    ,readFailureText:'Read Failure'
    ,dataErrorText:'Data Error'

    // {{{
    /**
     * Initializes state from the passed state object or array.
     * This method can be called early during page load having the state Array/Object
     * retrieved from database by server.
     * @param {Array/Object} state State to initialize state manager with
     */
    ,initState:function(state) {
        if(state instanceof Array) {
            Ext.each(state, function(item) {
                this.state[item.name] = this.decodeValue(item[this.paramNames.value]);
            }, this);
        }
        else {
            this.state = state ? state : {};
        }
    } // eo function initState
    // }}}
    // {{{
    /**
     * Sets the passed state variable name to the passed value and queues the change
     * @param {String} name Name of the state variable
     * @param {Mixed} value Value of the state variable
     */
    ,set:function(name, value) {
        if(!name) {
            return;
        }

        this.queueChange(name, value);

    } // eo function set
    // }}}
    // {{{
    /**
     * Starts submitting state changes to server
     */
    ,start:function() {
        this.dt.delay(this.delay);
        this.started = true;
    } // eo function start
    // }}}
    // {{{
    /**
     * Stops submitting state changes
     */
    ,stop:function() {
        this.dt.cancel();
        this.started = false;
    } // eo function stop
    // }}}
    // {{{
    /**
     * private, queues the state change if state has changed
     */
    ,queueChange:function(name, value) {
        var changed = undefined === this.state[name] || this.state[name] !== value;
        var o = {};
        var i;
        var found = false;
        if(changed) {
            o[this.paramNames.name] = name;
            o[this.paramNames.value] = this.encodeValue(value);
            for(i = 0; i < this.queue.length; i++) {
                if(this.queue[i].name === o.name) {
                    this.queue[i] = o;
                    found = true;
                }
            }
            if(false === found) {
                this.queue.push(o);
            }
            this.dirty = true;
        }
        if(this.started) {
            this.start();
        }
        return changed;
    } // eo function bufferChange
    // }}}
    // {{{
    /**
     * private, submits state to server by asynchronous Ajax request
     */
    ,submitState:function() {
        if(!this.dirty) {
            this.dt.delay(this.delay);
            return;
        }
        this.dt.cancel();

        var o = {
             url:this.saveUrl || this.url
            ,method:this.method
            ,scope:this
            ,success:this.onSaveSuccess
            ,failure:this.onSaveFailure
            ,queue:Ext.ux.util.clone(this.queue)
            ,params:{}
        };

        var params = Ext.apply({}, this.saveBaseParams);
        params[this.paramNames.id] = this.id;
        params[this.paramNames.user] = this.user;
        params[this.paramNames.session] = this.session;
        params[this.paramNames.data] = Ext.encode(o.queue);

        Ext.apply(o.params, params);

        // be optimistic
        this.dirty = false;

        Ext.Ajax.request(o);
    } // eo function submitState
    // }}}
    // {{{
    /**
     * Clears the state variable
     * @param {String} name Name of the variable to clear
     */
    ,clear:function(name) {
        this.set(name, undefined);
    } // eo function clear
    // }}}
    // {{{
    /**
     * private, save success callback
     */
    ,onSaveSuccess:function(response, options) {
        var o = {};
        try {o = Ext.decode(response.responseText);}
        catch(e) {
            if(true === this.logFailure) {
                this.log(this.saveFailureText, e, response);
            }
            this.dirty = true;
            return;
        }
        if(true !== o.success) {
            if(true === this.logFailure) {
                this.log(this.saveFailureText, o, response);
            }
            this.dirty = true;
        }
        else {
            Ext.each(options.queue, function(item) {
                if(!item) {
                    return;
                }
                var name = item[this.paramNames.name];
                var value = this.decodeValue(item[this.paramNames.value]);

                if(undefined === value || null === value) {
                    Ext.ux.state.HttpProvider.superclass.clear.call(this, name);
                }
                else {
                    // parent sets value and fires event
                    Ext.ux.state.HttpProvider.superclass.set.call(this, name, value);
                }
            }, this);
            if(false === this.dirty) {
                this.queue = [];
            }
            else {
                var i, j, found;
                for(i = 0; i < options.queue.length; i++) {
                    found = false;
                    for(j = 0; j < this.queue.length; j++) {
                        if(options.queue[i].name === this.queue[j].name) {
                            found = true;
                            break;
                        }
                    }
                    if(true === found && this.encodeValue(options.queue[i].value) === this.encodeValue(this.queue[j].value)) {
                        this.queue.remove(this.queue[j]);
                    }
                }
            }
            if(true === this.logSuccess) {
                this.log(this.saveSuccessText, o, response);
            }
            this.fireEvent('savesuccess', this);
        }
    } // eo function onSaveSuccess
    // }}}
    // {{{
    /**
     * private, save failure callback
     */
    ,onSaveFailure:function(response, options) {
        if(true === this.logFailure) {
            this.log(this.saveFailureText, response);
        }
        this.dirty = true;
        this.fireEvent('savefailure', this);
    } // eo function onSaveFailure
    // }}}
    // {{{
    /**
     * private, read state callback
     */
    ,onReadFailure:function(response, options) {
        if(true === this.logFailure) {
            this.log(this.readFailureText, response);
        }
        this.fireEvent('readfailure', this);

    } // eo function onReadFailure
    // }}}
    // {{{
    /**
     * private, read success callback
     */
    ,onReadSuccess:function(response, options) {
        var o = {}, data;
        try {o = Ext.decode(response.responseText);}
        catch(e) {
            if(true === this.logFailure) {
                this.log(this.readFailureText, e, response);
            }
            return;
        }
        if(true !== o.success) {
            if(true === this.logFailure) {
                this.log(this.readFailureText, o, response);
            }
        }
        else {
            data = o[this.paramNames.data];
            if(!(data instanceof Array) && true === this.logFailure) {
                this.log(this.dataErrorText, data, response);
                return;
            }
            Ext.each(data, function(item) {
//              console.log([item[this.paramNames.name], this.decodeValue(item[this.paramNames.value])]);
                this.state[item[this.paramNames.name]] = this.decodeValue(item[this.paramNames.value]);
            }, this);
//          console.log(this.state);
            this.queue = [];
            this.dirty = false;
            if(true === this.logSuccess) {
                this.log(this.readSuccessText, data, response);
            }
            this.fireEvent('readsuccess', this);
        }
    } // eo function onReadSuccess
    // }}}
    // {{{
    /**
     * Reads saved state from server by sending asynchronous Ajax request and processing the response
     */
    ,readState:function() {
        var o = {
             url:this.readUrl || this.url
            ,method:this.method
            ,scope:this
            ,success:this.onReadSuccess
            ,failure:this.onReadFailure
            ,params:{}
        };

        var params = Ext.apply({}, this.readBaseParams);
        params[this.paramNames.id] = this.id;
        params[this.paramNames.user] = this.user;
        params[this.paramNames.session] = this.session;

        Ext.apply(o.params, params);
        Ext.Ajax.request(o);
    } // eo function readState
    // }}}
    // {{{
    /**
     * private, logs errors or successes
     */
    ,log:function() {
        if(console) {
            console.log.apply(console, arguments);
        }
    } // eo log
    // }}}
    
    ,encodeValue : function(v) {
        return Ext.util.JSON.encode(v);
    }
    
    ,decodeValue : function(v) {
        return Ext.util.JSON.decode(v);
    }

}); // eo extend

// eof