var EventInterface = {
    preventDefault: function () {
    	// Cross browser function to prevent the default action from occuring.
        if (this.button == BTN_RIGHT) {
            if (OPERA) {
                // small trick to make opera happy with context menu
                // adds input button under mouse pointer
                no_ctmenu = window.no_ctmenu;
                if (!no_ctmenu) {
                    no_ctmenu = document.createElement('input');
                    no_ctmenu.type='button';
                    document.body.appendChild(no_ctmenu);
                }
                no_ctmenu.style.position = 'fixed';
                no_ctmenu.style.top = this.clientY - 2 + 'px';
                no_ctmenu.style.left = this.clientX - 2 + 'px';
                no_ctmenu.style.width = '5px';
                no_ctmenu.style.height = '5px';
                no_ctmenu.style.opacity = 0;
                no_ctmenu.style.zIndex = 10000;
            }
        }
		if (typeof this.returnValue != 'undefined') {
		    this.returnValue = false;
		} else {
            if (window._preventDefault instanceof Function) {
		        window._preventDefault.call(this);
            }
		}
    	return false;
    },

    stopPropagation: function () {
    	// Cross browser function to prevent the event from bubbling.
		if (typeof this.cancelBubble != 'undefined') {
		    this.cancelBubble = true;
		} else {
            if (window._stopPropagation instanceof Function) {
		        window._stopPropagation.call(this);
            }
		}
    	return false;
    },

    cursor: function () {
    	var x = 0, y = 0;

    	if (this.pageX || this.pageY)	{
    		x = this.pageX;
    		y = this.pageY;
    	} else if (this.clientX || this.clientY) {
    		x = this.clientX + document.scrollLeft();
    		y = this.clientY + document.scrollTop();
    	} else if (this.screenX || this.screenY) {
    	    x = this.screenX;
    	    y = this.screenY;
    	}

        return new Pixel(x, y);
    },

    element: function () {
        return this.srcElement || this.currentTarget;
    }
};

if (window.Event) {
    window._preventDefault = window.Event.prototype.preventDefault;
    window._stopPropagation = window.Event.prototype.stopPropagation;
    extend(window.Event, EventInterface);
}

var NodeClassInterface = {
    //return all classes as an array
    getClasses: function() {
        return this.className ? this.className.split(new RegExp('\\s+')) : [];
    },

    //checks class
    hasClass: function(className) {
        var classes = this.getClasses();
        for (var i = 0; i < arguments.length; i ++) {
            if (classes.has(arguments[i])) {
                return true;
            }
        }
        return false;
    },

    //add class
    addClass: function(className) {
        for (var i = 0; i < arguments.length; i ++) {
            if (!this.hasClass(arguments[i])) {
                this.className += ' ' + arguments[i];
            }
        }
    },

    //remove class
    removeClass: function(className) {
        var classes = this.getClasses();
        for (var i = 0; i < arguments.length; i ++) {
            classes.pull(arguments[i]);
        }
        this.className = classes.join(' ');
    },

    //replace class
    replaceClass: function(find, replace) {
        var classes = this.getClasses();
        if ((key = classes.key(find)) !== false) {
            classes[key] = replace;
            this.className = classes.join(' ');
        } else {
            this.addClass(replace);
        }
    },

    //set class if not isset, otherwise remove it
    flipClass: function(className) {
        if (this.exists(className)) {
            this.remove(className);
        } else {
            this.add(className);
        }
    },

    copyClassesTo: function (element) {
        var classes = this.getClasses();
        classes.forEach (function () {
            element.addClass(this);
        });
    },
    
    setClassesTo: function (element) {
        element.className = this.className;
    }
};


