// @define Ext.Factory
/**
 * @class Ext.Factory
 * Manages factories for families of classes (classes with a common `alias` prefix). The
 * factory for a class family is a function stored as a `static` on `Ext.Factory`. These
 * are created either by directly calling `Ext.Factory.define` or by using the
 * `Ext.mixin.Factoryable` interface.
 *
 * To illustrate, consider the layout system's use of aliases. The `hbox` layout maps to
 * the `"layout.hbox"` alias that one typically provides via the `layout` config on a
 * Container.
 *
 * Under the covers this maps to a call like this:
 *
 *      Ext.Factory.layout('hbox');
 *
 * Or possibly:
 *
 *      Ext.Factory.layout({
 *          type: 'hbox'
 *      });
 *
 * The value of the `layout` config is passed to the `Ext.Factory.layout` function. The
 * exact signature of a factory method matches `{@link Ext.Factory#method!create}`.
 *
 * To define this factory directly, one could call `Ext.Factory.define` like so:
 *
 *      Ext.Factory.define('layout', 'auto');  // "layout.auto" is the default type
 *
 * @since 5.0.0
 */
Ext.Factory = function(type) {
    var me = this;
 
    me.aliasPrefix = type + '.';
    me.cache = {};
    me.name = type.replace(me.fixNameRe, me.fixNameFn);
    me.type = type;
 
    /**
     * @cfg {String} [creator]
     * The name of the method used to prepare config objects for creation. This defaults
     * to `'create'` plus the capitalized name (e.g., `'createLayout'` for the 'laoyut'
     * alias family).
     */
    me.creator = 'create' + Ext.String.capitalize(me.name);
};
 
