/**
 * The Ajax Command class
 * Factory - Manages XMLHTTPRequest objects for many different contexts
 *
 * @package    Core
 * @author     Luis Merino <mail@luismerino.name>
 */
var AjaxCommand = new Class({
	
	Implements: Options,
	
	options: {
		baseHref: URI.base.valueOf(),
		mainContainer: 'main-content'
	},
	
	initialize: function(options){
		this.setOptions(options);
		this.html = this.json = this.raw = null;
	},
	
	getWrapper: function(object){
		if (!this[object]) {
			var type = object.capitalize();
			var klass = AjaxCommand[type];
			var IAjaxCommand = new MethodsInterface('AjaxCommand', ['addEvent', 'addEvents', 'setOption', 'getOption', 'execute', 'get']);
			MethodsInterface.ensureImplements(new klass, IAjaxCommand);
			this[object] = new klass(this.options);
		}
		return this[object];
	}
	
});

/**
 * The Ajax Command Raw wrapper
 * Abstracts the Request object using accesors and mutators, as well as executions of xhr requests.
 *
 * @package    site
 * @author     Luis Merino <mail@luismerino.name>
 */
AjaxCommand.Raw = new Class(function(params){

	var object, options, events = {};
	
	var settings = {
		method: 'get',
		link: 'cancel'
	};
	
	var getObject = function(){
		return object;
	};
	
	var flushEvents = function(){
		for (var key in events) object.removeEvent(key, events[key]);
		events = {};
	};
	
	var callChainEvents = function(){
		this.$chain.length.times(this.callChain, this);
	}.bind(this);
	
	$extend(this, new Chain);
	
	$extend(this, {
		
		getOption: function(name){
			return options[name];
		},
		
		setOption: function(key, value){
			options[key] = value;
			return this;
		},
		
		addEvent: function(type, fn){
			this.chain(function(){
				events[type] = fn;
				getObject().addEvent(type, fn);
			});
			return this;
		},
		
		addEvents: function(events){
			for (var type in events) this.addEvent(type, events[type]);
			return this;
		},
		
		execute: function(path, data){
			return getObject().send($merge({
				method: this.getOption('method'),
				data: data,
				url: this.getOption('baseHref') + path
			}, options));
		},
		
		get: function(prop){
			return $unlink(getObject()[prop]);
		}
		
	});
	
	return (function(){
		options = $merge(settings, params);
		object = new Request(settings);
		object.addEvent('finish', flushEvents);
		object.addEvent('start', callChainEvents);
	})();
	
});

/**
 * The Ajax Command Html wrapper
 * Abstracts the Request.HTML object using accesors and mutators, as well as executions of xhr requests.
 *
 * @package    site
 * @author     Luis Merino <mail@luismerino.name>
 */
AjaxCommand.Html = new Class(function(params){

	var object, options, events = {};
	
	var settings = {
		method: 'get',
		link: 'chain'
	};
	
	var getObject = function(){
		return object;
	};
	
	var flushEvents = function(){
		for (var key in events) object.removeEvent(key, events[key]);
		events = {};
	};
	
	var callChainEvents = function(){
		this.$chain.length.times(this.callChain, this);
	}.bind(this);
	
	$extend(this, new Chain);
	
	$extend(this, {
		
		getOption: function(name){
			return options[name];
		},
		
		setOption: function(key, value, toObject){
			if (toObject) {
				getObject().options[key] = value;
			} else {
				options[key] = value;
			}
			return this;
		},
		
		addEvent: function(type, fn){
			this.chain(function(){
				events[type] = fn;
				getObject().addEvent(type, fn);
			});
			return this;
		},
		
		addEvents: function(events){
			for (var type in events) this.addEvent(type, events[type]);
			return this;
		},
		
		execute: function(path, data){
			return getObject().send($merge({
				method: this.getOption('method'),
				url: this.getOption('baseHref') + path
			}, options));
		},
		
		get: function(prop){
			return $unlink(getObject()[prop]);
		}
		
	});
	
	return (function(){
		options = $merge(settings, params);
		object = new Request.HTML(settings);
		object.addEvent('finish', flushEvents);
		object.addEvent('start', callChainEvents);
	})();
	
});

/**
 * The Ajax Command Json wrapper
 * Abstracts the Request.JSON object using accesors and mutators, as well as executions of xhr requests.
 *
 * @package    site
 * @author     Luis Merino <mail@luismerino.name>
 */
AjaxCommand.Json = new Class(function(params){

	var object, options, events = {};
	
	var settings = {
		method: 'post',
		link: 'chain'
	};
	
	var getObject = function(){
		return object;
	};
	
	var flushEvents = function(){
		for (var key in events) object.removeEvent(key, events[key]);
		events = {};
	};
	
	var callChainEvents = function(){
		this.$chain.length.times(this.callChain, this);
	}.bind(this);
	
	$extend(this, new Chain);
	
	$extend(this, {
		
		getOption: function(name){
			return options[name];
		},
		
		setOption: function(key, value){
			options[key] = value;
			return this;
		},
		
		addEvent: function(type, fn){
			this.chain(function(){
				events[type] = fn;
				getObject().addEvent(type, fn);
			});
			return this;
		},
		
		addEvents: function(events){
			for (var type in events) this.addEvent(type, events[type]);
			return this;
		},
		
		execute: function(path, data){
			return getObject().sendJSON($merge({
				method: this.getOption('method'),
				url: this.getOption('baseHref') + path,
				data: data
			}, options));
		},
		
		get: function(prop){
			return $unlink(getObject()[prop]);
		}
		
	});
	
	return (function(){
		options = $merge(settings, params);
		object = new Request.JSON.Advanced(settings);
		object.addEvent('finish', flushEvents);
		object.addEvent('start', callChainEvents);
	})();
	
});