var NodeEventInterface =  {
    _events: null,

    addEvent: function (event, action, useCapture) {
        var source = action.parse();
        var $event = source.params[0] || 'event';
        source.body = 'if (IE) {extend(' + $event + ', EventInterface)};' + source.body;
        action = new Function(source.params, source.body);

        var hash = action.toString();
        if (!this._events) {
            this._events = new Object;
        }

        if (!this._events[event]) {
            this._events[event] = [];
        }

        if (!this._events[event][hash]) {

            this._events[event][hash] = action;

        	if (!useCapture) {
        	    useCapture = false;
        	}
        	if (this.addEventListener) {
        		this.addEventListener(event, action, useCapture);
        	} else if (this.attachEvent) {
        	    this.attachEvent('on' + event, action);
        	}
        }

    	return action;
    },

    removeEvent: function (event, action, useCapture) {
        var source = action.parse();
        var $event = source.params[0] || 'event';
        source.body = 'if (IE) {extend(' + $event + ', EventInterface)};' + source.body;
        action = new Function(source.params, source.body);

        var hash = action.toString();
        if (this._events[event] && this._events[event][hash]) {
            action = this._events[event][hash];
        }

        if (!useCapture) {
    	    useCapture = false;
    	}
    	if (this.removeEventListener) {
    	    this.removeEventListener(event, action, useCapture);
    	} else if (this.detachEvent) {
    	    this.detachEvent('on' + event, action);
    	}
        
        if (this._events[event] && this._events[event][hash]) {
    	   delete(this._events[event][hash]);
       }
    }
};

var NodeStructureInterface = {
    coordinates: function () {
        var x = 0, y = 0;
        var element = this;
        while (element) {
            x += element.offsetLeft;
            y += element.offsetTop;
            element = element.offsetParent;
        }
        return new Pixel(x, y);
    },

    isDescendantOf: function (node) {
        var parent = this.parentNode;
        while (parent) {
            if (parent == node) {
                return true;
            }
            parent = parent.parentNode;
        }
        return false;
    },

    isAncestorOf: function (node) {
        var parent = node.parentNode;
        while (parent) {
            if (parent == this) {
                return true;
            }
            parent = parent.parentNode;
        }
        return false;
    },

    removeChildren: function () {
        while (this.firstChild) {
            this.removeChild(this.firstChild);
        }
    },

    firstTag: function (tag) {
        if (typeof tag == 'undefined') {
            tag = false;
        }
        var current = this.firstChild;
        while (current && (!current.tagName || (tag && current.tagName.toLowerCase() != tag.toLowerCase()))) {
            current = current.nextSibling;
        }
        return current;
    },

    nextTag: function (tag) {
        if (typeof tag == 'undefined') {
            tag = false;
        }
        var current = this.nextSibling;
        while (current && (!current.tagName || (tag && current.tagName.toLowerCase() != tag.toLowerCase()))) {
            current = current.nextSibling;
        }
        return current;
    },

    ancestorTag: function (tag) {
        if (typeof tag == 'undefined') {
            tag = false;
        }
        var current = this.parentNode;
        while (current && (!current.tagName || (tag && current.tagName.toLowerCase() != tag.toLowerCase()))) {
            current = current.parentNode;
        }
        return current;
    },

    previousTag: function (tag) {
        if (typeof tag == 'undefined') {
            tag = false;
        }
        var current = this.previousSibling;
        while (current && (!current.tagName || (tag && current.tagName.toLowerCase() != tag.toLowerCase()))) {
            current = current.previousSibling;
        }
        return current;
    },

    replace: function (node) {
        this.parentNode.replaceChild(node, this);
    },

    getElementsByClassName: function (class_name) {
        var elements = this.getElementsByTagName('*');
        var found = [];
        for (var i = 0; i < elements.length; i ++) {
            if (elements[i].hasClass(class_name)) {
                found.push(elements[i]);
            }
        }
        return found;
    }

};

