    /**
     * @author Jacky Nguyen <jacky@sencha.com>
     * @docauthor Jacky Nguyen <jacky@sencha.com>
     * @class Ext.Class
     *
     * Handles class creation throughout the framework. This is a low level factory that is used by Ext.ClassManager and generally
     * should not be used directly. If you choose to use Ext.Class you will lose out on the namespace, aliasing and depency loading
     * features made available by Ext.ClassManager. The only time you would use Ext.Class directly is to create an anonymous class.
     *
     * If you wish to create a class you should use {@link Ext#define Ext.define} which aliases
     * {@link Ext.ClassManager#create Ext.ClassManager.create} to enable namespacing and dynamic dependency resolution.
     *
     * Ext.Class is the factory and **not** the superclass of everything. For the base class that **all** Ext classes inherit
     * from, see {@link Ext.Base}.
     */
    (function() {

        var Class,
            Base = Ext.Base,
            baseStaticProperties = [],
            baseStaticProperty;

        for (baseStaticProperty in Base) {
            if (Base.hasOwnProperty(baseStaticProperty)) {
                baseStaticProperties.push(baseStaticProperty);
            }
        }

        /**
         * @method constructor
         * Creates new class.
         * @param {Object} classData An object represent the properties of this class
         * @param {Function} createdFn (Optional) The callback function to be executed when this class is fully created.
         * Note that the creation process can be asynchronous depending on the pre-processors used.
         * @return {Ext.Base} The newly created class
         */
        Ext.Class = Class = function(newClass, classData, onClassCreated) {
            if (typeof newClass != 'function') {
                onClassCreated = classData;
                classData = newClass;
                newClass = function() {
                    return this.constructor.apply(this, arguments);
                };
            }

            if (!classData) {
                classData = {};
            }

            var preprocessorStack = classData.preprocessors || Class.getDefaultPreprocessors(),
                registeredPreprocessors = Class.getPreprocessors(),
                index = 0,
                preprocessors = [],
                preprocessor, staticPropertyName, process, i, j, ln;

            for (i = 0, ln = baseStaticProperties.length; i < ln; i++) {
                staticPropertyName = baseStaticProperties[i];
                newClass[staticPropertyName] = Base[staticPropertyName];
            }

            delete classData.preprocessors;

            for (j = 0, ln = preprocessorStack.length; j < ln; j++) {
                preprocessor = preprocessorStack[j];

                if (typeof preprocessor == 'string') {
                    preprocessor = registeredPreprocessors[preprocessor];

                    if (!preprocessor.always) {
                        if (classData.hasOwnProperty(preprocessor.name)) {
                            preprocessors.push(preprocessor.fn);
                        }
                    }
                    else {
                        preprocessors.push(preprocessor.fn);
                    }
                }
                else {
                    preprocessors.push(preprocessor);
                }
            }

            classData.onClassCreated = onClassCreated || Ext.emptyFn;

            classData.onBeforeClassCreated = function(cls, data) {
                onClassCreated = data.onClassCreated;

                delete data.onBeforeClassCreated;
                delete data.onClassCreated;

                cls.implement(data);

                onClassCreated.call(cls, cls);
            };

            process = function(cls, data) {
                preprocessor = preprocessors[index++];

                if (!preprocessor) {
                    data.onBeforeClassCreated.apply(this, arguments);
                    return;
                }

                if (preprocessor.call(this, cls, data, process) !== false) {
                    process.apply(this, arguments);
                }
            };

            process.call(Class, newClass, classData);

            return newClass;
        };

        Ext.apply(Class, {

            /** @private */
            preprocessors: {},

            /**
             * Register a new pre-processor to be used during the class creation process
             *
             * @member Ext.Class
             * @param {String} name The pre-processor's name
             * @param {Function} fn The callback function to be executed. Typical format:
             *
             *     function(cls, data, fn) {
             *         // Your code here
             *
             *         // Execute this when the processing is finished.
             *         // Asynchronous processing is perfectly ok
             *         if (fn) {
             *             fn.call(this, cls, data);
             *         }
             *     });
             *
             * @param {Function} fn.cls The created class
             * @param {Object} fn.data The set of properties passed in {@link Ext.Class} constructor
             * @param {Function} fn.fn The callback function that **must** to be executed when this pre-processor finishes,
             * regardless of whether the processing is synchronous or aynchronous
             *
             * @return {Ext.Class} this
             * @static
             */
            registerPreprocessor: function(name, fn, always) {
                this.preprocessors[name] = {
                    name: name,
                    always: always ||  false,
                    fn: fn
                };

                return this;
            },

            /**
             * Retrieve a pre-processor callback function by its name, which has been registered before
             *
             * @param {String} name
             * @return {Function} preprocessor
             * @static
             */
            getPreprocessor: function(name) {
                return this.preprocessors[name];
            },

            getPreprocessors: function() {
                return this.preprocessors;
            },

            /**
             * Retrieve the array stack of default pre-processors
             *
             * @return {Function[]} defaultPreprocessors
             * @static
             */
            getDefaultPreprocessors: function() {
                return this.defaultPreprocessors || [];
            },

            /**
             * Set the default array stack of default pre-processors
             *
             * @param {Function/Function[]} preprocessors
             * @return {Ext.Class} this
             * @static
             */
            setDefaultPreprocessors: function(preprocessors) {
                this.defaultPreprocessors = Ext.Array.from(preprocessors);

                return this;
            },

            /**
             * Inserts this pre-processor at a specific position in the stack, optionally relative to
             * any existing pre-processor. For example:
             *
             *     Ext.Class.registerPreprocessor('debug', function(cls, data, fn) {
             *         // Your code here
             *
             *         if (fn) {
             *             fn.call(this, cls, data);
             *         }
             *     }).setDefaultPreprocessorPosition('debug', 'last');
             *
             * @param {String} name The pre-processor name. Note that it needs to be registered with
             * {@link #registerPreprocessor registerPreprocessor} before this
             * @param {String} offset The insertion position. Four possible values are:
             * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
             * @param {String} relativeName
             * @return {Ext.Class} this
             * @static
             */
            setDefaultPreprocessorPosition: function(name, offset, relativeName) {
                var defaultPreprocessors = this.defaultPreprocessors,
                    index;

                if (typeof offset == 'string') {
                    if (offset === 'first') {
                        defaultPreprocessors.unshift(name);

                        return this;
                    }
                    else if (offset === 'last') {
                        defaultPreprocessors.push(name);

                        return this;
                    }

                    offset = (offset === 'after') ? 1 : -1;
                }

                index = Ext.Array.indexOf(defaultPreprocessors, relativeName);

                if (index !== -1) {
                    Ext.Array.splice(defaultPreprocessors, Math.max(0, index + offset), 0, name);
                }

                return this;
            }
        });

        /**
         * @cfg {String} extend
         * The parent class that this class extends. For example:
         *
         *     Ext.define('Person', {
         *         say: function(text) { alert(text); }
         *     });
         *
         *     Ext.define('Developer', {
         *         extend: 'Person',
         *         say: function(text) { this.callParent(["print "+text]); }
         *     });
         */
        Class.registerPreprocessor('extend', function(cls, data) {
            var extend = data.extend,
                base = Ext.Base,
                basePrototype = base.prototype,
                prototype = function() {},
                parent, i, k, ln, staticName, parentStatics,
                parentPrototype, clsPrototype;

            if (extend && extend !== Object) {
                parent = extend;
            }
            else {
                parent = base;
            }

            parentPrototype = parent.prototype;

            prototype.prototype = parentPrototype;
            clsPrototype = cls.prototype = new prototype();

            if (!('$class' in parent)) {
                for (i in basePrototype) {
                    if (!parentPrototype[i]) {
                        parentPrototype[i] = basePrototype[i];
                    }
                }
            }

            clsPrototype.self = cls;

            cls.superclass = clsPrototype.superclass = parentPrototype;

            delete data.extend;

            //<feature classSystem.inheritableStatics>
            // Statics inheritance
            parentStatics = parentPrototype.$inheritableStatics;

            if (parentStatics) {
                for (k = 0, ln = parentStatics.length; k < ln; k++) {
                    staticName = parentStatics[k];

                    if (!cls.hasOwnProperty(staticName)) {
                        cls[staticName] = parent[staticName];
                    }
                }
            }
            //</feature>

            //<feature classSystem.config>
            // Merge the parent class' config object without referencing it
            if (parentPrototype.config) {
                clsPrototype.config = Ext.Object.merge({}, parentPrototype.config);
            }
            else {
                clsPrototype.config = {};
            }
            //</feature>

            //<feature classSystem.onClassExtended>
            if (clsPrototype.$onExtended) {
                clsPrototype.$onExtended.call(cls, cls, data);
            }

            if (data.onClassExtended) {
                clsPrototype.$onExtended = data.onClassExtended;
                delete data.onClassExtended;
            }
            //</feature>

        }, true);

        //<feature classSystem.statics>
        /**
         * @cfg {Object} statics
         * List of static methods for this class. For example:
         *
         *     Ext.define('Computer', {
         *          statics: {
         *              factory: function(brand) {
         *                  // 'this' in static methods refer to the class itself
         *                  return new this(brand);
         *              }
         *          },
         *
         *          constructor: function() { ... }
         *     });
         *
         *     var dellComputer = Computer.factory('Dell');
         */
        Class.registerPreprocessor('statics', function(cls, data) {
            cls.addStatics(data.statics);

            delete data.statics;
        });
        //</feature>

        //<feature classSystem.inheritableStatics>
        /**
         * @cfg {Object} inheritableStatics
         * List of inheritable static methods for this class.
         * Otherwise just like {@link #statics} but subclasses inherit these methods.
         */
        Class.registerPreprocessor('inheritableStatics', function(cls, data) {
            cls.addInheritableStatics(data.inheritableStatics);

            delete data.inheritableStatics;
        });
        //</feature>

        //<feature classSystem.config>
        /**
         * @cfg {Object} config
         * List of configuration options with their default values, for which automatically
         * accessor methods are generated.  For example:
         *
         *     Ext.define('SmartPhone', {
         *          config: {
         *              hasTouchScreen: false,
         *              operatingSystem: 'Other',
         *              price: 500
         *          },
         *          constructor: function(cfg) {
         *              this.initConfig(cfg);
         *          }
         *     });
         *
         *     var iPhone = new SmartPhone({
         *          hasTouchScreen: true,
         *          operatingSystem: 'iOS'
         *     });
         *
         *     iPhone.getPrice(); // 500;
         *     iPhone.getOperatingSystem(); // 'iOS'
         *     iPhone.getHasTouchScreen(); // true;
         *     iPhone.hasTouchScreen(); // true
         */
        Class.registerPreprocessor('config', function(cls, data) {
            var prototype = cls.prototype;

            Ext.Object.each(data.config, function(name) {
                var cName = name.charAt(0).toUpperCase() + name.substr(1),
                    pName = name,
                    apply = 'apply' + cName,
                    setter = 'set' + cName,
                    getter = 'get' + cName;

                if (!(apply in prototype) && !data.hasOwnProperty(apply)) {
                    data[apply] = function(val) {
                        return val;
                    };
                }

                if (!(setter in prototype) && !data.hasOwnProperty(setter)) {
                    data[setter] = function(val) {
                        var ret = this[apply].call(this, val, this[pName]);

                        if (typeof ret != 'undefined') {
                            this[pName] = ret;
                        }

                        return this;
                    };
                }

                if (!(getter in prototype) && !data.hasOwnProperty(getter)) {
                    data[getter] = function() {
                        return this[pName];
                    };
                }
            });

            Ext.Object.merge(prototype.config, data.config);
            delete data.config;
        });
        //</feature>

        //<feature classSystem.mixins>
        /**
         * @cfg {Object} mixins
         * List of classes to mix into this class. For example:
         *
         *     Ext.define('CanSing', {
         *          sing: function() {
         *              alert("I'm on the highway to hell...")
         *          }
         *     });
         *
         *     Ext.define('Musician', {
         *          extend: 'Person',
         *
         *          mixins: {
         *              canSing: 'CanSing'
         *          }
         *     })
         */
        Class.registerPreprocessor('mixins', function(cls, data) {
            var mixins = data.mixins,
                name, mixin, i, ln;

            delete data.mixins;

            Ext.Function.interceptBefore(data, 'onClassCreated', function(cls) {
                if (mixins instanceof Array) {
                    for (i = 0,ln = mixins.length; i < ln; i++) {
                        mixin = mixins[i];
                        name = mixin.prototype.mixinId || mixin.$className;

                        cls.mixin(name, mixin);
                    }
                }
                else {
                    for (name in mixins) {
                        if (mixins.hasOwnProperty(name)) {
                            cls.mixin(name, mixins[name]);
                        }
                    }
                }
            });
        });

        //</feature>

        Class.setDefaultPreprocessors([
            'extend'
            //<feature classSystem.statics>
            ,'statics'
            //</feature>
            //<feature classSystem.inheritableStatics>
            ,'inheritableStatics'
            //</feature>
            //<feature classSystem.config>
            ,'config'
            //</feature>
            //<feature classSystem.mixins>
            ,'mixins'
            //</feature>
        ]);

        //<feature classSystem.backwardsCompatible>
        // Backwards compatible
        Ext.extend = function(subclass, superclass, members) {
            if (arguments.length === 2 && Ext.isObject(superclass)) {
                members = superclass;
                superclass = subclass;
                subclass = null;
            }

            var cls;

            if (!superclass) {
                Ext.Error.raise("Attempting to extend from a class which has not been loaded on the page.");
            }

            members.extend = superclass;
            members.preprocessors = [
                'extend'
                //<feature classSystem.statics>
                ,'statics'
                //</feature>
                //<feature classSystem.inheritableStatics>
                ,'inheritableStatics'
                //</feature>
                //<feature classSystem.mixins>
                ,'mixins'
                //</feature>
                //<feature classSystem.config>
                ,'config'
                //</feature>
            ];

            if (subclass) {
                cls = new Class(subclass, members);
            }
            else {
                cls = new Class(members);
            }

            cls.prototype.override = function(o) {
                for (var m in o) {
                    if (o.hasOwnProperty(m)) {
                        this[m] = o[m];
                    }
                }
            };

            return cls;
        };
        //</feature>

    })();