/**
 * @private
 * Common methods for both classic & modern containers
 */
Ext.define('Ext.mixin.Container', {
    extend: 'Ext.Mixin',
 
    mixinConfig: {
        id: 'container'
    },
 
    /**
     * @property {Boolean} isContainer
     * `true` in this class to identify an object as an instantiated Container, or subclass thereof.
     */
    isContainer: true,
 
    /**
     * @cfg {Boolean} nameHolder
     * When `true` child components are tracked by their `name` property and can be
     * retrieved using the `lookupName` method.
     */
    nameHolder: false,
 
    /**
     * @cfg {Boolean} referenceHolder
     * If `true`, this container will be marked as being a point in the hierarchy where
     * references to items with a specified `reference` config will be held. The container
     * will automatically become a referenceHolder if a {@link #controller} is specified.
     *
     * See the introductory docs for {@link Ext.container.Container} for more information
     * about references & reference holders.
     */
    referenceHolder: false,
 
    /**
     * Returns an object holding the descendants of this container keyed by their
     * `name`. This object should not be held past the scope of the function calling this
     * method. It will not be valid if items are added or removed from this or any
     * sub-container.
     *
     * The intended usage is shown here (assume there are 3 components with names of
     * "foo", "bar" and "baz" at some level below this container):
     *
     *      onClick: function () {
     *          var items = this.getNamedItems();
     *
     *          // using "items" we can access any descendant by its "name"
     *
     *          items.foo.getValue() + items.bar.getValue() + items.baz.getValue();
     *      }
     *
     * If `this` component has a `name` assigned to it, it is **not** included in this
     * object. That name is understood to belong to the ancestor container configured
     * as the `nameHolder`.
     *
     * @return {Object} An object with each named child. This will be `null` if this
     * container has no descendants with a `name` specified.
     * @since 6.5.0
     */
    getNamedItems: function() {
        var CM = Ext.ComponentManager;
 
        if (CM.referencesDirty) {
            CM.fixReferences();
        }
 
        return this.nameRefs || null;
    },
 
    /**
     * Returns an object holding the descendants of this view keyed by their
     * `{@link Ext.Component#cfg-reference reference}`. This object should not be held
     * past the scope of the function calling this method. It will not be valid if items
     * are added or removed from this or any sub-container.
     *
     * The intended usage is shown here (assume there are 3 components with reference
     * values of "foo", "bar" and "baz" at some level below this container):
     *
     *      onClick: function () {
     *          var refs = this.getReferences();
     *
     *          // using "refs" we can access any descendant by its "reference"
     *
     *          refs.foo.getValue() + refs.bar.getValue() + refs.baz.getValue();
     *      }
     *
     * If `this` component has a `{@link Ext.Component#cfg-reference reference}` assigned
     * to it, that is **not** included in this object. That reference is understood to
     * belong to the ancestor container configured as the `referenceHolder`.
     *
     * @return {Object} An object with each child reference. This will be `null` if this
     * container has no descendants with a `{@link Ext.Component#cfg-reference reference}`
     * specified.
     * @since 5.0.0
     */
    getReferences: function() {
        var CM = Ext.ComponentManager;
 
        if (CM.referencesDirty) {
            CM.fixReferences();
        }
 
        return this.refs || null;
    },
 
    /**
     * Gets a reference to the component with the specified
     * {@link Ext.Component#cfg-reference reference} value.
     *
     * The method is a short-hand for the {@link #lookupReference} method.
     *
     * @param {String} ref The value of the `reference` to lookup.
     * @return {Ext.Component} The referenced component or `null` if it is not found.
     * @since 6.0.1
     */
    lookup: function(ref) {
        var refs = this.getReferences();
        
        return (refs && refs[ref]) || null;
    },
 
    /**
     * Gets a reference to the component with the specified `name` property.
     *
     * @param {String} name The name of the descendant to lookup.
     * @return {Ext.Component} The component or `null` if it is not found.
     * @since 6.5.0
     */
    lookupName: function(name) {
        var items = this.getNamedItems();
        
        return (items && items[name]) || null;
    },
 
    /**
     * Gets a reference to the component with the specified {@link #reference} value.
     *
     * The {@link #lookup} method is a short-hand version of this method.
     *
     * @param {String} ref The name of the reference to lookup.
     * @return {Ext.Component} The referenced component or `null` if it is not found.
     * @since 5.0
     */
    lookupReference: function(ref) {
        return this.lookup(ref);
    },
 
    privates: {
        /**
         * Sets up a component name reference.
         * @param {Ext.Component} component The component to reference.
         * @private
         */
        attachNameRef: function(component) {
            var me = this,
                key = component.name || component._name,
                entry, nameRefs;
 
            // Cleaning all this up later anyway
            if (key && !me.destroying && !me.destroyed) {
                nameRefs = me.nameRefs || (me.nameRefs = {});
                entry = nameRefs[key];
                
                if (!entry) {
                    entry = component.shareableName ? [component] : component;
                }
                else if (!entry.isInstance) {
                    // Else an existing entry is either a component (which will have false
                    // for shareableName) or an array (all elements of which have true
                    // for their shareableName).
                    entry.push(component);
                }
                //<debug>
                else {
                    Ext.raise('Duplicate name: "' + key + '" on ' +
                        me.id + ' between ' + entry.id + ' and ' + component.id);
                }
                //</debug>
 
                nameRefs[key] = entry;
            }
        },
 
        /**
         * Sets up a component reference.
         * @param {Ext.Component} component The component to reference.
         * @private
         */
        attachReference: function(component) {
            var me = this,
                key, refs;
 
            // Cleaning all this up later anyway
            if (!me.destroying && !me.destroyed) {
                refs = me.refs || (me.refs = {});
                key = component.referenceKey;
 
                //<debug>
                if (refs[key] && refs[key] !== component) {
                    Ext.log.warn('Duplicate reference: "' + key + '" on ' + me.id);
                }
                //</debug>
 
                refs[key] = component;
            }
        },
 
        containerOnAdded: function(component, instanced) {
            // We have been added to a container, we may have child references
            // or be a reference ourself. At this point we have no way of knowing if 
            // our references are correct, so trigger a fix.
            if (instanced) {
                Ext.ComponentManager.markReferencesDirty();
            }
        },
 
        containerOnRemoved: function(destroying) {
            // If we're destroying this will get cleaned up anyway
 
            if (!destroying) {
                // Clear any references here, they will be reset after the
                // next call to lookupReference after being marked dirty.
                // It's easier to wipe & re-establish them than attempt to
                // track what changed and prune the collection
                Ext.ComponentManager.markReferencesDirty();
            }
        },
 
        initContainerInheritedState: function(inheritedState, inheritedStateInner) {
            var me = this,
                controller = me.getController(),
                session = me.getSession(),
                // Don't instantiate it here, we just want to know whether we
                // were configured with a VM
                viewModel = me.getConfig('viewModel', true),
                reference = me.reference,
                referenceHolder = me.referenceHolder;
 
            if (me.nameHolder) {
                inheritedState.nameHolder = me;
            }
 
            if (controller) {
                inheritedState.referenceHolder = controller;
                referenceHolder = true;
            }
            else if (referenceHolder) {
                inheritedState.referenceHolder = me;
            }
 
            if (referenceHolder) {
                inheritedState.referencePath = '';
            }
            else if (reference && me.isParentReference) {
                inheritedState.referencePath = me.referenceKey + '.';
            }
 
            if (session) {
                inheritedState.session = session;
            }
 
            if (viewModel) {
                inheritedState.viewModelPath = '';
            }
            else if (reference && me.isParentReference) {
                inheritedState.viewModelPath = me.viewModelKey + '.';
            }
        },
        
        setupReference: function(reference) {
            var len;
 
            if (reference && reference.charAt(len = reference.length - 1) === '>') {
                this.isParentReference = true;
                reference = reference.substring(0, len);
            }
 
            //<debug>
            if (reference && !Ext.validIdRe.test(reference)) {
                Ext.Error.raise('Invalid reference "' + reference + '" for ' + this.getId() +
                    ' - not a valid identifier');
            }
            //</debug>
 
            return reference;
        }
    }
});