describe("Ext.Class", function() {
    var cls, emptyFn = function(){};
    beforeEach(function() {
        window.My = {
            awesome: {
                Class: function(){console.log(11)},
                Class1: function(){console.log(12)},
                Class2: function(){console.log(13)}
            },
            cool: {
                AnotherClass: function(){console.log(21)},
                AnotherClass1: function(){console.log(22)},
                AnotherClass2: function(){console.log(23)}
            }
        };
    });

    afterEach(function() {
        if (window.My) {
            window.My = undefined;
        }

        try {
            delete window.My;
        } catch (e) {}
    });

    // START PREPROCESSORS =================================================================== /
    describe("preprocessors", function() {

        beforeEach(function() {
            cls = function() {};
            cls.prototype.config = {};
            cls.ownMethod = function(name, fn) {
                this.prototype[name] = fn;
            };
        });

        describe("extend", function() {

            it("should extend from Base if no 'extend' property found", function() {
                var data = {};

                Ext.Class.preprocessors.extend.fn(cls, data, emptyFn, emptyFn);

                expect((new cls) instanceof Ext.Base).toBeTruthy();
            });

            it("should extend from given parent class", function() {
                var data = {
                    extend: My.awesome.Class
                };

                Ext.Class.preprocessors.extend.fn(cls, data, emptyFn, emptyFn);

                expect((new cls) instanceof My.awesome.Class).toBeTruthy();
            });

            it("should have superclass reference", function() {
                var data = {
                    extend: My.awesome.Class
                };

                var parentPrototype = My.awesome.Class.prototype;

                Ext.Class.preprocessors.extend.fn(cls, data, emptyFn, emptyFn);

                expect(cls.superclass).toEqual(parentPrototype);
                expect((new cls).superclass).toEqual(parentPrototype);
            });
        });

        describe("config", function() {

            it("should create getter if not exists", function() {
                var data = {
                    config: {
                        someName: 'someValue'
                    }
                };

                Ext.Class.preprocessors.config.fn(cls, data, emptyFn, emptyFn);

                expect(data.getSomeName).toBeDefined();
            });

            it("should NOT create getter if already exists", function() {
                var data = {
                    config: {
                        someName: 'someValue'
                    }
                };

                var called = false;
                cls.prototype.getSomeName = function() {
                    called = true;
                };

                Ext.Class.preprocessors.config.fn(cls, data, emptyFn, emptyFn);

                expect(data.getSomeName).not.toBeDefined();
            });

            it("should create setter if not exists", function() {
                var data = {
                    config: {
                        someName: 'someValue'
                    }
                };

                Ext.Class.preprocessors.config.fn(cls, data, emptyFn, emptyFn);

                expect(data.setSomeName).toBeDefined();
            });

            it("should NOT create setter if already exists", function() {
                var data = {
                    config: {
                        someName: 'someValue'
                    }
                };

                var called = false;

                cls.prototype.setSomeName = function() {
                    called = true;
                };

                Ext.Class.preprocessors.config.fn(cls, data, emptyFn, emptyFn);

                expect(data.setSomeName).not.toBeDefined();
            });

            it("should create apply if not exists", function() {
                var data = {
                    config: {
                        someName: 'someValue'
                    }
                };

                Ext.Class.preprocessors.config.fn(cls, data, emptyFn, emptyFn);

                expect(data.applySomeName).toBeDefined();
            });

            it("should NOT create apply if already exists", function() {
                var data = {
                    config: {
                        someName: 'someValue'
                    }
                };

                var called = false;
                cls.prototype.applySomeName = function() {
                    called = true;
                };

                Ext.Class.preprocessors.config.fn(cls, data, emptyFn, emptyFn);

                expect(data.applySomeName).not.toBeDefined();
            });
        });

        describe("statics", function() {
            it("should copy static properties to the class", function() {
                var data = {
                    statics: {
                        someName: 'someValue',
                        someMethod: Ext.emptyFn
                    }
                };

                Ext.Class.preprocessors.statics.fn(cls, data, emptyFn, emptyFn);

                var obj = new cls;

                expect(obj.statics).not.toBeDefined();
                expect(cls.someName).toBe('someValue');
                expect(cls.someMethod).toBe(Ext.emptyFn);
            });
        });

        describe("inheritableStatics", function() {

            it("should store names of inheritable static properties", function() {
                var data = {
                    inheritableStatics: {
                        someName: 'someValue',
                        someMethod: Ext.emptyFn
                    }
                };

                Ext.Class.preprocessors.inheritableStatics.fn(cls, data, emptyFn, emptyFn);

                var obj = new cls;

                expect(obj.inheritableStatics).not.toBeDefined();
                expect(cls.someName).toBe('someValue');
                expect(cls.prototype.$inheritableStatics).toEqual(['someName', 'someMethod']);
                expect(cls.someMethod).toBe(Ext.emptyFn);
            });

            it("should inherit inheritable statics", function() {
                var data = {
                    inheritableStatics: {
                        someName: 'someValue',
                        someMethod: Ext.emptyFn
                    }
                }, cls2 = function(){};

                Ext.Class.preprocessors.inheritableStatics.fn(cls, data, emptyFn, emptyFn);
                Ext.Class.preprocessors.extend.fn(cls2, { extend: cls }, emptyFn, emptyFn);

                expect(cls2.someName).toEqual('someValue');
                expect(cls2.someMethod).toBe(Ext.emptyFn);
            });

            it("should NOT inherit inheritable statics if the class already has it", function() {
                var data = {
                    inheritableStatics: {
                        someName: 'someValue',
                        someMethod: Ext.emptyFn
                    }
                }, cls2 = function(){};

                cls2.someName = 'someOtherValue';
                cls2.someMethod = function(){};

                Ext.Class.preprocessors.inheritableStatics.fn(cls, data, emptyFn, emptyFn);
                Ext.Class.preprocessors.extend.fn(cls2, { extend: cls }, emptyFn, emptyFn);

                expect(cls2.someName).toEqual('someOtherValue');
                expect(cls2.someMethod).not.toBe(Ext.emptyFn);
            });
        });
    });

    // END PREPROCESSORS =================================================================== /

    describe("Instantiation", function() {
        var subClass, parentClass, mixinClass1, mixinClass2;

        beforeEach(function() {
            mixinClass1 = new Ext.Class({
                config: {
                    mixinConfig: 'mixinConfig'
                },

                constructor: function(config) {
                    this.initConfig(config);

                    this.mixinConstructor1Called = true;
                },

                mixinProperty1: 'mixinProperty1',

                mixinMethod1: function() {
                    this.mixinMethodCalled = true;
                }
            });

            mixinClass2 = new Ext.Class({
                constructor: function(config) {
                    this.initConfig(config);

                    this.mixinConstructor2Called = true;
                },

                mixinProperty2: 'mixinProperty2',

                mixinMethod2: function() {
                    this.mixinMethodCalled = true;
                }
            });

            parentClass = new Ext.Class({
                mixins: {
                    mixin1: mixinClass1
                },
                config: {
                    name: 'parentClass',
                    isCool: false,
                    members: {
                        abe: 'Abraham Elias',
                        ed: 'Ed Spencer'
                    },
                    hobbies: ['football', 'bowling']
                },
                constructor: function(config) {
                    this.initConfig(config);

                    this.parentConstructorCalled = true;

                    this.mixins.mixin1.constructor.apply(this, arguments);
                },

                parentProperty: 'parentProperty',

                parentMethod: function() {
                    this.parentMethodCalled = true;
                }
            });

            subClass = new Ext.Class({
                extend: parentClass,
                mixins: {
                    mixin1: mixinClass1,
                    mixin2: mixinClass2
                },
                config: {
                    name: 'subClass',
                    isCool: true,
                    members: {
                        jacky: 'Jacky Nguyen',
                        tommy: 'Tommy Maintz'
                    },
                    hobbies: ['sleeping', 'eating', 'movies'],
                    isSpecial: true
                },
                constructor: function(config) {
                    this.initConfig(config);

                    this.subConstrutorCalled = true;

                    subClass.superclass.constructor.apply(this, arguments);

                    this.mixins.mixin2.constructor.apply(this, arguments);
                },
                myOwnMethod: function() {
                    this.myOwnMethodCalled = true;
                }
            });
        });

        describe("addStatics", function() {
            it("single with name - value arguments", function() {
                var called = false;

                subClass.addStatics({
                    staticMethod: function(){
                        called = true;
                    }
                });

                expect(subClass.staticMethod).toBeDefined();
                subClass.staticMethod();

                expect(called).toBeTruthy();
            });

            it("multiple with object map argument", function() {
                subClass.addStatics({
                    staticProperty: 'something',
                    staticMethod: function(){}
                });

                expect(subClass.staticProperty).toEqual('something');
                expect(subClass.staticMethod).toBeDefined();
            });
        });


        describe("override", function() {
            it("should override", function() {
                subClass.override({
                    myOwnMethod: function(){
                        this.isOverridden = true;

                        this.callOverridden(arguments);
                    }
                });

                var obj = new subClass;
                obj.myOwnMethod();

                expect(obj.isOverridden).toBe(true);
                expect(obj.myOwnMethodCalled).toBe(true);
            });
        });

        describe("mixin", function() {
            it("should have all properties of mixins", function() {
                var obj = new subClass;
                expect(obj.mixinProperty1).toEqual('mixinProperty1');
                expect(obj.mixinProperty2).toEqual('mixinProperty2');
                expect(obj.mixinMethod1).toBeDefined();
                expect(obj.mixinMethod2).toBeDefined();
                expect(obj.config.mixinConfig).toEqual('mixinConfig');
            });
        });

        describe("config", function() {
            it("should merge properly", function() {
                var obj = new subClass;
                expect(obj.config).toEqual({
                    mixinConfig: 'mixinConfig',
                    name: 'subClass',
                    isCool: true,
                    members: {
                        abe: 'Abraham Elias',
                        ed: 'Ed Spencer',
                        jacky: 'Jacky Nguyen',
                        tommy: 'Tommy Maintz'
                    },
                    hobbies: ['sleeping', 'eating', 'movies'],
                    isSpecial: true
                });
            });

            it("should apply default config", function() {
                var obj = new subClass;
                expect(obj.getName()).toEqual('subClass');
                expect(obj.getIsCool()).toEqual(true);
                expect(obj.getHobbies()).toEqual(['sleeping', 'eating', 'movies']);
            });

            it("should apply with supplied config", function() {
                var obj = new subClass({
                    name: 'newName',
                    isCool: false,
                    members: {
                        aaron: 'Aaron Conran'
                    }
                });

                expect(obj.getName()).toEqual('newName');
                expect(obj.getIsCool()).toEqual(false);
                expect(obj.getMembers().aaron).toEqual('Aaron Conran');
            });

            it("should not share the same config", function() {
                var obj1 = new subClass({
                    name: 'newName',
                    isCool: false,
                    members: {
                        aaron: 'Aaron Conran'
                    }
                });

                var obj2 = new subClass();

                expect(obj2.getName()).not.toEqual('newName');
            });
        });

        describe("overriden methods", function() {
            it("should call self constructor", function() {
                var obj = new subClass;
                expect(obj.subConstrutorCalled).toBeTruthy();
            });

            it("should call parent constructor", function() {
                var obj = new subClass;
                expect(obj.parentConstructorCalled).toBeTruthy();
            });

            it("should call mixins constructors", function() {
                var obj = new subClass;
                expect(obj.mixinConstructor1Called).toBeTruthy();
                expect(obj.mixinConstructor2Called).toBeTruthy();
            });
        });

    });

});