Ext.Factory.prototype = {
    /**
     * @cfg {String} [aliasPrefix]
     * The prefix to apply to `type` values to form a complete alias. This defaults to the
     * proper value in most all cases and should not need to be specified.
     *
     * @since 5.0.0
     */
 
    /**
     * @cfg {String} [defaultProperty="type"]
     * The config property to set when the factory is given a config that is a string.
     *
     * @since 5.0.0
     */
    defaultProperty: 'type',
 
    /**
     * @cfg {String} [defaultType=null]
     * An optional type to use if none is given to the factory at invocation. This is a
     * suffix added to the `aliasPrefix`. For example, if `aliasPrefix="layout."` and
     * `defaultType="hbox"` the default alias is `"layout.hbox"`. This is an alternative
     * to `xclass` so only one should be provided.
     *
     * @since 5.0.0
     */
 
    /**
     * @cfg {String} [instanceProp="isInstance"]
     * The property that identifies an object as instance vs a config.
     *
     * @since 5.0.0
     */
    instanceProp: 'isInstance',
 
    /**
     * @cfg {String} [xclass=null]
     * The full classname of the type of instance to create when none is provided to the
     * factory. This is an alternative to `defaultType` so only one should be specified.
     *
     * @since 5.0.0
     */
 
    /**
     * @property {Ext.Class} [defaultClass=null]
     * The Class reference of the type of instance to create when none is provided to the
     * factory. This property is set from `xclass` when the factory instance is created.
     * @private
     * @readonly
     *
     * @since 5.0.0
     */
 
    /**
     * @cfg {String} [typeProperty="type"]
     * The property from which to read the type alias suffix.
     * @since 6.5.0
     */
    typeProperty: 'type',
 
    /**
     * Creates an instance of this class family given configuration options.
     *
     * @param {Object/String} [config] The configuration or instance (if an Object) or
     * just the type (if a String) describing the instance to create.
     * @param {String} [config.xclass] The full class name of the class to create.
     * @param {String} [config.type] The type string to add to the alias prefix for this
     * factory.
     * @param {String/Object} [defaultType] The type to create if no type is contained in the
     * `config`, or an object containing a default set of configs.
     * @return {Object} The newly created instance.
     *
     * @since 5.0.0
     */
    create: function(config, defaultType) {
        var me = this,
            Manager = Ext.ClassManager,
            cache = me.cache,
            typeProperty = me.typeProperty,
            alias, className, klass, suffix;
 
        if (config) {
            if (config[me.instanceProp]) {
                return config;
            }
 
            if (typeof config === 'string') {
                suffix = config;
                config = {};
                config[me.defaultProperty] = suffix;
            }
 
            className = config.xclass;
            suffix = config[typeProperty];
        }
 
        if (defaultType && defaultType.constructor === Object) {
            config = Ext.apply({}, config, defaultType);
            defaultType = defaultType[typeProperty];
        }
 
        if (className) {
            if (!(klass = Manager.get(className))) {
                return Manager.instantiate(className, config);
            }
        }
        else {
            if (!(suffix = suffix || defaultType || me.defaultType)) {
                klass = me.defaultClass;
            }
 
            //<debug>
            if (!suffix && !klass) {
                Ext.raise('No type specified for ' + me.type + '.create');
            }
            //</debug>
 
            if (!klass && !(klass = cache[suffix])) {
                alias = me.aliasPrefix + suffix;
                className = Manager.getNameByAlias(alias);
 
                // this is needed to support demand loading of the class
                if (!(klass = className && Manager.get(className))) {
                    return Manager.instantiateByAlias(alias, config);
                }
 
                cache[suffix] = klass;
            }
        }
 
        return klass.isInstance ? klass : new klass(config);
    },
 
    fixNameRe: /\.[a-z]/ig,
    fixNameFn: function(match) {
        return match.substring(1).toUpperCase();
    },
 
    clearCache: function() {
        this.cache = {};
        this.instanceCache = {};
    },
 
    /**
     * Sets a hook on the creation process. If the hook `fn` returns `undefined` then
     * the original `create` method is called.
     *
     * @param {Function} fn The hook function to call when `create` is invoked.
     * @param {Function} fn.original The original `create` method.
     * @param {String/Object} fn.config See {@link #method!create create}.
     * @param {String/Object} fn.defaultType See {@link #method!create create}.
     * @private
     * @since 6.5.0
     */
    hook: function(fn) {
        var me = this,
            original = me.create;
 
        me.create = function(config, defaultType) {
            var ret = fn.call(me, original, config, defaultType);
 
            if (ret === undefined) {
                ret = original.call(me, config, defaultType);
            }
 
            return ret;
        };
    },
 
    /**
     * This method accepts a `config` object and an existing `instance` if one exists
     * (can be `null`).
     *
     * The details are best explained by example:
     *
     *      config: {
     *          header: {
     *              xtype: 'itemheader'
     *          }
     *      },
     *
     *      applyHeader: function (header, oldHeader) {
     *          return Ext.Factory.widget.update(oldHeader, header,
     *              this, 'createHeader');
     *      },
     *
     *      createHeader: function (header) {
     *          return Ext.apply({
     *              xtype: 'itemheader',
     *              ownerCmp: this
     *          }, header);
     *      }
     *
     * Normally the `applyHeader` method would have to coordinate potential reuse of
     * the `oldHeader` and perhaps call `setConfig` on it with the new `header` config
     * options. If there was no `oldHeader`, of course, a new instance must be created
     * instead. These details are handled by this method. If the `oldHeader` is not
     * reused, it will be {@link Ext.Base#method!destroy destroyed}.
     *
     * For derived class flexibility, the pattern of calling out to a "creator" method
     * that only returns the config object has become widely used in many components.
     * This pattern is also covered in this method. The goal is to allow the derived
     * class to `callParent` and yet not end up with an instantiated component (since
     * the type may not yet be known).
     *
     * This mechanism should be used in favor of `Ext.factory()`.
     *
     * @param {Ext.Base} instance 
     * @param {Object/String} config The configuration (see {@link #method!create}).
     * @param {Object} [creator] If passed, this object must provide the `creator`
     * method or the `creatorMethod` parameter.
     * @param {String} [creatorMethod] The name of a creation wrapper method on the
     * given `creator` instance that "upgrades" the raw `config` object into a final
     * form for creation.
     * @param {String} [defaultsConfig] The name of a config property (on the provided
     * `creator` instance) that contains defaults to be used to create instances. These
     * defaults are present in the config object passed to the `creatorMethod`.
     * @return {Object} The reconfigured `instance` or a newly created one.
     * @since 6.5.0
     */
    update: function(instance, config, creator, creatorMethod, defaultsConfig) {
        var me = this,
            aliases, defaults, reuse, type;
 
        // If config is falsy or a valid instance, destroy the current instance
        // (if it exists) and replace with the new one
        if (!config || config.isInstance) {
            //<debug>
            if (config && !config[me.instanceProp]) {
                Ext.raise('Config instance failed ' + me.instanceProp + ' requirement');
            }
            //</debug>
 
            if (instance && instance !== config) {
                instance.destroy();
            }
 
            return config;
        }
 
        if (typeof config === 'string') {
            type = config;
            config = {};
            config[me.defaultProperty] = type;
        }
 
        // See if the existing instance can just be reconfigured:
        if (instance) {
            if (config === true) {
                return instance;
            }
 
            if (!(type = config.xclass)) {
                if (!(type = config.xtype)) {
                    type = config[me.typeProperty];
 
                    if (type) {
                        // instance must have the right alias...
                        type = me.aliasPrefix + type;
                        aliases = instance.self.prototype;
 
                        // The alias for the class is on the prototype (derived
                        // classes do not really own their inherited aliases since
                        // they won't be created when using them):
                        if (aliases.hasOwnProperty('alias')) {
                            aliases = aliases.alias;
 
                            if (aliases) {
                                reuse = aliases === type || aliases.indexOf(type) > -1;
                            }
                        }
                    }
                }
                else {
                    // config = { xtype: ... }
                    reuse = instance.isXType(type, /* shallow= */ true);
                }
            }
            else {
                // config = { xclass: ... } so we're good if they match
                reuse = instance.$className === type;
            }
 
            if (reuse) {
                instance.setConfig(config);
 
                return instance;
            }
 
            instance.destroy();
        }
 
        if (config === true) {
            config = {};
        }
 
        if (creator) {
            if (defaultsConfig) {
                defaults = Ext.Config.map[defaultsConfig];
                defaults = creator[defaults.names.get]();
 
                if (defaults) {
                    config = Ext.merge(Ext.clone(defaults), config);
                }
            }
 
            creatorMethod = creatorMethod || me.creator;
 
            if (creator[creatorMethod]) {
                config = creator[creatorMethod](config);
 
                //<debug>
                if (!config) {
                    Ext.raise('Missing return value from ' + creatorMethod + ' on class ' +
                        creator.$className);
                }
                //</debug>
            }
        }
 
        return me.create(config);
    }
};
 
