/**
 * Encapsulates a DOM element, adding simple DOM manipulation facilities, normalizing for browser differences.
 * 
 * All instances of this class inherit the methods of Ext.Fx making visual effects easily available to all DOM elements.
 * 
 * Note that the events documented in this class are not Ext events, they encapsulate browser events. To access the 
 * underlying browser event, see Ext.EventObject.browserEvent. Some older browsers may not support the full range of 
 * events. Which events are supported is beyond the control of Ext JS.
 * 
 * ## Usage
 * 
 *     // by id
 *     var el = Ext.get("my-div");
 * 
 *     // by DOM element reference
 *     var el = Ext.get(myDivElement);
 * 
 * ## Composite (Collections of) Elements
 * 
 * For working with collections of Elements, see Ext.CompositeElement
 */
Ext.define('Ext.dom.Element', {
    extend: 'Ext.dom.AbstractElement',
    alternateClassName: 'Ext.Element',

    requires: [
        'Ext.dom.Query',
        'Ext.dom.Helper'
    ],

    mixins: [
        'Ext.mixin.Observable'
    ],

    observableType: 'element',

    xtype: 'element',

    WIDTH: 'width',

    HEIGHT: 'height',

    TOP: 'top',

    RIGHT: 'right',

    BOTTOM: 'bottom',

    LEFT: 'left',

    SEPARATOR: '-',

    spacesRegex: /\s+/,

    statics: {
        CREATE_ATTRIBUTES: {
            style: 'style',
            className: 'className',
            cls: 'cls',
            classList: 'classList',
            text: 'text',
            hidden: 'hidden',
            html: 'html',
            children: 'children'
        },

        create: function(attributes, domNode) {
            var ATTRIBUTES = this.CREATE_ATTRIBUTES,
                element, elementStyle, tag, value, name, i, ln;

            if (!attributes) {
                attributes = {};
            }

            if (attributes.isElement) {
                return attributes.dom;
            }
            else if ('nodeType' in attributes) {
                return attributes;
            }

            if (typeof attributes == 'string') {
                return document.createTextNode(attributes);
            }

            tag = attributes.tag;

            if (!tag) {
                tag = 'div';
            }

            element = document.createElement(tag);
            elementStyle = element.style;

            for (name in attributes) {
                if (name != 'tag' && attributes.hasOwnProperty(name)) {
                    value = attributes[name];

                    switch (name) {
                        case ATTRIBUTES.style:
                                if (typeof value == 'string') {
                                    element.setAttribute(name, value);
                                }
                                else {
                                    for (i in value) {
                                        if (value.hasOwnProperty(i)) {
                                            elementStyle[i] = value[i];
                                        }
                                    }
                                }
                            break;

                        case ATTRIBUTES.className:
                        case ATTRIBUTES.cls:
                            element.className = value;
                            break;

                        case ATTRIBUTES.classList:
                            element.className = value.join(' ');
                            break;

                        case ATTRIBUTES.text:
                            element.textContent = value;
                            break;

                        case ATTRIBUTES.hidden:
                            if (value) {
                                element.style.display = 'none';
                            }
                            break;

                        case ATTRIBUTES.html:
                            element.innerHTML = value;
                            break;

                        case ATTRIBUTES.children:
                            for (i = 0,ln = value.length; i < ln; i++) {
                                element.appendChild(this.create(value[i], true));
                            }
                            break;

                        default:
                            element.setAttribute(name, value);
                    }
                }
            }

            if (domNode) {
                return element;
            }
            else {
                return Ext.get(element);
            }
        },

        documentElement: null,

        cache: {},

        get: function(element) {
            var cache = this.cache,
                instance, dom, id;

            if (!element) {
                return null;
            }

            if (typeof element == 'string') {
                if (cache.hasOwnProperty(element)) {
                    return cache[element];
                }

                if (!(dom = document.getElementById(element))) {
                    return null;
                }

                cache[element] = instance = new this(dom);

                return instance;
            }

            if ('tagName' in element) { // dom element
                id = element.id;

                if (cache.hasOwnProperty(id)) {
                    return cache[id];
                }

                instance = new this(element);
                cache[instance.getId()] = instance;

                return instance;
            }

            if (element.isElement) {
                return element;
            }

            if (element.isComposite) {
                return element;
            }

            if (Ext.isArray(element)) {
                return this.select(element);
            }

            if (element === document) {
                // create a bogus element object representing the document object
                if (!this.documentElement) {
                    this.documentElement = new this(document.documentElement);
                    this.documentElement.setId('ext-application');
                }

                return this.documentElement;
            }

            return null;
        }
    },

    isElement: true,

    classNameSplitRegex: /[\s]+/,

    isSynchronized: false,

    constructor: function(dom) {
        if (typeof dom == 'string') {
            dom = document.getElementById(dom);
        }

        if (!dom) {
            throw new Error("Invalid domNode reference or an id of an existing domNode: " + dom);
        }

        this.dom = dom;

        this.getUniqueId();
    },

    getUniqueId: function() {
        var id = this.id,
            dom;

        if (!id) {
            dom = this.dom;

            if (dom.id.length > 0) {
                this.id = id = dom.id;
            }
            else {
                dom.id = id = this.mixins.identifiable.getUniqueId.call(this);
            }

            Ext.Element.cache[id] = this;
        }

        return id;
    },

    setId: function(id) {
        var currentId = this.id,
            cache = Ext.Element.cache;

        if (currentId) {
            delete cache[currentId];
        }

        this.dom.id = id;
        this.id = id;

        cache[id] = this;

        return this;
    },

    /**
     * @private
     */
    synchronize: function() {
        var dom = this.dom,
            hasClassMap = {},
            className = dom.className,
            classList, i, ln, name;

        if (className.length > 0) {
            classList = dom.className.split(this.classNameSplitRegex);

            for (i = 0,ln = classList.length; i < ln; i++) {
                name = classList[i];
                hasClassMap[name] = true;
            }
        }
        else {
            classList = [];
        }

        this.classList = classList;

        this.hasClassMap = hasClassMap;

        this.isSynchronized = true;

        return this;
    },

    /**
     * Adds the given CSS class(es) to this Element
     * @param {String} names The CSS class(es) to add to this element
     * @param {String} prefix Optional prefix to prepend to each class
     * @param {String} suffix Optional suffix to append to each class
     */
    addCls: function(names, prefix, suffix) {
        if (!names) {
            return this;
        }

        if (!this.isSynchronized) {
            this.synchronize();
        }

        var dom = this.dom,
            map = this.hasClassMap,
            classList = this.classList,
            SEPARATOR = this.SEPARATOR,
            i, ln, name;

        prefix = prefix ? prefix + SEPARATOR : '';
        suffix = suffix ? SEPARATOR + suffix : '';

        if (typeof names == 'string') {
            names = names.split(this.spacesRegex);
        }

        for (i = 0,ln = names.length; i < ln; i++) {
            name = prefix + names[i] + suffix;

            if (!map[name]) {
                map[name] = true;
                classList.push(name);
            }
        }

        dom.className = classList.join(' ');

        return this;
    },

    /**
     * Removes the given CSS class(es) from this Element
     * @param {String} names The CSS class(es) to remove from this element
     * @param {String} prefix Optional prefix to prepend to each class to be removed
     * @param {String} suffix Optional suffix to append to each class to be removed
     */
    removeCls: function(names, prefix, suffix) {
        if (!names) {
            return this;
        }

        if (!this.isSynchronized) {
            this.synchronize();
        }


        if (!suffix) {
            suffix = '';
        }

        var dom = this.dom,
            map = this.hasClassMap,
            classList = this.classList,
            SEPARATOR = this.SEPARATOR,
            i, ln, name;

        prefix = prefix ? prefix + SEPARATOR : '';
        suffix = suffix ? SEPARATOR + suffix : '';

        if (typeof names == 'string') {
            names = names.split(this.spacesRegex);
        }

        for (i = 0,ln = names.length; i < ln; i++) {
            name = prefix + names[i] + suffix;

            if (map[name]) {
                delete map[name];
                Ext.Array.remove(classList, name);
            }
        }

        dom.className = classList.join(' ');

        return this;
    },

    replaceCls: function(oldName, newName, prefix, suffix) {
        return this.removeCls(oldName, prefix, suffix).addCls(newName, prefix, suffix);
    },

    hasCls: function(name) {
        if (!this.isSynchronized) {
            this.synchronize();
        }

        return this.hasClassMap.hasOwnProperty(name);
    },

    show: function() {
        this.dom.style.display = '';
    },

    hide: function() {
        this.dom.style.display = 'none !important';
    },

    setHtml: function(html) {
        this.dom.innerHTML = html;
    },

    setHTML: function() {
        this.setHtml.apply(this, arguments);
    },

    setText: function(text) {
        this.dom.textContent = text;
    },

    setWidth: function(width) {
        return this.setLengthValue(this.WIDTH, width);
    },

    setHeight: function(height) {
        return this.setLengthValue(this.HEIGHT, height);
    },

    setTop: function(top) {
        return this.setLengthValue(this.TOP, top);
    },

    setRight: function(right) {
        return this.setLengthValue(this.RIGHT, right);
    },

    setBottom: function(bottom) {
        return this.setLengthValue(this.BOTTOM, bottom);
    },

    setLeft: function(left) {
        return this.setLengthValue(this.LEFT, left);
    },

    setMargin: function(margin) {
        if (margin || margin === 0) {
            margin = this.self.unitizeBox((margin === true) ? 5 : margin);
        }
        else {
            margin = null;
        }
        this.dom.style.margin = margin;
    },

    setPadding: function(padding) {
        if (padding || padding === 0) {
            padding = this.self.unitizeBox((padding === true) ? 5 : padding);
        }
        else {
            padding = null;
        }
        this.dom.style.padding = padding;
    },

    setBorder: function(border) {
        if (border || border === 0) {
            border = this.self.unitizeBox((border === true) ? 1 : border);
        }
        else {
            border = null;
        }
        this.dom.style.borderWidth = border;
    },

    setLengthValue: function(name, value) {
        if (typeof value == 'number') {
            value = value + 'px';
        }

        this.dom.style[name] = value;

        return this;
    },

    getParent: function() {
        return Ext.get(this.dom.parentNode);
    },

    getFirstChild: function() {
        return Ext.get(this.dom.firstElementChild);
    },

    append: function(element) {
        this.dom.appendChild(Ext.getDom(element));

        return this;
    },

    insertFirst: function(element) {
        var elementDom = Ext.getDom(element),
            dom = this.dom,
            firstChild = dom.firstChild;

        if (!firstChild) {
            dom.appendChild(elementDom);
        }
        else {
            dom.insertBefore(elementDom, firstChild);
        }

        return this;
    },

    wrap: function(config, domNode) {
        var dom = this.dom,
            wrapper = this.self.create(config, domNode),
            wrapperDom = (domNode) ? wrapper : wrapper.dom,
            parentNode = dom.parentNode;

        if (parentNode) {
            parentNode.insertBefore(wrapperDom, dom);
        }

        wrapperDom.appendChild(dom);

        return wrapper;
    },

    wrapAllChildren: function(config) {
        var dom = this.dom,
            children = dom.childNodes,
            wrapper = this.self.create(config),
            wrapperDom = wrapper.dom;

        while (children.length > 0) {
            wrapperDom.appendChild(dom.firstChild);
        }

        dom.appendChild(wrapperDom);

        return wrapper;
    },

    unwrapAllChildren: function() {
        var dom = this.dom,
            children = dom.childNodes,
            parentNode = dom.parentNode;

        if (parentNode) {
            while (children.length > 0) {
                parentNode.insertBefore(dom, dom.firstChild);
            }

            this.destroy();
        }
    },

    unwrap: function() {
        var dom = this.dom,
            parentNode = dom.parentNode,
            grandparentNode;

        if (parentNode) {
            grandparentNode = parentNode.parentNode;
            grandparentNode.insertBefore(dom, parentNode);
            grandparentNode.removeChild(parentNode);
        }
        else {
            grandparentNode = document.createDocumentFragment();
            grandparentNode.appendChild(dom);
        }

        return this;
    },

    redraw: function() {
        var dom = this.dom,
            domStyle = dom.style;

        domStyle.display = 'none';
        dom.offsetHeight;
        domStyle.display = '';
    },

    isPainted: function() {
        return Boolean(this.dom.offsetParent);
    },

    destroy: function() {
        this.destroy = Ext.emptyFn;

        var cache = this.self.cache,
            dom = this.dom;

        if (dom && dom.parentNode && dom.tagName != 'BODY') {
            dom.parentNode.removeChild(dom);
            delete cache[this.id];
        }

        delete this.dom;
    }

}, function(Element) {
    Ext.elements = Ext.cache = Element.cache;

    Ext.get = function(element) {
        return Element.get.call(Element, element);
    }
});