/**
 * A flyweight Ext.dom.Element that can be dynamically attached to a DOM node.
 * In general this class should not be instantiated directly.  Use {@link Ext#fly}
 * to create and retrieve Fly instances.
 */
Ext.define('Ext.dom.Fly', {
    extend: 'Ext.dom.Element',
    alternateClassName: 'Ext.dom.Element.Fly',

    // This adds the ability to wrap DOCUMENT_FRAGMENT_NODE
    // Document Fragments cannot have event listeners and therefore do not
    // need  the caching mechanism provided by Ext.get.
    // However the many Element APIs are useful such as Traversal, child appending/removing.
    validNodeTypes: {
        1: 1, // ELEMENT_NODE
        9: 1, // DOCUMENT_NODE
        11: 1 // DOCUMENT_FRAGMENT_NODE
    },

    /**
     * @property {Boolean} isFly
     * This is `true` to identify Element flyweights
     */
    isFly: true,

    constructor: function(dom) {
        this.dom = dom;

        // set an "el" property that references "this".  This allows
        // Ext.util.Positionable methods to operate on this.el.dom since it
        // gets mixed into both Element and Component
        this.el = this;
    },

    attach: function (dom) {
        var me = this;

        if (!dom) {
            return me.detach();
        }
        me.dom = dom;

        // If the element is not being managed by an Ext.Element instance,
        // we have to assume that the classList/classMap in the data object are out of sync with reality.
        if (!Ext.cache[dom.id]) {
            me.getData().isSynchronized = false;
        }

        return me;
    },

    detach: function() {
        this.dom = null;
    },

    addListener:
        //<debug>
        function() {
            Ext.raise(
                "Cannot use addListener() on Ext.dom.Fly instances. " +
                "Please use Ext.get() to retrieve an Ext.dom.Element instance instead."
            );
        } ||
        //</debug>
        null,

    removeListener: 
        //<debug>
        function() {
            Ext.raise(
                "Cannot use removeListener() on Ext.dom.Fly instances. " +
                "Please use Ext.get() to retrieve an Ext.dom.Element instance instead."
            );
        } ||
        //</debug>
        null
}, function(Fly) {
    var flyweights = {},
        detachedBodyEl;

    /**
     * @member Ext
     * @property {Object} cache
     * Stores `Fly` instances keyed by their assigned or generated name.
     * @readonly
     * @private
     * @since 5.0.0
     */
    Fly.cache = flyweights;

    /**
     * @member Ext
     * @method fly
     * Gets the globally shared flyweight Element, with the passed node as the active
     * element. Do not store a reference to this element - the dom node can be overwritten
     * by other code. {@link Ext#fly} is alias for {@link Ext.dom.Element#fly}.
     *
     * Use this to make one-time references to DOM elements which are not going to be
     * accessed again either by application code, or by Ext's classes. If accessing an
     * element which will be processed regularly, then {@link Ext#get Ext.get} will be
     * more appropriate to take advantage of the caching provided by the
     * {@link Ext.dom.Element} class.
     * 
     * If this method is called with and id or element that has already been cached by
     * a previous call to Ext.get() it will return the cached Element instead of the
     * flyweight instance.
     *
     * @param {String/HTMLElement} dom The DOM node or `id`.
     * @param {String} [named] Allows for creation of named reusable flyweights to prevent 
     * conflicts (e.g. internally Ext uses "_global").
     * @return {Ext.dom.Element} The shared Element object (or `null` if no matching
     * element was found).
     */
    Ext.fly = function(dom, named) {
        var fly = null,
            fn = Ext.fly,
            nodeType, data;

        // name the flyweight after the calling method name if possible.
        named = named || (fn.caller && fn.caller.$name) || '_global';

        dom = Ext.getDom(dom);

        if (dom) {
            nodeType = dom.nodeType;
            // check if we have a valid node type or if the el is a window object before
            // proceeding. This allows elements, document fragments, and document/window
            // objects (even those inside iframes) to be wrapped.
            // Note: a window object can be detected by comparing it's window property to
            // itself, but we must use == for the comparison because === will return false
            // in IE8 even though the 2 window objects are the same
            if (Fly.prototype.validNodeTypes[nodeType] || (!nodeType && (dom.window == dom))) {
                fly = Ext.cache[dom.id];

                // If there's no Element cached, or the element cached is for another DOM node, return a Fly
                if (!fly || fly.dom !== dom) {
                    fly = flyweights[named] || (flyweights[named] = new Fly());
                    fly.dom = dom;
                    data = fly.getData(true);
                    if (data) {
                        data.isSynchronized = false;
                    }
                }
            }
        }
        return fly;
    };

    /**
     * Returns an HTML div element into which {@link Ext.container.Container#method-remove removed} components
     * are placed so that their DOM elements are not garbage collected as detached Dom trees.
     * @return {Ext.dom.Element}
     * @method getDetachedBody
     * @member Ext
     * @private
     */
    Ext.getDetachedBody = function () {
        if (!detachedBodyEl) {
            Ext.detachedBodyEl = detachedBodyEl = new Fly(document.createElement('div'));
            detachedBodyEl.isDetachedBody = true;
        }

        return detachedBodyEl;
    };
});
