(function () {

    var run,
        destroying$;

    var run = function (fn) {
        if (fn && typeof fn === 'function') {
            return fn();
        }
    };

    Ember.View.reopen({

        isAnimatingIn : false,
        isAnimatingOut : false,
        hasAnimatedIn : false,
        hasAnimatedOut : false,

        _animateInCallbacks : null,
        _animateOutCallbacks : null,

        _afterRender : function () {

            var self = this;

            this.$el = this.$();

            this._transitionTo = this._transitionTo || this.transitionTo;

            if (!self.isDestroyed) {

                self.willAnimateIn();
                self.isAnimatingIn = true;
                self.hasAnimatedIn = false;

                Ember.run.next(function () {

                    if (!self.isDestroyed) {

                        self.animateIn(function () {

                            var i;

                            self.isAnimatingIn = false;
                            self.hasAnimatedIn = true;
                            self.didAnimateIn();

                            if (self._animateInCallbacks && self._animateInCallbacks.length) {
                                for (i = 0; i < self._animateInCallbacks.length; i ++) {
                                    run(self._animateInCallbacks[i]);
                                }
                            }

                            self._animateInCallbacks = null;

                        });
                    }
                });
            }
        },

        willInsertElement : function () {
            Ember.run.scheduleOnce('afterRender', this, this._afterRender);
            return this._super();
        },

        willAnimateIn : Ember.K,
        willAnimateOut : Ember.K,
        didAnimateIn : Ember.K,
        didAnimateOut : Ember.K,

        animateIn : run,
        animateOut : run,

        onAnimateIn : function (callback) {

            this._animateInCallbacks = this._animateInCallbacks || [];

            if (typeof callback === 'function') {
                this._animateInCallbacks.push(callback);
            }
        },

        onAnimateOut : function (callback) {

            this._animateOutCallbacks = this._animateOutCallbacks || [];

            if (typeof callback === 'function') {
                this._animateOutCallbacks.push(callback);
            }
        },

        destroy : function (done) {

            var _super = this._super;

            this.onAnimateOut(done);

            if (this.isAnimatingOut) {
                return;
            }

            if (!this.$el || this.isDestroyed) {

                if (this._animateOutCallbacks && this._animateOutCallbacks.length) {
                    for (i = 0; i < this._animateOutCallbacks.length; i ++) {
                        run(this._animateOutCallbacks[i]);
                    }
                }

                this._animateOutCallbacks = null;

                return _super.call(this);
            }

            if (!this.$()) {
                this.$ = function () {
                    return this.$el;
                }
            }

            this.willAnimateOut();
            this.isAnimatingOut = true;

            this.animateOut(function () {

                this.isAnimatingOut = false;
                this.hasAnimatedOut = true;

                this.didAnimateOut();

                if (this._animateOutCallbacks && this._animateOutCallbacks.length) {
                    for (i = 0; i < this._animateOutCallbacks.length; i ++) {
                        run(this._animateOutCallbacks[i]);
                    }
                }

                this.isDestroying = false;

                _super.call(this);

                // remove from parent if found. Don't call removeFromParent,
                // as removeFromParent will try to remove the element from
                // the DOM again.
                if (this._parentView) {
                    this._parentView.removeChild(this);
                }

                this.isDestroying = true;

                this._transitionTo('destroying', false);

                delete this.$;
                delete this.$el;

                return this;

            }.bind(this));

            return this;
        }
    });

    Ember.ContainerView.reopen({

        currentView : null,
        activeView : null,
        newView : null,
        nextView : null,

        animationSequence : 'sync', // sync, async, reverse

        init : function () {

            var currentView;

            this._super();

            if (currentView = this.get('currentView')) {
                this.set('activeView', currentView);
            }
        },

        _currentViewWillChange : Ember.K,

        _currentViewDidChange : Ember.observer('currentView', function () {

            var self,
                newView,
                oldView,
                asyncCount;

            self = this;
            oldView = this.get('activeView');
            newView = this.get('currentView');

            this.set('newView', newView);

            function pushView (view) {

                if (view ) {
                    self.pushObject(view);
                }

                if (!self.get('isDestroyed')) {
                    self.set('activeView', view);
                }

            }

            function removeView (view) {

                if (view.isAnimatingOut) {
                    return;
                }

                if (!view.hasAnimatedIn) {
                    view.onAnimateIn(view.destroy.call(view));
                    return;
                }

                view.destroy();
            };

            if (oldView) {

                // reverse
                if (this.animationSequence === 'reverse') {

                    newView.onAnimateIn(function () {
                        removeView(oldView);
                    });

                    pushView(newView);
                }

                // async
                else if (this.animationSequence === 'async') {
                    removeView(oldView);
                    pushView(newView);
                }

                // sync
                else {

                    oldView.onAnimateOut(function () {
                        pushView(self.get('currentView'));
                    });

                    removeView(oldView);
                }
            }

            else {
                pushView(newView);
            }
        })

    });

})();