$extend(AjaxCommand.Json, {
	"STATUS_SUCCESS": 200,
	"STATUS_CREATED": 201,
	"STATUS_ACCEPTED": 202,
	"STATUS_NO_CONTENT": 204,
	"STATUS_UPDATED": 207,
	"STATUS_REDIRECT": 300,
	"STATUS_VALIDATION_ERROR": 400,
	"STATUS_NOT_FOUND": 404,
	"STATUS_VALIDATION_CRITICAL": 500,
	"STATUS_ERROR_NOT_IMPLEMENTED": 501,
	"STATUS_ERROR_UNKNOWN": 510
});

Request = Class.refactor(Request, {
	
    onSuccess: function(){
		this.fireEvent('complete', arguments).fireEvent('success', arguments).fireEvent('finish').callChain();
	},
	
	onFailure: function(){
		this.previous();
		this.fireEvent('finish');
	},
	
	send: function(options){
		this.fireEvent('start');
		this.previous(options);
	},
	
	cancel: function(){
		this.previous();
		this.fireEvent('finish');
	}
	
});

Request.JSON.Advanced = new Class({
	
	Extends: Request.JSON,
	
	Implements: Log,
	
	options: {
		debug: false
	},
	
	initialize: function(options){
		this.parent(options);
		if (this.options.debug) this.enableLog();
	},
	
	onStateChange: function(){
		if (this.xhr.readyState != 4 || !this.running) return;
		this.running = false;
		this.status = 0;
		$try(function(){
			this.status = this.xhr.status;
		}.bind(this));
		this.xhr.onreadystatechange = $empty;
		this.response = {text: this.xhr.responseText};
		if (this.options.debug) this.log(this.response.text);
		if (this.options.isSuccess.call(this, this.status)){
			this.success(this.response.text);
		} else {
			this.failure(this.response.text);
		}
	},
	
	success: function(text){
		this.response.json = JSON.decode(text, this.options.secure);
		try {
			var status = this.response.json.status;
		} catch(e) {
			var status = this.status;
			this.log(e + "\n-> The JSON response mignt not contain a ´status code´.");
		}
		if (status >= AjaxCommand.Json.STATUS_REDIRECT && status < AjaxCommand.Json.STATUS_VALIDATION_ERROR) {
			this.onRedirect(this.response.json, text);
		} else {
			this.onSuccess(this.response.json, text);
		}
	},

	onRedirect: function(){
		this.fireEvent('complete', arguments).fireEvent('redirect', arguments).fireEvent('finish');
	},
	
	failure: function(text){
		this.response.json = JSON.decode(text, this.options.secure);
		this.onFailure(this.response.json, text);
	},

	onFailure: function(){
		this.fireEvent('complete', arguments).fireEvent('failure', arguments).fireEvent('finish');
	},
	
	sendJSON: function(options){
		if (!this.check(options)) return this;
		this.fireEvent('start');
		this.running = true;

		var type = $type(options);
		if (type == 'string' || type == 'element') options = {data: options};

		var old = this.options;
		options = $extend({data: old.data, url: old.url, method: old.method}, options);
		var data = options.data, url = String(options.url), method = options.method.toLowerCase();

		switch ($type(data)){
			case 'element': data = document.id(data).toQueryJSON(); break;
			case 'object': case 'hash': data = JSON.encode(data);
		}

		if (this.options.format){
			var format = 'format=' + this.options.format;
			data = (data) ? format + '&' + data : format;
		}

		if (this.options.emulation && !['get', 'post'].contains(method)){
			var _method = '_method=' + method;
			data = (data) ? _method + '&' + data : _method;
			method = 'post';
		}

		var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
		this.headers.set('Content-type', 'application/json' + encoding);

		if (this.options.noCache){
			var noCache = 'noCache=' + new Date().getTime();
			data = (data) ? noCache + '&' + data : noCache;
		}

		var trimPosition = url.lastIndexOf('/');
		if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition);

		if (data && method == 'get'){
			url = url + (url.contains('?') ? '&' : '?') + (this.options.urlEncoded ? encodeURI(data) : data);
			data = null;
		}

		this.xhr.open(method.toUpperCase(), url, this.options.async);

		this.xhr.onreadystatechange = this.onStateChange.bind(this);

		this.headers.each(function(value, key){
			try {
				this.xhr.setRequestHeader(key, value);
			} catch (e){
				this.fireEvent('exception', [key, value]);
			}
		}, this);

		this.fireEvent('request');
		this.xhr.send(data);
		if (!this.options.async) this.onStateChange();
		return this;
	}

});