﻿(function ()
{
	function _selector(args, state)
	{
		this.elements = [];
		var i, parts = [];

		for (i = 0; i < args.length; i++)
		{
			parts = parts.concat(args[i].split(','));
		}

		for (i = 0; i < parts.length; i++)
		{
			this.elements = this.elements.concat(this.realize(this.resolve(parts[i]), null));
		}

		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;
		}
	}

	_selector.prototype =
	{
		resolve: function (s)
		{
			var identifier = '';
			var valid = true;
			while (s.charAt(0) === ' ')
			{
				s = s.substring(1);
			}
			var i = 0;
			var inbrackets = false;
			while (valid && i < s.length)
			{
				var ch = s.charAt(i);
				valid = /[a-z,A-Z,\d,\#,\.,\>,_,\[,\],\=,\~,\^,\$,\*,\"]/.test(ch);

				if (inbrackets)
				{
					valid = true;
				}

				if (inbrackets && ch === ']')
				{
					inbrackets = false;
				}

				if (!inbrackets && ch === '[')
				{
					inbrackets = true;
				}

				if (valid)
				{
					identifier += ch;
				}

				if (((ch === '#' || ch === '.') && i > 0) || ch === '>')
				{
					valid = false;
				}

				i++;
			}
			s = s.substring(i);

			if (identifier === '')
			{
				return null;
			}
			else
			{
				return { 'selector': identifier, 'next': this.resolve(s) };
			}
		},

		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;

			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];

			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.indexOf(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 (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;
		},

		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;
		}
	};

	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]);

		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.$chain = function ()
	{
		return new _selector(arguments, 3);
	};
})();

