/**
 * Core JavaScript library for fusionLib.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * @copyright Copyright (c) 2007 - 2016 fusionLib. All rights reserved.
 * @link http://fusionlib.com
 */

(function() {
	var fusionLib, window = this;

	/**
	 * Get the transport for AJAX requests.
	 *
	 * @return XMLHttpRequest The object to perform the request.
	 */
	function getTransport() {
		// If native XMLHttpRequest object
		if(typeof XMLHttpRequest !== 'undefined' && (window.location.protocol !== 'file:' || !window.ActiveXObject))
			return new XMLHttpRequest();
		// If ActiveX
		else {
			var progIDs = ['Msxml2.XMLHTTP.6.0', 'Msxml2.XMLHTTP.3.0', 'Msxml2.XMLHTTP'];
			for(var i in progIDs) {
				try {
					return new ActiveXObject(progIDs[i]);
				}
				catch(e) { }
			}
		}

		return null;
	}

	// Defaults for ajax
	var defaultSettings = {};

	window.$fl = window.fusionLib = fusionLib = function(selector, context) {
		return new fusionLib.fn.find(selector, context);
	}

	fusionLib.fn = fusionLib.prototype = {

		/**
		 * Find the elements matching selector e.g. find('li'); returns all the li elements or
		 * find('li.class a') to select all a elements inside li elements of a given class.
		 *
		 * @param string selector The query.
		 * @param HTMLElement root Optional root to start search at.
		 * @return This object.
		 */
		find: function(selector, root) {
			var elems = [], i, nodes, n, m, p;
			root = typeof root === 'undefined' ? document : root;

			// No selector, don't do anything.
			if(!selector) {
				this.length = 0;
				return this;
			}
			else if(typeof selector === 'object')
				return this._result([selector]);
			// If query on object with elements
			else if(this.length) {
				for(i=0;i<this.length;i++) {
					nodes = fusionLib(selector, this[i]);
					for(n=0;n<nodes.length;n++) elems.push(nodes[n]);
				}
			}
			// Else new query
			else {
				if(m = selector.split(/\s+/g)) {
					// Id
					if(p = m[0].match(/([a-z0-9_-]+)?#([a-z0-9_-]+)/i)) {
						elems = [root.getElementById(p[2])];
						if(!elems[0] || (elems[0] && p[1] && elems[0].nodeName.toLowerCase() != p[1]))
							elems = [];
					}
					// Class
					else if(p = m[0].match(/([a-z0-9_-]+)?\.([a-z0-9_-]+)/i)) {
						nodes = root.getElementsByTagName(p[1] ? p[1] : '*');
						var clsName = new RegExp('(^|\\s)' + p[2] + '(\\s|$)');
						for(i=0;i<nodes.length;i++)
							if(nodes[i].className.baseVal != undefined) {
								if(clsName.test(nodes[i].className.baseVal))
									elems.push(nodes[i]);
							}
							else if(clsName.test(nodes[i].className))
								elems.push(nodes[i]);
					}
					// Name
					else if((p = m[0].match(/([a-z0-9_-]+)?\[name="([a-z0-9_-]+(\[\])?)"\]/i))) {
						nodes = root.getElementsByTagName(p[1] ? p[1] : '*');
						for(i=0;i<nodes.length;i++)
							if(nodes[i].name == p[2]) elems.push(nodes[i]);
					}
					// Element
					else {
						nodes = root.getElementsByTagName(m[0]);
						for(i=0;i<nodes.length;i++) elems.push(nodes[i]);
					}

					// Process rest of selector
					for(p=1;p<m.length;p++) {
						var elems2 = [];
						for(i=0;i<elems.length;i++) {
							nodes = fusionLib(m[p], elems[i]);
							for(n=0;n<nodes.length;n++)elems2.push(nodes[n]);
						}
						elems = elems2;
					}
				}
			}
			return this._result(elems);
		},

		/**
		 * Select the elements from the current selection which have children matching the selector.
		 *
		 * @param string selector The selector string to match.
		 * @return fusionLib The elements from the current selection which have children matching the selection.
		 */
		has: function(selector) {
			var elems = [];
			this.each(function() { if(fusionLib(this).find(selector).length) elems.push(e); });
			return this._result(elems);
		},

		/**
		 * Build a new fusionLib object that consists of the parent elements of the elements in the selection.
		 *
		 * @return fusionLib The parents of the selected elements.
		 */
		parent: function() {
			var elems = [];
			this.each(function() { elems.push(this.parentNode); });
			return this._result(elems);
		},

		/**
		 * Find the closest element by traversing up the tree
		 *
		 * @param string selector The selector to match.
		 * @return fusionLib The closest matching element.
		 */
		closest: function(selector) {
			var elems = [];
			this.each(function() {
				var e = fusionLib(this);
				while (!e.is('body')) {
					if(e.is(selector)) {
						elems.push(e[0]);
						break;
					}
					e = e.parent();
				}
			});
			return this._result(elems);
		},

		/**
		 * Create a new object with the given elements.
		 *
		 * @param NodeList elems The list of elements to set.
		 * @return fusionLib The new object with the elements set.
		 */
		_result: function(elems) {
			var ret = fusionLib();
			[].push.apply(ret, elems);
			return ret;
		},

		/**
		 * Call a function for each element.
		 *
		 * @param function fn The function to call fn(idx, domElem), returns false to stop
		 * processing. this is the DOM element being processed.
		 * @return fusionLib This object.
		 */
		each: function(fn) {
			for(var i=0;i<this.length;i++)
				if(fn.call(this[i], i, this[i]) === false) break;
			return this;
		},

		/**
		 * Filter the current node list.
		 *
		 * @param function fn The function to call to filter, fn(i), returns true to keep
		 * the node or false to remove it, or the name of a filter function ':odd' or ':even'.
		 * @return fusionLib The results.
		 */
		filter: function(fn) {
			var elems = [];
			if(typeof fn == 'string') {
				if(fn == ':odd')
					return this.filter(function(i) { return i % 2 == 1; });
				else if(fn == ':even')
					return this.filter(function(i) { return i % 2 == 0; });
				else
					return fusionLib();
			}
			return this.each(function(i) { if(fn.call(this, i)) elems.push(this); })._result(elems);
		},

		/**
		 * Get the nth element from the results.
		 *
		 * @param int n The index of the element to get, 0 is the 1st and -1 is the last.
		 * @return fusionLib The object with the element.
		 */
		eq: function(n) {
			return fusionLib(this[n < 0 ? this.length + n : n]);
		},

		/**
		 * Get the 1st element from the results.
		 *
		 * @return fusionLib Object with the 1st element in.
		 */
		first: function() {
			return this.eq(0);
		},

		/**
		 * Get the last element from the results.
		 *
		 * @return fusionLib Object with the last element in.
		 */
		last: function() {
			return this.eq(-1);
		},

		/**
		 * Extend the functionality of the object.
		 *
		 * @param object The object to extend or if 1 parameter the object to extend fusionLib with.
		 * @param object The option object to extend the 1st with.
		 * @return object The extended object.
		 */
		extend: function() {
			var target, o;
			if(arguments.length == 1) {
				target = fusionLib.fn;
				o = arguments[0];
			}
			else {
				target = arguments[0];
				o = arguments[1];
			}

			for(var i in o) target[i] = o[i];
			return target;
		}
	};

	fusionLib.fn.find.prototype = fusionLib.fn;
	fusionLib.extend = fusionLib.fn.extend;

})();
