﻿(function () {
    function _selector(args, state, bypassNative, xpath) {
        this.elements = [];
        var i, parts = [];
        var parents = null;
        var isArray = false;

        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')) parents = [arg];
                    else {
                        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));
                else this.elements = this.elements.concat(this.realize(this.resolveXpath(parts[i]), parents));
            }
            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;
	    }
	};

    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]);

        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]);

        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);
    };
})();