/**
 * For example, the layout alias family could be defined like this:
 *
 *      Ext.Factory.define('layout', {
 *          defaultType: 'auto'
 *      });
 *
 * To define multiple families at once:
 *
 *      Ext.Factory.define({
 *          layout: {
 *              defaultType: 'auto'
 *          }
 *      });
 *
 * @param {String} type The alias family (e.g., "layout").
 * @param {Object/String} [config] An object specifying the config for the `Ext.Factory`
 * to be created. If a string is passed it is treated as the `defaultType`.
 * @return {Function} 
 * @static
 * @since 5.0.0
 */
Ext.Factory.define = function(type, config) {
    var Factory = Ext.Factory,
        cacheable = config && config.cacheable,
        defaultClass, factory, fn;
 
    if (type.constructor === Object) {
        Ext.Object.each(type, Factory.define, Factory);
    }
    else {
        factory = new Ext.Factory(type);
 
        if (config) {
            if (config.constructor === Object) {
                Ext.apply(factory, config);
 
                if (typeof(defaultClass = factory.xclass) === 'string') {
                    factory.defaultClass = Ext.ClassManager.get(defaultClass);
                }
            }
            else {
                factory.defaultType = config;
            }
        }
 
        /*
         *  layout = Ext.Factory.layout('hbox');
         */
        Factory[factory.name] = fn = function(config, defaultType) {
            // maintain indirection through "create" name on instance to allow
            // the hook() mechanism to replace it.
            return factory.create(config, defaultType);
        };
 
        if (cacheable) {
            factory.instanceCache = {};
 
            factory.hook(function(original, config, defaultType) {
                var cache = this.instanceCache,
                    v;
 
                if (typeof config === 'string' && !(= cache[config])) {
                    v = original.call(this, config, defaultType);
 
                    // Validator may have cacheable:false to force new instances each time,
                    // avoiding the cache
                    if (v.cacheable !== false) {
                        cache[config] = v;
                        //<debug>
                        // this should catch some improper modifications to the shared
                        // cached instance, during development but not in production.
                        Ext.Object.freeze(v);
                        //</debug>
                    }
                }
 
                return v;
            });
        }
 
        fn.instance = factory;
 
        /*
         * Typically called by an applier:
         *
         *      applyLayout: function (layout, oldLayout) {
         *          return Ext.Factory.layout.update(oldLayout, layout, this);
         *      },
         *
         *      createLayout: function (config) {
         *          return Ext.apply({
         *              //.. stuff
         *          }, config);
         *      }
         */
        fn.update = function(instance, config, creator, creatorMethod, defaultsConfig) {
            return factory.update(instance, config, creator, creatorMethod, defaultsConfig);
        };
    }
 
    return fn;
};
 
