﻿(function ()
{
	function _selector(args, state, bypassNative, xpath)
	{
		this.elements = [];
		var i, parts = [];
		var parents = null;
		var isArray = false;
		var orgArg = null;

		for (i = 0; i < args.length; i++)
		{
			var arg = args[i];
			if (typeof (arg) == 'string') parts = parts.concat(arg.split(','));
			else
			{
				if (typeof (arg) == 'object')
				{
					if (!$ts.exists(arg, 'length'))
					{
						orgArg = arg;
						parents = [arg];
					}
					else
					{
						orgArg = arg;
						parents = arg;
						isArray = true;
					}
				}
			}
		}

		var nativeSupport = (!bypassNative) && ($ts.exists(document, 'querySelectorAll') && isArray === false && (parents === null || $ts.exists(parents, 'querySelectorAll')));
		for (i = 0; i < parts.length; i++)
		{
			if (!nativeSupport)
			{
				if (!xpath) this.elements = this.elements.concat(this.realize(this.resolve(parts[i]), parents, orgArg));
				else this.elements = this.elements.concat(this.realize(this.resolveXpath(parts[i]), parents, orgArg));
			}
			else
			{
				var root = (parents === null) ? document : parents;
				this.elements = this.elements.concat(root.querySelectorAll(parts[i]));
			}
		}

		if (!nativeSupport)
		{
			var retval = [];

			for (i = 0; i < this.elements.length; i++)
			{
				(function (element)
				{
					if (!element.hashcode)
					{
						element.hashcode = i + 1;
					}

					if (retval[element.hashcode] === undefined)
					{
						retval[element.hashcode] = element;
					}
				})(this.elements[i]);
			}

			this.elements = [];

			for (var key in retval)
			{
				if (retval.hasOwnProperty(key))
				{
					this.elements.push(retval[key]);
				}

				retval[key].hashcode = undefined;
			}
		}
		else
		{
			var retVal = this.elements;
			this.elements = [];
			for (i = 0; i < retVal.length; i++)
			{
				var inner = retVal[i];
				for (var j = 0; j < inner.length; j++) this.elements.push(inner[j]);
			}
		}
	}

	_selector.prototype =
	{
		resolve: function (s)
		{
			var identifier = '';
			var valid = true;
			while (s.charAt(0) === ' ')
			{
				s = s.substring(1);
			}
			var i = 0;
			var inbrackets = false;
			var inbraces = false;
			while (valid && i < s.length)
			{
				var ch = s.charAt(i);
				valid = /[a-z,A-Z,\d,\#,\.,\>,_,\[,\],\(,\),\=,\~,\^,\$,\-,\:,\*,\"]/.test(ch);

				if (inbrackets || inbraces)
				{
					valid = true;
				}

				if (inbrackets && ch === ']') inbrackets = false;
				if (inbraces && ch === ')') inbraces = false;

				if (!inbrackets && ch === '[') inbrackets = true;
				if (!inbraces && ch === '(') inbraces = true;

				if (valid)
				{
					identifier += ch;
				}

				if ((((ch === '#' || ch === '.') && i > 0) || ch === '>') && !inbraces)
				{
					valid = false;
				}

				i++;
			}
			s = s.substring(i);
			if (identifier === '')
			{
				return null;
			}
			else
			{
				return { 'selector': identifier, 'next': this.resolve(s) };
			}
		},

		resolveXpath: function (s)
		{
			var t = s.replace('//', '').replace(/\//g, '>').replace(/@/g, '');
			return this.resolve(t);
		},

		realize: function (treenode, parents, parentNode)
		{
			var i, j, elm, elms;
			treenode.elements = [];
			treenode.parentNode = parentNode;
			if (parents === null)
			{
				parents = [document.body];
			}
			var ident = treenode.selector;

			if (ident === '*')
			{
				for (var key in parents)
				{
					var allElms = parents[key].getElementsByTagName('*');
					for (var k = 0; k < allElms.length; k++)
					{
						treenode.elements.push(allElms[k]);
					}
				}
				return treenode.elements;
			}

			treenode.isId = ident.charAt(0) === '#';
			treenode.isClass = ident.charAt(0) === '.';

			treenode.whereId = ident.charAt(ident.length - 1) === '#';
			treenode.whereClass = ident.charAt(ident.length - 1) === '.';

			var re = new RegExp(/\[(\w+)(~=|\|=|\^=|\$=|\*=|=)?(\"[\w\s]+\")?\]/);
			var qry = re.exec(ident);
			treenode.query = [];
			if (qry)
			{
				do
				{
					var nextqry = re.exec(RegExp.rightContext);
					query = {};
					query.expando = qry[1];
					query.operator = qry[2];
					query.value = qry[3] ? qry[3].replace(/\"/g, '') : null;
					treenode.query.push(query);
					qry = nextqry;
				}
				while (qry != null);
			}

			if (ident === '>')
			{
				treenode.next.immidiate = true;
				treenode.parentNode.next = treenode.next;

				return (this.realize(treenode.next, parents, parentNode));
			}

			if (treenode.next !== null)
			{
				treenode.next.immidiate = false;
			}

			var id = /([a-z,A-Z,\d,_]+)/.exec(ident)[0];
			var obj = null;
			var pseudo = (obj = /:([^(]*)/.exec(ident)) === null ? null : obj[1];
			var pseudoNumber = (obj = /\((\d+)\)/.exec(ident)) === null ? null : obj[1];
			var pseudoSelector = (obj = /\((.*)\)/.exec(ident)) === null ? null : obj[1];
			if (pseudoNumber != null) pseudoNumber = parseInt(pseudoNumber, 10);
			if (treenode.isClass || (treenode.parentNode && treenode.parentNode.whereClass))
			{
				var regEx = new RegExp('\\b' + id + '\\b');
				if (treenode.isClass)
				{
					for (j = 0; j < parents.length; j++)
					{
						elms = treenode.immidiate ? parents[j].childNodes : parents[j].getElementsByTagName('*');

						for (i = 0; i < elms.length; i++)
						{
							if (regEx.test(elms[i].className))
							{
								treenode.elements.push(elms[i]);
							}
						}
					}
				}
				else
				{
					for (i = 0; i < parents.length; i++)
					{
						if (regEx.test(parents[i].className))
						{
							treenode.elements.push(parents[i]);
						}
					}
				}
			}
			else if (treenode.isId || (treenode.parentNode && treenode.parentNode.whereId))
			{
				if (!treenode.parentNode)
				{
					elm = document.getElementById(id);
					if (elm !== null)
					{
						treenode.elements.push(elm);
					}
				}
				else
				{
					for (i = 0; i < parents.length; i++)
					{
						elms = parents[i].getElementsByTagName("*");
						for (j = 0; j < elms.length; j++)
						{
							if (elms[j].id === id)
							{
								treenode.elements.push(elms[j]);
							}
						}
					}
				}
			}
			else
			{
				for (j = 0; j < parents.length; j++)
				{
					elms = treenode.immidiate ? parents[j].childNodes : parents[j].getElementsByTagName(id);
					for (i = 0; i < elms.length; i++)
					{
						if (elms[i].tagName && elms[i].tagName.toLowerCase() === id.toLowerCase())
						{
							if (!treenode.query.length > 0)
							{
								treenode.elements.push(elms[i]);
							}
							else
							{
								var fullQueryOk = true;
								for (var qi = 0; qi < treenode.query.length; qi++)
								{
									var query = treenode.query[qi];
									var v = elms[i].getAttribute(query.expando);
									if (typeof (v) == "boolean" && v === true) v = query.value;
									else if (typeof (v) == "boolean" && v === false) v = '';
									var queryOk = false;
									switch (query.operator)
									{
										case "=":
											queryOk = (v === query.value);
											break;
										case "|=":
											queryOk = (v === query.value || query.value.indexOf(v + '-') === 0);
											break;
										case "~=":
											var vals = query.value.replace(/\s+/g, ' ').split(' ');
											for (var subv in vals)
											{
												if (vals.hasOwnProperty(v))
												{
													queryOk = (vals[subv] === v);
													if (queryOk)
													{
														break;
													}
												}
											}
											break;
										case "^=":
											queryOk = v !== null && v !== undefined && v !== '' && v.indexOf(query.value) === 0;
											break;
										case "$=":
											queryOk = v !== null && v !== undefined && v.length >= query.value.length && v.lastIndexOf(query.value) != -1 && v.lastIndexOf(query.value) == v.length - query.value.length;
											break;
										case "*=":
											queryOk = v !== null && v !== undefined && v !== '' && v.indexOf(query.value) > -1;
											break;
										default:
											queryOk = v !== null && v !== undefined;
											break;
									}

									fullQueryOk = fullQueryOk && queryOk;
								}

								if (fullQueryOk)
								{
									treenode.elements.push(elms[i]);
								}
							}
						}
					}
				}
			}
			if ($ts.exists(pseudo))
			{
				var pseudoElements = [];
				if (pseudo === 'not' && pseudoSelector != null)
				{
					pseudoElements = $elms(pseudoSelector);
				}
				for (var k = 0; k < treenode.elements.length; k++)
				{
					var elm = treenode.elements[k];
					switch (pseudo)
					{
						case "enabled":
							if ($ts.exists(elm, 'disabled') && elm.disabled === true) elm = null;
							break;
						case "disabled":
							if ($ts.exists(elm, 'disabled') && elm.disabled === false) elm = null;
							break;
						case "checked":
							if ($ts.exists(elm, 'checked') && elm.checked === false || !$ts.exists(elm, 'checked')) elm = null;
							break;
						case "focus":
							if (document.activeElement !== elm) elm = null;
							break;
						case "empty":
							if (elm.childNodes.length > 0) elm = null;
							break;
						case "only-child":
						case "only-of-type":
							var parentNode = elm.parentNode;
							var tagFilter = null;
							if (pseudo === 'only-of-type') tagFilter = elm.tagName;
							var siblings = $ts.getChildElements(parentNode, tagFilter);
							if (siblings.length !== 1) elm = null;
							break;
						case "first-child":
						case "last-child":
						case "first-of-type":
						case "last-of-type":
							var parentNode = elm.parentNode;
							var tagFilter = null;
							if (pseudo === 'first-of-type' || pseudo === 'last-of-type') tagFilter = elm.tagName;
							var siblings = $ts.getChildElements(parentNode, tagFilter);
							if (pseudo === 'last-child' || pseudo === 'last-of-type') siblings.reverse();
							for (var l = 0; l < siblings.length; l++)
							{
								var node = siblings[l];
								if (node === elm) break;
								else
								{
									elm = null;
									break;
								}
							}
							break;
						case 'nth-child':
						case 'nth-last-child':
						case 'nth-of-type':
						case 'nth-last-of-type':
							var parentNode = elm.parentNode;
							var tagFilter = null;
							if (pseudo === 'nth-of-type' || pseudo === 'nth-last-of-type') tagFilter = elm.tagName;
							var siblings = $ts.getChildElements(parentNode, tagFilter);
							if (pseudo === 'nth-last-child' || pseudo === 'nth-last-of-type') siblings.reverse();
							if (pseudoNumber < siblings.length)
							{
								if (siblings[pseudoNumber] !== elm) elm = null;
							}
							else elm = null;
							break;
						case "not":
							if ($ts.isElementInArray(pseudoElements, elm)) elm = null;
							break;
					}
					treenode.elements[k] = elm;
				}
				var arr = [];
				for (var k = 0; k < treenode.elements.length; k++)
				{
					var elm = treenode.elements[k];
					if ($ts.exists(elm)) arr.push(elm);
				}
				treenode.elements = arr;
			}
			if (treenode.next !== null)
			{
				return (this.realize(treenode.next, treenode.elements, treenode));
			}
			return treenode.elements;
		},

		each: function (fn)
		{
			for (var i = 0, len = this.elements.length; i < len; ++i)
			{
				fn.call(this, this.elements[i]);
			}

			return this;
		},

		addClass: function (className)
		{
			this.each(function (el)
			{
				$ts.addClass(el, className);
			});

			return this;
		},

		removeClass: function (className)
		{
			this.each(function (el)
			{
				$ts.removeClass(el, className);
			});

			return this;
		},

		setStyle: function (prop, val)
		{
			this.each(function (el)
			{
				el.style[prop] = val;
			});

			return this;
		},

		show: function ()
		{
			var that = this;

			this.each(function (el)
			{
				that.setStyle('display', 'block');
			});

			return this;
		},

		addEvent: function (type, fn)
		{
			this.each(function (el)
			{
				$ts.addEvent(el, type, fn);
			});

			return this;
		},

		removeEvent: function (type, fn)
		{
			this.each(function (el)
			{
				$ts.removeEvent(el, type, fn);
			});

			return this;
		},

		disabled: function (value)
		{
			this.each(function (el)
			{
				el.disabled = value;
			});

			return this;
		},

		onclick: function (func)
		{
			this.each(function (el)
			{
				el.onclick = func;
			});

			return this;
		},

		savehandler: function (name)
		{
			var save = function (el)
			{
				if (el.handlers === undefined || el.handlers === null)
				{
					el.handlers = [];
				}

				el.handlers[name] = el[name];
			};

			this.each(function (el)
			{
				save(el);
			});

			return this;
		},

		restorehandler: function (name)
		{
			var restore = function (el)
			{
				if ($ts.exists(el.handlers) && el.handlers[name] !== undefined)
				{
					el[name] = el.handlers[name];
				}
			};

			this.each(function (el)
			{
				restore(el);
			});

			return this;
		},

		collapse: function ()
		{
			this.each($ts.UI.Animation.Collapse);
			return this;
		},

		expand: function ()
		{
			this.each($ts.UI.Animation.Expand);
			return this;
		}
	};

	window.$xpath = function ()
	{
		return new _selector(arguments, 1, true, true).elements;
	};

	window.$telms = function ()
	{
		return new _selector(arguments, 1, true).elements;
	};

	window.$elms = function ()
	{
		return new _selector(arguments, 1).elements;
	};

	window.$elm = function ()
	{
		if (arguments[0] == null) return null;
		if (typeof (arguments[0]) != 'string' && $ts.exists(arguments[0].tagName)) { return arguments[0]; }

		var element = document.getElementById(arguments[0]);

		for (i = 1; i < arguments.length; i++)
		{
			var arg = arguments[i];
			if (typeof (arg) == 'object')
			{
				element = null;
			}
		}

		if ($ts.exists(element))
		{
			return element;
		}
		else
		{
			var selector = new _selector(arguments, 2);

			if (selector.elements.length > 0)
			{
				return selector.elements[0];
			}
			else
			{
				return null;
			}
		}
	};

	window.$telm = function ()
	{
		if (arguments[0] == null) return null;
		if (typeof (arguments[0]) != 'string' && $ts.exists(arguments[0].tagName)) { return arguments[0]; }

		var element = document.getElementById(arguments[0]);

		for (i = 1; i < arguments.length; i++)
		{
			var arg = arguments[i];
			if (typeof (arg) == 'object')
			{
				element = null;
			}
		}

		if ($ts.exists(element))
		{
			return element;
		}
		else
		{
			var selector = new _selector(arguments, 2, true);

			if (selector.elements.length > 0)
			{
				return selector.elements[0];
			}
			else
			{
				return null;
			}
		}
	};

	window.$chain = function ()
	{
		return new _selector(arguments, 3);
	};

	window.$tchain = function ()
	{
		return new _selector(arguments, 3, true);
	};
})();

