Document.implement({
    newElement: function (tag, props) {
		var orgTag = tag;
        try {
			if (Browser.Engine.trident && props) {
            
				['name', 'type', 'checked'].each(function (attribute) {
					if (!props[attribute]) return;
					tag += ' ' + attribute + '="' + props[attribute] + '"';
					if (attribute != 'checked') delete props[attribute]
				});
				tag = '<' + tag + '>'
			}
			return $.element(this.createElement(tag)).set(props)
		}
		catch(err) {
			tag = orgTag;
			return $.element(this.createElement(tag)).set(props)
		}
    },
    newTextNode: function (text) {
        return this.createTextNode(text)
    },
    getDocument: function () {
        return this
    },
    getWindow: function () {
        return this.defaultView || this.parentWindow
    },
    purge: function () {
        var elements = this.getElementsByTagName('*');
        for (var i = 0, l = elements.length; i < l; i++) Browser.freeMem(elements[i])
    }
});
var Element = new Native({
    name: 'Element',
    legacy: window.Element,
    initialize: function (tag, props) {
        var konstructor = Element.Constructors.get(tag);
        if (konstructor) return konstructor(props);
        if (typeof tag == 'string') return document.newElement(tag, props);
        return $(tag).set(props)
    },
    afterImplement: function (key, value) {
        if (!Array[key]) Elements.implement(key, Elements.multi(key));
        Element.Prototype[key] = value
    }
});
Element.Prototype = {
    $family: {
        name: 'element'
    }
};
Element.Constructors = new Hash;
var IFrame = new Native({
    name: 'IFrame',
    generics: false,
    initialize: function () {
        var params = Array.link(arguments, {
            properties: Object.type,
            iframe: $defined
        });
        var props = params.properties || {};
        var iframe = $(params.iframe) || false;
        var onload = props.onload || $empty;
        delete props.onload;
        props.id = props.name = $pick(props.id, props.name, iframe.id, iframe.name, 'IFrame_' + $time());
        iframe = new Element(iframe || 'iframe', props);
        var onFrameLoad = function () {
                var host = $try(function () {
                    return iframe.contentWindow.location.host
                });
                if (host && host == window.location.host) {
                    var win = new Window(iframe.contentWindow);
                    var doc = new Document(iframe.contentWindow.document);
                    $extend(win.Element.prototype, Element.Prototype)
                }
                onload.call(iframe.contentWindow, iframe.contentWindow.document)
            };
        (!window.frames[props.id]) ? iframe.addListener('load', onFrameLoad) : onFrameLoad();
        return iframe
    }
});
var Elements = new Native({
    initialize: function (elements, options) {
        options = $extend({
            ddup: true,
            cash: true
        }, options);
        elements = elements || [];
        if (options.ddup || options.cash) {
            var uniques = {},
                returned = [];
            for (var i = 0, l = elements.length; i < l; i++) {
                var el = $.element(elements[i], !options.cash);
                if (options.ddup) {
                    if (uniques[el.uid]) continue;
                    uniques[el.uid] = true
                }
                returned.push(el)
            }
            elements = returned
        }
        return (options.cash) ? $extend(elements, this) : elements
    }
});
Elements.implement({
    filter: function (filter, bind) {
        if (!filter) return this;
        return new Elements(Array.filter(this, (typeof filter == 'string') ?
        function (item) {
            return item.match(filter)
        } : filter, bind))
    }
});
Elements.multi = function (property) {
    return function () {
        var items = [];
        var elements = true;
        for (var i = 0, j = this.length; i < j; i++) {
            var returns = this[i][property].apply(this[i], arguments);
            items.push(returns);
            if (elements) elements = ($type(returns) == 'element')
        }
        return (elements) ? new Elements(items) : items
    }
};
Window.implement({
    $: function (el, nocash) {
        if (el && el.$family && el.uid) return el;
        var type = $type(el);
        return ($[type]) ? $[type](el, nocash, this.document) : null
    },
    $$: function (selector) {
        if (arguments.length == 1 && typeof selector == 'string') return this.document.getElements(selector);
        var elements = [];
        var args = Array.flatten(arguments);
        for (var i = 0, l = args.length; i < l; i++) {
            var item = args[i];
            switch ($type(item)) {
            case 'element':
                item = [item];
                break;
            case 'string':
                item = this.document.getElements(item, true);
                break;
            default:
                item = false
            }
            if (item) elements.extend(item)
        }
        return new Elements(elements)
    },
    getDocument: function () {
        return this.document
    },
    getWindow: function () {
        return this
    }
});
$.string = function (id, nocash, doc) {
    id = doc.getElementById(id);
    return (id) ? $.element(id, nocash) : null
};
$.element = function (el, nocash) {
    $uid(el);
    if (!nocash && !el.$family && !(/^object|embed$/i).test(el.tagName)) {
        var proto = Element.Prototype;
        for (var p in proto) el[p] = proto[p]
    };
    return el
};
$.object = function (obj, nocash, doc) {
    if (obj.toElement) return $.element(obj.toElement(doc), nocash);
    return null
};
$.textnode = $.whitespace = $.window = $.document = $arguments(0);
Native.implement([Element, Document], {
    getElement: function (selector, nocash) {
        return $(this.getElements(selector, true)[0] || null, nocash)
    },
    getElements: function (tags, nocash) {
        tags = tags.split(',');
        var elements = [];
        var ddup = (tags.length > 1);
        tags.each(function (tag) {
            var partial = this.getElementsByTagName(tag.trim());
            (ddup) ? elements.extend(partial) : elements = partial
        }, this);
        return new Elements(elements, {
            ddup: ddup,
            cash: !nocash
        })
    }
});
Element.Storage = {
    get: function (uid) {
        return (this[uid] || (this[uid] = {}))
    }
};
Element.Inserters = new Hash({
    before: function (context, element) {
        if (element.parentNode) element.parentNode.insertBefore(context, element)
    },
    after: function (context, element) {
        if (!element.parentNode) return;
        var next = element.nextSibling;
        (next) ? element.parentNode.insertBefore(context, next) : element.parentNode.appendChild(context)
    },
    bottom: function (context, element) {
        element.appendChild(context)
    },
    top: function (context, element) {
        var first = element.firstChild;
        (first) ? element.insertBefore(context, first) : element.appendChild(context)
    }
});
Element.Inserters.inside = Element.Inserters.bottom;
Element.Inserters.each(function (value, key) {
    var Key = key.capitalize();
    Element.implement('inject' + Key, function (el) {
        value(this, $(el, true));
        return this
    });
    Element.implement('grab' + Key, function (el) {
        value($(el, true), this);
        return this
    })
});
Element.implement({
    getDocument: function () {
        return this.ownerDocument
    },
    getWindow: function () {
        return this.ownerDocument.getWindow()
    },
    getElementById: function (id, nocash) {
        var el = this.ownerDocument.getElementById(id);
        if (!el) return null;
        for (var parent = el.parentNode; parent != this; parent = parent.parentNode) {
            if (!parent) return null
        }
        return $.element(el, nocash)
    },
    set: function (prop, value) {
        switch ($type(prop)) {
        case 'object':
            for (var p in prop) this.set(p, prop[p]);
            break;
        case 'string':
            var property = Element.Properties.get(prop);
            (property && property.set) ? property.set.apply(this, Array.slice(arguments, 1)) : this.setProperty(prop, value)
        }
        return this
    },
    get: function (prop) {
        var property = Element.Properties.get(prop);
        return (property && property.get) ? property.get.apply(this, Array.slice(arguments, 1)) : this.getProperty(prop)
    },
    erase: function (prop) {
        var property = Element.Properties.get(prop);
        (property && property.erase) ? property.erase.apply(this, Array.slice(arguments, 1)) : this.removeProperty(prop);
        return this
    },
    match: function (tag) {
        return (!tag || Element.get(this, 'tag') == tag)
    },
    inject: function (el, where) {
        Element.Inserters.get(where || 'bottom')(this, $(el, true));
        return this
    },
    wraps: function (el, where) {
        el = $(el, true);
        return this.replaces(el).grab(el, where)
    },
    grab: function (el, where) {
        Element.Inserters.get(where || 'bottom')($(el, true), this);
        return this
    },
    appendText: function (text, where) {
        return this.grab(this.getDocument().newTextNode(text), where)
    },
    adopt: function () {
        Array.flatten(arguments).each(function (element) {
            element = $(element, true);
            if (element) this.appendChild(element)
        }, this);
        return this
    },
    dispose: function () {
        return (this.parentNode) ? this.parentNode.removeChild(this) : this
    },
    clone: function (contents, keepid) {
        switch ($type(this)) {
        case 'element':
            var attributes = {};
            for (var j = 0, l = this.attributes.length; j < l; j++) {
                var attribute = this.attributes[j],
                    key = attribute.nodeName.toLowerCase();
                if (Browser.Engine.trident && (/input/i).test(this.tagName) && (/width|height/).test(key)) continue;
                var value = (key == 'style' && this.style) ? this.style.cssText : attribute.nodeValue;
                if (!$chk(value) || key == 'uid' || (key == 'id' && !keepid)) continue;
                if (value != 'inherit' && ['string', 'number'].contains($type(value))) attributes[key] = value
            }
            var element = new Element(this.nodeName.toLowerCase(), attributes);
            if (contents !== false) {
                for (var i = 0, k = this.childNodes.length; i < k; i++) {
                    var child = Element.clone(this.childNodes[i], true, keepid);
                    if (child) element.grab(child)
                }
            }
            return element;
        case 'textnode':
            return document.newTextNode(this.nodeValue)
        }
        return null
    },
    replaces: function (el) {
        el = $(el, true);
        el.parentNode.replaceChild(this, el);
        return this
    },
    hasClass: function (className) {
        return this.className.contains(className, ' ')
    },
    addClass: function (className) {
        if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean();
        return this
    },
    removeClass: function (className) {
        this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1').clean();
        return this
    },
    toggleClass: function (className) {
        return this.hasClass(className) ? this.removeClass(className) : this.addClass(className)
    },
    getComputedStyle: function (property) {
        if (this.currentStyle) return this.currentStyle[property.camelCase()];
        var computed = this.getWindow().getComputedStyle(this, null);
        return (computed) ? computed.getPropertyValue([property.hyphenate()]) : null
    },
    empty: function () {
        $A(this.childNodes).each(function (node) {
            Browser.freeMem(node);
            Element.empty(node);
            Element.dispose(node)
        }, this);
        return this
    },
    destroy: function () {
        Browser.freeMem(this.empty().dispose());
        return null
    },
    getSelected: function () {
        return new Elements($A(this.options).filter(function (option) {
            return option.selected
        }))
    },
    toQueryString: function () {
        var queryString = [];
        this.getElements('input, select, textarea').each(function (el) {
            if (!el.name || el.disabled) return;
            var value = (el.tagName.toLowerCase() == 'select') ? Element.getSelected(el).map(function (opt) {
                return opt.value
            }) : ((el.type == 'radio' || el.type == 'checkbox') && !el.checked) ? null : el.value;
            $splat(value).each(function (val) {
                if (val) queryString.push(el.name + '=' + encodeURIComponent(val))
            })
        });
        return queryString.join('&')
    },
    getProperty: function (attribute) {
        var EA = Element.Attributes,
            key = EA.Props[attribute];
        var value = (key) ? this[key] : this.getAttribute(attribute, 2);
        return (EA.Bools[attribute]) ? !! value : (key) ? value : value || null
    },
    getProperties: function () {
        var args = $A(arguments);
        return args.map(function (attr) {
            return this.getProperty(attr)
        }, this).associate(args)
    },
    setProperty: function (attribute, value) {
        var EA = Element.Attributes,
            key = EA.Props[attribute],
            hasValue = $defined(value);
        if (key && EA.Bools[attribute]) value = (value || !hasValue) ? true : false;
        else if (!hasValue) return this.removeProperty(attribute);
        (key) ? this[key] = value : this.setAttribute(attribute, value);
        return this
    },
    setProperties: function (attributes) {
        for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
        return this
    },
    removeProperty: function (attribute) {
        var EA = Element.Attributes,
            key = EA.Props[attribute],
            isBool = (key && EA.Bools[attribute]);
        (key) ? this[key] = (isBool) ? false : '' : this.removeAttribute(attribute);
        return this
    },
    removeProperties: function () {
        Array.each(arguments, this.removeProperty, this);
        return this
    }
});
(function () {
    var walk = function (element, walk, start, match, all, nocash) {
            var el = element[start || walk];
            var elements = [];
            while (el) {
                if (el.nodeType == 1 && (!match || Element.match(el, match))) {
                    elements.push(el);
                    if (!all) break
                }
                el = el[walk]
            }
            return (all) ? new Elements(elements, {
                ddup: false,
                cash: !nocash
            }) : $(elements[0], nocash)
        };
    Element.implement({
        getPrevious: function (match, nocash) {
            return walk(this, 'previousSibling', null, match, false, nocash)
        },
        getAllPrevious: function (match, nocash) {
            return walk(this, 'previousSibling', null, match, true, nocash)
        },
        getNext: function (match, nocash) {
            return walk(this, 'nextSibling', null, match, false, nocash)
        },
        getAllNext: function (match, nocash) {
            return walk(this, 'nextSibling', null, match, true, nocash)
        },
        getFirst: function (match, nocash) {
            return walk(this, 'nextSibling', 'firstChild', match, false, nocash)
        },
        getLast: function (match, nocash) {
            return walk(this, 'previousSibling', 'lastChild', match, false, nocash)
        },
        getParent: function (match, nocash) {
            return walk(this, 'parentNode', null, match, false, nocash)
        },
        getParents: function (match, nocash) {
            return walk(this, 'parentNode', null, match, true, nocash)
        },
        getChildren: function (match, nocash) {
            return walk(this, 'nextSibling', 'firstChild', match, true, nocash)
        },
        hasChild: function (el) {
            el = $(el, true);
            return ( !! el && $A(this.getElementsByTagName(el.tagName)).contains(el))
        }
    })
})();
Element.Properties = new Hash;
Element.Properties.style = {
    set: function (style) {
        this.style.cssText = style
    },
    get: function () {
        return this.style.cssText
    },
    erase: function () {
        this.style.cssText = ''
    }
};
Element.Properties.tag = {
    get: function () {
        return this.tagName.toLowerCase()
    }
};
Element.Properties.href = {
    get: function () {
        return (!this.href) ? null : this.href.replace(new RegExp('^' + document.location.protocol + '\/\/' + document.location.host), '')
    }
};
Element.Properties.html = {
    set: function () {
        return this.innerHTML = Array.flatten(arguments).join('')
    }
};
Native.implement([Element, Window, Document], {
    addListener: function (type, fn) {
        if (this.addEventListener) this.addEventListener(type, fn, false);
        else this.attachEvent('on' + type, fn);
        return this
    },
    removeListener: function (type, fn) {
        if (this.removeEventListener) this.removeEventListener(type, fn, false);
        else this.detachEvent('on' + type, fn);
        return this
    },
    retrieve: function (property, dflt) {
        var storage = Element.Storage.get(this.uid);
        var prop = storage[property];
        if ($defined(dflt) && !$defined(prop)) prop = storage[property] = dflt;
        return $pick(prop)
    },
    store: function (property, value) {
        var storage = Element.Storage.get(this.uid);
        storage[property] = value;
        return this
    },
    eliminate: function (property) {
        var storage = Element.Storage.get(this.uid);
        delete storage[property];
        return this
    }
});
Element.Attributes = new Hash({
    Props: {
        'html': 'innerHTML',
        'class': 'className',
        'for': 'htmlFor',
        'text': (Browser.Engine.trident) ? 'innerText' : 'textContent'
    },
    Bools: ['compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', 'disabled', 'readonly', 'multiple', 'selected', 'noresize', 'defer'],
    Camels: ['value', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', 'frameBorder', 'maxLength', 'readOnly', 'rowSpan', 'tabIndex', 'useMap']
});
Browser.freeMem = function (item) {
    if (!item) return;
    if (Browser.Engine.trident && (/object/i).test(item.tagName)) {
        for (var p in item) {
            if (typeof item[p] == 'function') item[p] = $empty
        }
        Element.dispose(item)
    }
    if (item.uid && item.removeEvents) item.removeEvents()
};
(function (EA) {
    var EAB = EA.Bools,
        EAC = EA.Camels;
    EA.Bools = EAB = EAB.associate(EAB);
    Hash.extend(Hash.combine(EA.Props, EAB), EAC.associate(EAC.map(function (v) {
        return v.toLowerCase()
    })));
    EA.erase('Camels')
})(Element.Attributes);
window.addListener('unload', function () {
    window.removeListener('unload', arguments.callee);
    document.purge();
    if (Browser.Engine.trident) CollectGarbage()
});
