///<reference path="../TESCO.js" />
///<reference path="event.js" />
///<reference path="event.manager.js" />
///<reference path="exception.js" />

TESCO.$("system.DOM").node = new function() {
    this.VERSION = "1.0.6";
    this.NAME = "TESCO.system.DOM.node";
    var NODE = this;

    this.CONST_XHTMLNAMESPACE = "http://www.w3.org/1999/xhtml";

    this.removeClassName = function(obj, name) {
        var _success = this.hasClassName(obj, name);
        if (_success) {
            var reg = new RegExp('(\\s|^)' + name + '(\\s|$)');
            obj.className = obj.className.replace(reg, ' ');
        }
        return _success;
    }

    this.addClassName = function(obj, name) {
        var _success = !this.hasClassName(obj, name);
        if (_success) obj.className += " " + name;
        return _success;
    }

    this.replaceClassName = function(obj, oldname, newname) {
        var _success = this.removeClassName(obj, oldname);
        if (_success) {
            _success = this.addClassName(obj, newname);
        }
        return _success;
    }

    this.hasClassName = function(obj, name) {
        var _re = new RegExp('(\\s|^)' + name + '(\\s|$)');
        if (obj.className || obj.getAttribute("class")) {
            return !!_re.test(obj.className || obj.getAttribute("class"));
        } else {
            return false;
        }
    }

    //	NodeList each 
    //#region
    this.each = function(nodeList, callback) {
        for (var i = 0, L = nodeList.length; i < L; i++) {
            callback.call(nodeList[i], i);
        }
    }
    //#endregion

    //	support for .getElementsByClassName
    //#region
    var _native = true;
    var _nodeName; //	conceal _nodeName argument from non native 

    if (!document.getElementsByClassName) {
        //	doesn't support .getElementsByClassName natively
        _native = false;

        function _getElementsByClassName(className) {
            if (!this) {
                throw new Error(NODE.NAME + ": _getElementsByClassName, context must be a node");
            } else if (!this.nodeType) {
                throw new Error(NODE.NAME + ": _getElementsByClassName, context must be a node");
            }
            var _byClass = [];
            var _byName = this.getElementsByTagName(_nodeName || "*");
            for (var i = 0, L = _byName.length; i < L; i++) {
                if (NODE.hasClassName(_byName[i], className))
                    _byClass.push(_byName[i]);
            }
            return _byClass;
        }
        document.getElementsByClassName = _getElementsByClassName; //	augment document

        if (window.HTMLElement)	//	augment DOM prototype for browsers which support it
            HTMLElement.prototype.getElementsByClassName = _getElementsByClassName;
    }

    this.getElementsByClassName = function(context, className) {
        //	use native or DOM augmented method for browsers which support it
        var _byClass = (context.getElementsByClassName) ?
			context.getElementsByClassName(className) : _getElementsByClassName.call(context, className);
        return _byClass.length ? _byClass : null;
    }

    this.getElementsByClassAndTagName = function(context, className, nodeName) {
        _nodeName = nodeName.toLowerCase();
        var _byClass = NODE.getElementsByClassName(context, className);

        if (_native && _byClass) {
            var _byName = [];
            for (var i = 0, L = _byClass.length; i < L; i++) {
                if (_byClass[i].nodeName.toLowerCase() === _nodeName)
                    _byName.push(_byClass[i]);
            }
            _byClass = _byName.length ? _byName : null;
        }

        _nodeName = null; //	reset _nodeName for use in _getElementsByClassName
        return _byClass;
    }
    //#endregion

    this.create = function(nodeName, textValue, context, ns) {
        if (nodeName) {
            var oNode;
            if (!context) {
                context = document;
            }
            if (context.createElementNS) {
                if (!ns && ns !== "") {
                    ns = this.CONST_XHTMLNAMESPACE;
                }
                oNode = context.createElementNS(ns, nodeName);
            } else {
                oNode = context.createElement(nodeName);
            }
            if (textValue) {
                this.setTextValue(oNode, textValue);
            }
        }
        return oNode;
    }

    this.append = function(container, node) {
        //#region
        //	append node in session to the container
        //	has to be done like this to execute any script nodes in IE
        if (node) {
            /*@cc_on
            //	strip scripts in node
            var _oldScripts = node.getElementsByTagName("script");
            var _newScripts = [];
            while (_oldScripts.length > 0) {
                var _script = document.createElement("script");
                _script.setAttribute("type", "text/javascript");
                //	copy the old script or src to the new script node
                if (_oldScripts[0].getAttribute("src")) {
                    _script.setAttribute("src", _oldScripts[0].getAttribute("src"));
                } else {
                    _script.text = _oldScripts[0].text
                }
                _newScripts.push(_script);
                _oldScripts[0].parentNode.removeChild(_oldScripts[0]); //	remove the old script
            }
            @*/
            container.appendChild(node);
            /*@cc_on
            //	append the new script blocks to the container
            for (var i = 0; i < _newScripts.length; i++) {
                container.appendChild(_newScripts[i]);
            }
            @*/
        }
        //#endregion
    }

    this.createText = function(textValue, context) {
        if (!context) {
            context = document;
        }
        return context.createTextNode(textValue);
    }
    this.removeTextValue = function(node) {
        node.parentNode.removeChild(node);
    }


    this.setTextValue = function(node, textValue) {
        if (node.nodeType == 3) {	// Text node
            node.nodeValue = textValue;
        } else {
            while (node.firstChild) {
                node.removeChild(node.firstChild);
            }
            node.appendChild(this.createText(textValue));
        }
    }

    this.getTextValue = function(node) {
        try {
            if (node.innerText) {
                return node.innerText.trim();
            } else {
                return node.textContent.trim();
            }
        } catch (e) {
            return node.innerText.trim();
        }
    }

    // Return the text value of 'node' and not of any child nodes
    this.getNodeTextValue = function(node) {
        var o = node.childNodes;
        var textValue = "";
        for (var i = 0, L = o.length; i < L; i++) {
            if (o[i].nodeType != 3) continue; // not text node
            textValue += this.getTextValue(o[i]);
        }
        return textValue;
    }

    this.addRemoveClassNames = function(obj, add, remove) {
        if (!add.ISARRAY) {
            add = [add];
        }
        if (!remove.ISARRAY) {
            remove = [remove];
        }
        for (var idx = 0; idx < remove.length; idx++) {
            this.removeClassName(obj, remove[idx]);
        }
        for (idx = 0; idx < add.length; idx++) {
            this.addClassName(obj, add[idx]);
        }
    }

    this.removeElementsByClassName = function(o, className, nodeName) {
        var oItems = NODE.getElementsByClassName(o, className);
        if (!oItems) return;
        for (var i = 0, j = oItems.length; i < j; i++) {
            if (nodeName) {
                if (nodeName.toLowerCase() !== oItems[i].nodeName.toLowerCase()) {
                    //	not the node name to remove, step to the next iteration without removing
                    continue;
                }
            }
            oItems[i].parentNode.removeChild(oItems[i]);
        }
    }

    this.removeChildNodes = function(o) {
        while (o.firstChild && o.nodeType == 1) {
            o.removeChild(o.firstChild);
        }
    }

    this.removeNode = function(o) {
        o.parentNode.removeChild(o);
    }

    this.getAncestorByAttributeRegExp = function(node, att, reg, limit) {
        var _i = 0;
        limit = limit || 0;
        while (node && node != document.documentElement && _i <= limit) {
            if (node.attributes[att] && reg.test(node.attributes[att].nodeValue)) {
                return node;
            }
            node = node.parentNode;
            if (limit) {
                _i++;
            }
        }
        return null;
    }

    this.getAttribute = function(node, attribute) {
        if (attribute == 'class') {
            return node.className || node.getAttribute("class");
        } else {
            return node.getAttribute(attribute);
        }
    }

    this.getDescendantsByAttributeRegExp = function(node, nodeName, att, reg) {
        var o = node.getElementsByTagName(nodeName);
        var res = [];
        for (var i = 0, j = o.length; i < j; i++) {
            if (this.getAttribute(o[i], att) && reg.test(this.getAttribute(o[i], att))) {
                res.push(o[i]);
            }
        }
        return (res.length) ? res : null;
    }

    this.getAncestor = function(o, tagName, className, limit, includeSelf) {
        if (tagName || className) {
            var obj = includeSelf ? o : o.parentNode;
            if (tagName) {
                tagName = tagName.toLowerCase();
            }
            if (!limit) {
                limit = 999;
            }
            while (obj && obj != document.documentElement) {
                // TODO: will the obj.tagName ever be required (can a parentNode not have a tagName?)
                if ((!tagName || (obj.tagName && obj.tagName.toLowerCase() == tagName)) &&
					(!className || this.hasClassName(obj, className))) {
                    return obj;
                }
                if (--limit <= 0) {
                    break;
                }
                obj = obj.parentNode;
            }
        }
        return null;
    }

    this.getSelfOrAncestor = function(o, tagName, className, limit) {
        return this.getAncestor(o, tagName, className, limit, true);
    }

    this.getSibling = function(o, tagName, className) {
        if (tagName || className) {
            var obj = o.nextSibling;
            if (tagName) {
                tagName = tagName.toLowerCase();
            }
            while (obj) {
                if ((!tagName || (obj.tagName.toLowerCase() == tagName)) &&
					(!className || this.hasClassName(obj, className))) {
                    return obj;
                }
                obj = obj.nextSibling;
            }
        }
        return null;
    }

    this.moveElement = function(body, element) {
        return body.insertBefore(body.removeChild(element), (body.firstChild ? body.firstChild : null));
    }

    this.insertAfter = function(o, node) {
        node.parentNode.insertBefore(o, node.nextSibling);
    }

    this.innerXML = function(node, xml) {
        try {
            if (node.ownerDocument.createRange) {  // doesn't exist in IE
                var r = node.ownerDocument.createRange();
                r.selectNodeContents(node); // this exceptions in Opera
                r.deleteContents();
                node.appendChild(r.createContextualFragment(xml));
            } else {
                node.innerHTML = xml;  // the default method is to use innerHTML
            }
        } catch (e) {
            node.innerHTML = xml;  // the default method is to use innerHTML
        }
        return node;
    }

    this.getChildrenByTagName = function(o, tagName, className) {
        var children = o.childNodes;
        var res = [];
        var element;
        tagName = tagName.toLowerCase();

        for (var i = 0; i < children.length; i++) {
            if (((element = children[i]).tagName && element.tagName.toLowerCase() == tagName) &&
				((className == null) || this.hasClassName(element, className))) {
                res.push(element);
            }
        }
        return res;
    }

    this.moveNodeToTop = function(node) {
        if (node.parentNode && node.nodeType == 1) {
            node.parentNode.insertBefore(node, node.parentNode.firstChild);
        }
    }

    this.getFirstChildByType = function(node, type, att, attValue) {
        for (var i = 0, j = node.childNodes.length; i < j; i++) {
            if (node.childNodes[i].nodeName && node.childNodes[i].nodeName.toLowerCase() == type.toLowerCase()) {
                if (att != null && attValue != null) {
                    var reg = '\\b' + attValue + '\\b';
                    var str = false;
                    str = (att.toLowerCase() == "class") ? node.childNodes[i].className : node.childNodes[i].getAttribute(att);
                    if (str && reg.test(str)) {
                        return node.childNodes[i];
                    }
                } else {
                    return node.childNodes[i];
                }
            }
        }
        return null;
    }

    // gets value of specific style property
    this.getStyle = function(obj, property) {
        if (obj.style[property]) {
            return obj.style[property]; 			// It's been set inline. Nice and quick
        } else if (obj.currentStyle) {
            return obj.currentStyle[property]; 	//IE's method + Opera 9 (I think - need to test)
        } else if (document.defaultView && document.defaultView.getComputedStyle) {
            property = property.replace(/([A-Z])/g, "-$1"); // follows css model of style writing e.g. text-align instead of textAlign
            property = property.toLowerCase();
            return document.defaultView.getComputedStyle(obj, "").getPropertyValue(property); // W3C's method
        } else {
            return null; // Some other browser
        }
    }

    this.isAncestor = function(node, possibleAncestorNode) {
        if (node) { // occurs when a mouseout is caused by a window coming in between the target window and the cursor
            for (var testNode = node.parentNode; testNode; testNode = testNode.parentNode) {
                if (testNode == possibleAncestorNode) return true;
            }
        }
        return false;
    }

    this.getAction = function(actionTable, node) {
        // Search for class names of a node or its ancestors.
        // Pass in a node and an array of classNames and actions
        // each action comprises :
        //	val - the object or string to return if className found
        //  tagName - optional, lowercase.
        //  className
        //  depth - optional.  If null, only the node is searched
        for (var index = 0, L = actionTable.length; index < L; index++) {
            var al = actionTable[index];
            if ((!al.tagName || node.tagName.toLowerCase() == al.tagName) &&
				(!al.className || this.hasClassName(node, al.className))) {
                return al.val;
            }
            if (al.depth) {
                if (this.getAncestor(node, al.tagName, al.className, al.depth)) return al.val;
            }
        }
        return "default";
    }

};

