/**
 * This class is used as a mixin.
 *
 * This class is to be used to provide basic methods for binding/unbinding stores to other
 * classes.
 *
 * This class is not intended for direct use but rather internally by those classes that
 * manage a Store.
 * @private
 */
Ext.define('Ext.util.StoreHolder', {
    requires: [
        'Ext.data.StoreManager'
    ],
    mixinId: 'storeholder',
 
    /**
     * @property {Boolean} [autoDestroyBoundStore] This property allows the object
     * to destroy bound stores that have {@link Ext.data.AbstractStore#autoDestroy}
     * option set to `true`. 
     */
    autoDestroyBoundStore: false,
 
    /**
     * Binds a store to this instance.
     * @param {Ext.data.AbstractStore/String} [store] The store to bind or ID of the store.
     * When no store given (or when `null` or `undefined` passed), unbinds the existing store.
     * @param initial
     * @param propertyName
     */
    bindStore: function(store, initial, propertyName) {
        var me = this,
            oldStore;
 
        // Private params
        // @param {Boolean} [initial=false] True to not remove listeners from existing store.
        // @param {String} [propertyName="store"] The property in this object under which
        // to cache the passed Store.
        propertyName = propertyName || 'store';
        oldStore = initial ? null : me[propertyName];
 
        if (store !== oldStore) {
            if (oldStore) {
                // Perform implementation-specific unbinding operations *before*
                // possible Store destruction.
                if (!me.onUnbindStore.$emptyFn) {
                    me.onUnbindStore(oldStore, initial, propertyName);
                }
 
                // autoDestroy is only intended for when it is unbound from a component,
                // and the store could have been already destroyed upstream
                if (!oldStore.destroyed) {
                    if (me.autoDestroyBoundStore && propertyName === 'store' &&
                        oldStore.autoDestroy) {
                        oldStore.destroy();
                    }
                    else {
                        me.unbindStoreListeners(oldStore);
                    }
                }
            }
 
            if (store) {
                me[propertyName] = store = Ext.data.StoreManager.lookup(store);
                me.bindStoreListeners(store);
 
                if (!me.onBindStore.$emptyFn) {
                    me.onBindStore(store, oldStore, initial);
                }
            }
            else {
                me[propertyName] = null;
            }
 
            if (me.fireEvent) {
                me.fireEvent('storechange', me, store, oldStore);
            }
        }
 
        return me;
    },
 
    /**
     * Gets the current store instance.
     * @return {Ext.data.AbstractStore} The store, null if one does not exist.
     */
    getStore: function() {
        return this.store;
    },
 
    /**
     * Sets the store to the specified store.
     * @param store
     * @since 5.0.0
     */
    setStore: function(store) {
        this.bindStore(store);
    },
 
    /**
     * Unbinds listeners from this component to the store. By default it will remove
     * anything bound by the bindStoreListeners method, however it can be overridden
     * in a subclass to provide any more complicated handling.
     * @protected
     * @param {Ext.data.AbstractStore} store The store to unbind from
     */
    unbindStoreListeners: function(store) {
        // Can be overridden in the subclass for more complex removal
        var listeners = this.storeListeners;
 
        if (listeners) {
            store.un(listeners);
        }
    },
 
    /**
     * Binds listeners for this component to the store. By default it will add
     * anything bound by the getStoreListeners method, however it can be overridden
     * in a subclass to provide any more complicated handling.
     * @protected
     * @param {Ext.data.AbstractStore} store The store to bind to
     */
    bindStoreListeners: function(store) {
        // Can be overridden in the subclass for more complex binding
        var listeners = this.getStoreListeners(store);
 
        if (listeners) {
            listeners = Ext.apply({}, listeners);
 
            if (!listeners.scope) {
                listeners.scope = this;
            }
 
            this.storeListeners = listeners;
            store.on(listeners);
        }
    },
 
    /**
     * @method
     * Gets the listeners to bind to a new store.
     * @protected
     * @param {Ext.data.Store} store The Store which is being bound to for which a listeners object
     * should be returned.
     * @return {Object} The listeners to be bound to the store in object literal form. The scope
     * may be omitted, it is assumed to be the current instance.
     */
    getStoreListeners: Ext.emptyFn,
 
    /**
     * @method
     * Template method, it is called when an existing store is unbound
     * from the current instance.
     * @protected
     * @param {Ext.data.AbstractStore} store The store being unbound
     * @param {Boolean} initial True if this store is being bound as initialization of the instance.
     */
    onUnbindStore: Ext.emptyFn,
 
    /**
     * @method
     * Template method, it is called when a new store is bound
     * to the current instance.
     * @protected
     * @param {Ext.data.AbstractStore} store The store being bound
     * @param {Boolean} initial True if this store is being bound as initialization of the instance.
     */
    onBindStore: Ext.emptyFn
});