/*
 * Dependencies:
 * Bunny
 * YAHOO.lang.JSON
 * YAHOO.util.Connect
 */

/**
 * @class Bunny.Lib.AJAX
 */
(function()
{

	var ajaxer = {}; // Internal shorthand.

	Bunny.Lib.AJAX = ajaxer;

	var util = YAHOO.util,
		Dom = util.Dom,
		Connect = util.Connect,
		get = Dom.get;

	var lang = YAHOO.lang,
		parseJSON = lang.JSON.parse,
		isString = lang.isString,
		isNumber = lang.isNumber,
		isArray = lang.isArray,
		isFunction = lang.isFunction,
		isObject = lang.isObject;

	ajaxer.setLoadingImage = function(src)
	{
		// Implement me!
	};

	ajaxer.setLoadingText = function(text)
	{
		// Implement me!
	};

	/**
	 * Update an area within a page.
	 *
	 * @param {String | Object} element  The element ID or reference.
	 * @param {String} content  The HTML to be injected into element; if there are JavaScript blocks, they'll be evaluated before the injection takes place.
	 * @param {bool} evaluate (optional)  Whether to evaluate JavaScript or not.  Defaults to false.
	 * @type boolean
	 */
	ajaxer.update = function(element, content, evaluate)
	{
		element = get(element);

		if (
			!element ||
			(typeof element !== 'object') ||
			(element.nodeType !== 1)
		   ) {
			// Invalid HTML element.
			return false;
		}

		if (evaluate !== true) {
			element.innerHTML = content;
			return true;
		}

		function executeJS(code)
		{
			setTimeout(
				function()
				{
					eval(code);
				},
				10
			);
		}

		var re = new RegExp('(?:<script.*?>)((\r|\n|.)*?)(?:<\/script>)', 'gim');
		var result;

		try {
			while (result = re.exec(content)) {
				if (isArray(result) && (result.length > 1)) {
					executeJS(result[1]);
				}
			}
		} catch (e) {
			element.innerHTML = e.name + ':  ' + e.message;
			return false;
		}

		element.innerHTML = content.replace(re, '');
		return true;
	};

	/**
	 * The actual function is returned.
	 */
	ajaxer.asyncRequest = function()
	{
		/**
		 * Get instructions to update the page.
		 *
		 * @private
		 * @param {String | Array | Object} area  The area(s) to update.
		 * @param {Object} r  The response object.
		 * @throws SyntaxError
		 * @type Array
		 * @return
		 *         An array of objects with 2 properties:
		 *                                               element - The HTML element.
		 *                                               content - The returned content.
		 *         An empty array if area isn't string, array, nor object.
		 */
		function getInstruction(area, r)
		{
			var value = [];

			switch (true) {
				case isArray(area): // isArray() must come before isObject()
					var i, counter, tmp, result;

					try {
						result = parseJSON(r.responseText);

						if (!isArray(result)) {
							throw new TypeError('Invalid result type; result should be an array of strings.');
						}
					} catch (e) {
						throw e;
					}

					for (i = 0, counter = result.length; i < counter; i++) {
						tmp = result[i];

						// Construct the result set only if the content is a number or non-empty string.
						if (isNumber(tmp) || (isString(tmp) && tmp.length)) {
							value.push(
								{
									element: area[i],
									content: tmp
								}
							);
						}
					}
				break;
				case isString(area):
				case (isObject(area) && (area.nodeType === 1)):
					value[0] = {
						element: area,
						content: r.responseText
					};
				break;
			}

			return value;
		}

		/**
		 * Do AJAX request.
		 *
		 * @private
		 * @param {Object} o
		 * @type void
		 */
		function doRequest(o)
		{
			var area = o.area,
				data = o.data,
				handler = o.handler;

			function executeHandler(r)
			{
				switch (true) {
					case isFunction(handler): // isFunction() must come before isObject()
						handler(r);
					break;
					case isArray(handler): //isArray() must come before isObject()
						var i = 0,
							counter = handler.length,
							tmp;

						while (i < counter) {
							tmp = handler[i];
							if (isFunction(tmp)) {
								tmp(r);
							}
							i++;
						}
					break;
				}
			}

			Connect.asyncRequest(
				o.method,
				o.destination,
				{
					success: function(r)
					{
						var value = getInstruction(area, r);
						var update = ajaxer.update, evaluate = o.evaluate, handler = o.handler;
						var i = 0, counter = value.length, tmp = '';

						while (i < counter) {
							tmp = value[i];
							update(tmp.element, tmp.content, evaluate);
							i++;
						}

						executeHandler(r);
					},
					failure: function(r)
					{
						var element = get(area);

						if (
							element &&
							(typeof element === 'object') &&
							(element.nodeType === 1)
						   ) {
							element.innerHTML = 'Connection failed.  Please try again later.';
						}

						executeHandler(r);
					}
				},
				isString(data) ? data : ''
			);
		}

		/**
		 * Perform an asynchronous request
		 *
		 * @param {Object} o  An object with the following properties:
		 *                    destination {String}:  URL or URI.
		 *                    method {String} (optional):  GET or POST.  Defaults to GET.
		 *                    data {String | Object} (optional):  The post/query string to send.  Accepts either key=value string or "key":"value" object.
		 *                    area {String | Object | Array} (optional):  The HTML area to update.  Accepts either element ID, element reference, or array of element ID or reference.  If the responseText contains JavaScript blocks, they'll be evaluated before the innerHTML gets injected with the responseText.
		 *                    evaluate {bool} (optional):  Whether to evaluate JavaScript or not.  Defaults to false.
		 *                    handler {Function | Array} (optional):  User-defined callback function or array of functions to execute after the asynchronous request is complete.  The response object is passed automatically as the argument.
		 * @type void
		 */
		return function(o)
		{
			var destination = o.destination,
				method = (isString(o.method) && (o.method.toUpperCase() === 'POST')) ? 'POST' : 'GET',
				data = o.data,
				area = o.area,
				evaluate = o.evaluate,
				handler = o.handler;

			switch (true) {
				case isObject(data): // Post/query string construction from object.
					var tmp = '', data_container = [];

					for (var variable in data) {
						tmp = variable + '=' + encodeURIComponent(data[variable]);
						data_container.push(tmp);
					}

					data = data_container.join('&');
				break;
				case isString(data): // encodeURIComponent() all the values.
					var pair = data.split('&');
					var part = [], result = [];

					for (var i = 0, counter = pair.length; i < counter; i++) {
						part = pair[i].split('=');

						if (part.length === 2) {
							part[1] = encodeURIComponent(part[1]);
							result.push(part.join('='));
						}
					}

					data = result.join('&');
				break;
				default:
					data = false;
			}

			if ((method === 'GET') && data) {
				// If query string already exists, append with ampersand; otherwise, create query string.
				// Set data to false.
				destination += ((destination.indexOf('?') === -1) ? '?' : '&') + data;
				data = false;
			}

			doRequest(
				{
					destination: destination,
					method: method,
					data: data,
					area: area,
					evaluate: evaluate,
					handler: handler
				}
			);
		};
	}();

	/**
	 * Perform an asynchronous request with fields within a form
	 *
	 * @param {Object} o  An object with the following properties:
	 *                    form {String | Object}:  The form ID or reference.
	 *                    destination {String} (optional):  URL or URI.  Defaults to the form's action attribute or current URL.
	 *                    method {String} (optional):  GET or POST.  Defaults to the form's method attribute.
	 *                    data {String | Object} (optional):  Additional post/query string to send.  Accepts either 'key=value' string or 'key:value' object.  Key-value pairs will be appended.
	 *                    area {String | Object | Array} (optional):  The HTML area to update.  Accepts either element ID, element reference, or array of element ID or reference.  If the responseText contains JavaScript blocks, they'll be evaluated before the innerHTML gets injected with the responseText.
	 *                    evaluate {bool} (optional):  Whether to evaluate JavaScript or not.  Defaults to false.
	 *                    handler {Function | Array} (optional):  User-defined callback function or array of functions to execute after the asynchronous request is complete.  The response object is passed automatically as the argument.
	 * @type void
	 */
	ajaxer.sendForm = function(o)
	{
		Connect.setForm(o.form);

		o.method = o.method || o.form.method;
		o.destination = o.destination || o.form.action || location.href;

		ajaxer.asyncRequest(o);
	};

})();