(function() {
    TESCO.system.event.document.addEventListener("beforeload",
        function() {
            _augmentDocument();
            _extendBrowser();
        }
    );

    function _augmentDocument() {
        //  add jsenabled class to body
        TESCO.system.DOM.node.removeClassName(document.body, "jsDisabled");
        TESCO.system.DOM.node.addClassName(document.body, "jsEnabled");
    }

    function _extendBrowser() {
        var _fixedDiv = new TESCO.UI.Rectangle.Position(document.createElement("div"));
            _fixedDiv.moveToY(10);
            document.body.appendChild(_fixedDiv.rect);
            TESCO.system.DOM.node.addClassName(_fixedDiv.rect, 'fixed')
        var _offset = parseInt(_fixedDiv.rect.offsetTop, 10);
            document.body.removeChild(_fixedDiv.rect);
        //	augment TESCO.system.browser
	    TESCO.system.browser.supportsFixed = !!_offset;
    }
})();

(function() {
	if (window.HTMLElement) {
		var _emptyTags = {
		   "IMG":   true,
		   "BR":    true,
		   "INPUT": true,
		   "META":  true,
		   "LINK":  true,
		   "PARAM": true,
		   "HR":    true
		};

		HTMLElement.prototype.__defineGetter__("outerHTML",
			function() {
				var attrs = this.attributes;
				var str = "<" + this.tagName;
				for(var i = 0, L = attrs.length; i < L; i++) {
					str += " " + attrs[i].name + "=\"" + attrs[i].value + "\"";
				}
				if(_emptyTags[this.tagName]) {
					return str + ">";
				}
				return str + ">" + this.innerHTML + "</" + this.tagName + ">";
			}
		);
	}
})();