Ext.ns('Nexts.state');

/**
 * @class Nexts.state.HttpProvider
 * @extends Ext.state.Provider
 * @constructor
 * @param {Object} config Configuration object
 */
Nexts.state.HttpProvider = function(config) {

	Ext.apply(this, config, {
		// defaults
		delay: 60, // buffer changes for 60s
		autoLoad: false,
		queue: [],
		url: '.',
		readUrl: undefined,
		saveUrl: undefined,
		method: 'post',
		baseParams: {},
		saveBaseParams: {},
		readBaseParams: {},
		paramNames: {
			name:'name',
			value:'value',
			data:'data'
		}
	}); 

	this.addEvents(
		/**
		 * @event loadsuccess
		 * Fires after state has been successfully received from server and restored
		 * @param {HttpProvider} this
		 */
		 'loadsuccess',
		/**
		 * @event loadfailure
		 * Fires in the case of an error when attempting to load state from server
		 * @param {HttpProvider} this
		 */
		'loadfailure',
		/**
		 * @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 
	Nexts.state.HttpProvider.superclass.constructor.call(this);

	if (this.autoLoad) {
		this.loadState();
	}
	delete this.autoLoad;
	
	this.dt = new Ext.util.DelayedTask(this.saveState, this);
}; 


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

	/**
	 * 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.value);
			}, this);
		}
		else {
			this.state = state ? state : {};
		}
	},

	/**
	 * 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);
	},

	/**
	 * Starts submitting state changes to server
	 */
	start: function() {
		this.dt.delay(this.delay * 1000);
	},

	/**
	 * Stops submitting state changes
	 */
	stop: function() {
		this.dt.cancel();
	},

	/**
	 * private, queues the state change if state has changed
	 */
	queueChange: function(name, value) {
		var changed = undefined === this.state[name] || this.state[name] !== value;

		if (changed) {
			if (undefined === value || null === value) {
				Nexts.state.HttpProvider.superclass.clear.call(this, name);
			}
			else {
				Nexts.state.HttpProvider.superclass.set.call(this, name, value);
			}

			var found = false;
			for (var i = 0; i < this.queue.length; i++) {
				if (this.queue[i].name === name) {
					this.queue[i].value = value;
					found = true;
				}
			}
			if (false === found) {
				this.queue.push({
					name: name,
					value: value
				});
			}
			
			this.start();
		}
		return changed;
	},

	/**
	 * private, save state to server by asynchronous Ajax request
	 */
	saveState: function() {
		if (this.queue.length < 1) {
			this.start();
			return;
		}
		this.stop();

		var queue = this.queue;
		
		this.queue = [];
		
		var pn = this.paramNames.name;
		var pv = this.paramNames.value;
		var pd = this.paramNames.data;

		var params = Ext.apply({}, this.saveBaseParams || this.baseParams);

		for (var i = 0; i < queue.length; i++) {
			params[pd + '[' + i + '].' + pn] = queue[i].name;
			params[pd + '[' + i + '].' + pv] = this.encodeValue(queue[i].value);
		}

		Ext.Ajax.request({
			 url: this.saveUrl || this.url
			,method: this.method
			,scope: this
			,success: this.onSaveSuccess
			,failure: this.onSaveFailure
			,params: params
			,queue: queue
		});
	},

	/**
	 * Clears the state variable
	 * @param {String} name Name of the variable to clear
	 */
	clear: function(name) {
		this.set(name, undefined);
	},

	/**
	 * private, save failure callback
	 */
	onSaveFailure: function(response, options) {
		for (var i = 0; i < options.queue.length; i++) {
			var found = false;
			for (var j = 0; j < this.queue.length; j++) {
				if (options.queue[i].name === this.queue[j].name) {
					found = true;
					break;
				}
			}
			
			if (true === found && options.queue[i].value !== this.queue[j].value) {
				this.queue[j] = options.queue[i];
			}
			else {
				this.queue.push(options.queue[i]);
			}
		}
		
		this.fireEvent('savefailure', this, response, options);

		delete options.queue;
	},

	/**
	 * private, save success callback
	 */
	onSaveSuccess: function(response, options) {
		var o = {};
		try {
			o = Ext.decode(response.responseText);
		}
		catch(e) {
			this.onSaveFailure(response, options);
			return;
		}
		
		if (true !== o.success) {
			this.onSaveFailure(response, options);
		}
		else {
			this.fireEvent('savesuccess', this, response, options);
		}
	},

	/**
	 * private, load state callback
	 */
	onLoadFailure: function(response, options) {
		this.fireEvent('loadfailure', this, response, options);
	},

	/**
	 * private, load success callback
	 */
	onLoadSuccess: function(response, options) {
		var o = {};
		try {
			o = Ext.decode(response.responseText);
		}
		catch (e) {
			this.onLoadFailure(response, options);
			return;
		}
		
		if (true === o.success) {
			if (!o.data || !(o.data instanceof Array)) {
				this.onLoadFailure(response, options);
				return;
			}

			Ext.each(o.data, function(item) {
				this.state[item[this.paramNames.name]] = this.decodeValue(item[this.paramNames.value]);
			}, this);

			this.fireEvent('loadsuccess', this, response, options);
		}
		else {
			this.onLoadFailure(response, options);
		}
	},

	/**
	 * Load saved state from server by sending asynchronous Ajax request and processing the response
	 */
	loadState: function() {
		var o = {
			 url:this.readUrl || this.url
			,method:this.method
			,scope:this
			,success:this.onLoadSuccess
			,failure:this.onLoadFailure
			,params:{}
		};

		o.params = Ext.apply({}, this.loadBaseParams || this.baseParams);

		Ext.Ajax.request(o);
	}

}); 