Ext.Factory.clearCaches = function() {
    var Factory = Ext.Factory,
        key, item;
 
    for (key in Factory) {
        item = Factory[key];
        item = item.instance;
 
        if (item) {
            item.clearCache();
        }
    }
};
 
Ext.Factory.on = function(name, fn) {
    Ext.Factory[name].instance.hook(fn);
};
 
/**
 * This mixin automates use of `Ext.Factory`. When mixed in to a class, the `alias` of the
 * class is retrieved and combined with an optional `factoryConfig` property on that class
 * to produce the configuration to pass to `Ext.Factory`.
 *
 * The factory method created by `Ext.Factory` is also added as a static method to the
 * target class.
 *
 * Given a class declared like so:
 *
 *      Ext.define('App.bar.Thing', {
 *          mixins: [
 *              'Ext.mixin.Factoryable'
 *          ],
 *
 *          alias: 'bar.thing',  // this is detected by Factoryable
 *
 *          factoryConfig: {
 *              defaultType: 'thing',  // this is the default deduced from the alias
 *              // other configs
 *          },
 *
 *          ...
 *      });
 *
 * The produced factory function can be used to create instances using the following
 * forms:
 *
 *      var obj;
 *
 *      obj = App.bar.Thing.create('thing'); // same as "new App.bar.Thing()"
 *
 *      obj = App.bar.Thing.create({
 *          type: 'thing'       // same as above
 *      });
 *
 *      obj = App.bar.Thing.create({
 *          xclass: 'App.bar.Thing'  // same as above
 *      });
 *
 *      var obj2 = App.bar.Thing.create(obj);
 *      // obj === obj2  (passing an instance returns the instance)
 *
 * Alternatively the produced factory is available as a static method of `Ext.Factory`.
 *
 * @since 5.0.0
 */
Ext.define('Ext.mixin.Factoryable', {
    mixinId: 'factoryable',
 
    onClassMixedIn: function(targetClass) {
        var proto = targetClass.prototype,
            factoryConfig = proto.factoryConfig,
            alias = proto.alias,
            config = {},
            dot, createFn;
 
        alias = alias && alias.length && alias[0];
 
        if (alias && (dot = alias.lastIndexOf('.')) > 0) {
            config.type = alias.substring(0, dot);
            config.defaultType = alias.substring(dot + 1);
        }
 
        if (factoryConfig) {
            delete proto.factoryConfig;
            Ext.apply(config, factoryConfig);
        }
 
        createFn = Ext.Factory.define(config.type, config);
 
        if (targetClass.create === Ext.Base.create) {
            // allow targetClass to override the create method
            targetClass.create = createFn;
        }
    }
 
    /**
     * @property {Object} [factoryConfig]
     * If this property is specified by the target class of this mixin its properties are
     * used to configure the created `Ext.Factory`.
     */
});