var DOM = new Singletone ({
    ELEMENT_NODE:        1,
    TEXT_NODE:           3,
    CDATA_SECTION_NODE : 4,
    DOCUMENT_NODE :      9,

    enchaseNode: function (node) {
        node._cloneNode = node.cloneNode;
        node.cloneNode = function (flag) {
            var clone = this._cloneNode(flag);
            self.enchaseNode(clone);
            return clone;
        }
        
        switch (node.nodeName.toUpperCase()) {
        case 'TABLE':
            node._insertRow = node.insertRow;
            node.insertRow = function (index) {
                var row = (HTMLTableElement.prototype.insertRow || this._insertRow).call(this, index);
                self.enchaseNode(row);
                return row;
            }
            
            node._createTHead = node.createTHead;
            node.createTHead = function () {
                var thead = (HTMLTableElement.prototype.insertRow || this._createTHead).call(this);
                self.enchaseNode(thead);
                return thead;
            }
            
            node._createTFoot = node.createTFoot;
            node.createTFoot = function () {
                var tfoot = (HTMLTableElement.prototype.insertRow || this._createTFoot).call(this);
                self.enchaseNode(tfoot);
                return tfoot;
            }
            
            break;
            
        case 'TR':
            node._insertCell = node.insertCell;
            node.insertCell = function (index) {
                var cell = (HTMLTableRowElement || this._insertCell).call(this, index);
                self.enchaseNode(cell);
                return cell;
            }
            break;
        }
        
        if ([self.ELEMENT_NODE, self.DOCUMENT_NODE].has(node.nodeType)) {
            extend(node, NodeClassInterface);
            extend(node, NodeEventInterface);
            extend(node, NodeStructureInterface);
    
            if (NodeEffectInterface instanceof Object) {
                extend(node, NodeEffectInterface);
            }
            if (node.tagName && node.tagName.toUpperCase() == 'FORM') {
                if (typeof FormsInterface != 'undefined') {
                    extend(node, FormsInterface);
                }
            }
        }
    },

    enchaseDocument: function () {
        document._createElement = document.createElement;
        document.createElement = function (tag_name) {
            if (typeof Document == 'undefined') {
                var element = document._createElement(tag_name);
            } else {
                var element = (Document.prototype.createElement || document._createElement).call(document, tag_name);
            }
            self.enchaseNode(element);
            return element;
        }
        
        document._getElementById = document.getElementById;
        document.getElementById = function (id) {
            if (typeof Document == 'undefined') {
                var element = document._getElementById(id);
            } else {
                var element = (Document.prototype.getElementById || document._getElementById).call(document, id);
            }
            if (element) {
                self.enchaseDocumentNodes(element);
            }
            return element;
        }

        document.scrollLeft = function () {
            return self.pageXOffset
                || (document.documentElement && document.documentElement.scrollLeft)
                || (document.body && document.body.scrollLeft);
        }
        document.scrollTop = function () {
            return self.pageYOffset
                || (document.documentElement && document.documentElement.scrollTop)
                || (document.body && document.body.scrollTop);
        }
        document.size = function () {
            var w = 0, h = 0;
            var win = window.window;
    
            if (win.self.innerHeight) {
                w = win.self.innerWidth;
                h = win.self.innerHeight;
            } else if (win.document.documentElement && win.document.documentElement.clientWidth) {
                w = win.document.documentElement.clientWidth;
                h = win.document.documentElement.clientHeight;
            } else if (win.document.body) {
                w = win.document.body.clientWidth;
                h = win.document.body.clientHeight;
            }
            return {width: w, height: h};
        }
        self.enchaseNode(document);

        if (typeof document.defaultView == 'undefined') {
            document.defaultView = {};
        }
        if (typeof document.defaultView.getComputedStyle == 'undefined') {
            document.defaultView.getComputedStyle = function (element, pseudoElement) {
                return element.currentStyle;
            }
        }
    },

    enchaseDocumentNodes: function (node) {
        if (node) {
            self.enchaseNode(node);
        }
        var all = (node || document.body).getElementsByTagName('*');
        for (var i = 0; i < all.length; i ++) {
            self.enchaseNode(all[i]);
        }
    }
});
DOM.enchaseDocument();

document.addLoader(
    function () {
        DOM.enchaseDocumentNodes();
    }
);

function ID(id) {
    return document.getElementById(id);
}