/**
 * NavigationView is basically a {@link Ext.Container} with a {@link Ext.layout.Card card} layout, so only one view
 * can be visible at a time. However, NavigationView also adds extra functionality on top of this to allow
 * you to `push` and `pop` views at any time. When you do this, your NavigationView will automatically animate
 * between your current active view, and the new view you want to `push`, or the previous view you want to `pop`.
 *
 * Using the NavigationView is very simple. Here is a basic example of it in action:
 *
 *     var view = Ext.create('Ext.NavigationView', {
 *         fullscreen: true,
 *
 *         items: [{
 *             title: 'First',
 *             items: [{
 *                 xtype: 'button',
 *                 text: 'Push a new view!',
 *                 handler: function() {
 *                     //use the push() method to push another view. It works much like
 *                     //add() or setActiveItem(). it accepts a view instance, or you can give it
 *                     //a view config.
 *                     view.push({
 *                         title: 'Second',
 *                         html: 'Second view!'
 *                     });
 *                 }
 *             }]
 *         }]
 *     });
 *
 * Now, here comes the fun part: you can push any view/item into the NavigationView, at any time, and it will
 * automatically handle the animations between the two views, including adding a back button (if necessary)
 * and showing the new title.
 *
 *     view.push({
 *         title: 'A new view',
 *         html: 'Some new content'
 *     });
 *
 * As you can see, it is as simple as calling the {@link #method-push} method, with a new view (instance or object). Done.
 *
 * You can also `pop` a view at any time. This will remove the top-most view from the NavigationView, and animate back
 * to the previous view. You can do this using the {@link #method-pop} method (which requires no arguments).
 *
 *     view.pop();
 *
 */
Ext.define('Ext.navigation.View', {
    extend: 'Ext.Container',
    alternateClassName: 'Ext.NavigationView',
    xtype: 'navigationview',
    requires: ['Ext.navigation.Bar'],

    config: {
        // @inherit
        baseCls: Ext.baseCSSPrefix + 'navigationview',

        /**
         * @cfg {Boolean/Object} navigationBar
         * The NavigationBar used in this navigation view. It defaults to be docked to the top.
         *
         * You can just pass in a normal object if you want to customize the NavigationBar. For example:
         *
         *     navigationBar: {
         *         ui: 'dark',
         *         docked: 'bottom'
         *     }
         *
         * You **cannot** specify a *title* property in this configuration. The title of the navigationBar is taken
         * from the configuration of this views children:
         *
         *     view.push({
         *         title: 'This views title which will be shown in the navigation bar',
         *         html: 'Some HTML'
         *     });
         *
         * @accessor
         */
        navigationBar: {
            docked: 'top'
        },

        /**
         * @cfg {String} defaultBackButtonText
         * The text to be displayed on the back button if:
         * a) The previous view does not have a title
         * b) The {@link #useTitleForBackButtonText} configuration is true.
         * @accessor
         */
        defaultBackButtonText: 'Back',

        /**
         * @cfg {Boolean} useTitleForBackButtonText
         * Set to false if you always want to display the {@link #defaultBackButtonText} as the text
         * on the back button. True if you want to use the previous views title.
         * @accessor
         */
        useTitleForBackButtonText: false,

        /**
         * @cfg {Array/Object} items The child items to add to this NavigationView. This is usually an array of Component
         * configurations or instances, for example:
         *
         *    Ext.create('Ext.Container', {
         *        items: [
         *            {
         *                xtype: 'panel',
         *                title: 'My title',
         *                html: 'This is an item'
         *            }
         *        ]
         *    });
         *
         * If you want a title to be displayed in the {@link #navigationBar}, you must specify a `title` configuration in your
         * view, like above.
         *
         * Note: only one view will be visible at a time. If you want to change to another view, use the {@link #method-push} or
         * {@link #setActiveItem} methods.
         * @accessor
         */

        /**
         * @hide
         */
        layout: {
            type: 'card',
            animation: {
                duration: 300,
                easing: 'ease-in-out',
                type: 'slide',
                direction: 'left'
            }
        }

        // See https://sencha.jira.com/browse/TOUCH-1568
        // If you do, add to #navigationBar config docs:
        //
        //     If you want to add a button on the right of the NavigationBar,
        //     use the {@link #rightButton} configuration.
    },

    /**
     * @event push
     * Fires when a view is pushed into this navigation view
     * @param {Ext.navigation.View} this The component instance
     * @param {Mixed} view The view that has been pushed
     */

    /**
     * @event pop
     * Fires when a view is popped from this navigation view
     * @param {Ext.navigation.View} this The component instance
     * @param {Mixed} view The view that has been popped
     */

    /**
     * A stack array of all views in this navigation view.
     * @cfg {Array} stack
     * @private
     */

    /**
     * True of we are popping a view from the NavigationView
     * @cfg {Boolean} popping
     * @private
     */

    beforeInitialize: function() {
        this.stack = [];
    },

    // @private
    initialize: function() {
        //add a listener onto the back button in the navigationbar
        this.getNavigationBar().on({
            scope: this,
            back: this.onBackButtonTap
        });
    },

    /**
     * @private
     * Called when the user taps on the back button
     */
    onBackButtonTap: function() {
        this.popAnimated();
    },

    /**
     * Pushes a new view into this navigation view using the default animation that this view has.
     * @param {Object} view The view to push
     * @return {Ext.Component} The new item you just pushed
     */
    push: function(view) {
        if (!this.canPush()) {
            return;
        }

        this.popping = false;

        return this.setActiveItem(view);
    },

    /**
     * Pushes a new view into this navigation view using a animation passed as a configuration.
     * @param {Object} view The view to push
     * @param {Boolean/Object} config The animation configuration.
     * @return {Ext.Component} The new item you just pushed
     */
    pushAnimated: function(view, config) {
        if (!this.canPush()) {
            return;
        }

        this.popping = false;

        var layout = this.getLayout(),
            animation = layout.getAnimation(),
            item;

        //merge the new animation configuration with the existing one
        if (Ext.isObject(config)) {
            config = Ext.Object.merge({}, animation.getInitialConfig(), config);
        }

        layout.setAnimation(config);
        item = this.setActiveItem(view);
        layout.getAnimation().disable();
        layout.setAnimation(animation);
        layout.getAnimation().enable();

        return item;
    },

    /**
     * Removes the current active view from the stack and sets the previous view using the default animation
     * of this view.
     * @param {Number} count The number of views you want to pop
     * @return {Ext.Component} The new active item
     */
    pop: function(count) {
        if (this.onBeforePop(count)) {
            return this.doPop();
        }
    },

    /**
     * Removes the current active view from the stack and animates it to the previous view using the specified config.
     * @param {Number} count The number of views you want to pop
     * @param {Boolean/Object} config The animation configuration.
     * @return {Ext.Component} The new active item
     */
    popAnimated: function(count, config) {
        if (this.onBeforePop(count)) {
            return this.doPop(config);
        }
    },

    /**
     * @private
     * Calculates whether it needs to remove any items from the stack when you are popping more than 1
     * item. If it does, it removes those views from the stack and returns `true`.
     * @return {Boolean} True if it has removed views
     */
    onBeforePop: function(count) {
        var me = this,
            stack = me.stack,
            newStack = [],
            ln = stack.length,
            last, i;

        //default to 1 pop
        if (!Ext.isNumber(count) || count < 1) {
            count = 1;
        }

        //check if we are trying to remove more items than we have
        count = Math.min(count, stack.length - 1);

        if (count) {
            //we need to reset the backButtonStack in the navigation bar
            me.getNavigationBar().onBeforePop(count);

            //we dont want count to take into account the last item, as we need to keep it for animations
            count--;

            //we only want to keep the first and last views because we are going from the last view, to the first view
            newStack.push(stack.shift());
            last = stack.pop();

            //loop through all other views in the stack and remove them from the view
            for (i = 0; i < count; i++) {
                stack.pop();
            }

            newStack = newStack.concat(stack);
            newStack.push(last);

            //update the stack and pop back to the first view
            me.stack = newStack;

            return true;
        }

        return false;
    },

    /**
     * @private
     */
    doPop: function(config) {
        var me = this,
            layout = me.getLayout(),
            animation = layout.getAnimation();

        if (!this.canPop()) {
            //<debug>
            Ext.Logger.warn('Ext.navigation.View#pop: Trying to pop a view, but there are no views to pop.');
            //</debug>
            return;
        }

        //remove the last item in the stack
        me.stack.pop();

        if (Ext.isDefined(config)) {
            //merge the new animation configuration with the existing one
            if (Ext.isObject(config)) {
                config = Ext.Object.merge({}, animation.getInitialConfig(), config);
            }

            //set the animation configuration
            layout.setAnimation(config);
            layout.getAnimation().enable();
        } else {
            //reverse the animation
            if (animation && animation.isAnimation) {
                animation.setReverse(true);
            }
        }

        //set the new active item to be the new last item of the stack
        me.popping = true;
        me.setActiveItem(me.stack[me.stack.length - 1]);
        me.popping = false;

        if (Ext.isDefined(config)) {
            //revert the animation
            layout.setAnimation(animation);
            layout.getAnimation().enable();
        } else {
            //unreverse the animation
            if (animation && animation.isAnimation) {
                animation.setReverse(false);
            }
        }

        return this.getActiveItem();
    },

    /**
     * Simply returns true if this navigation view can purrently push a new item.
     * @return {Boolean} True if you can push
     */
    canPush: function() {
        var navigationBar = this.getNavigationBar();

        if (navigationBar && navigationBar.animating) {
            return false;
        }

        return true;
    },

    /**
     * Simply returns true if this navigation view can currently pop and item. False if there is only one view
     * in the stack.
     * @return {Boolean} True if you can pop
     */
    canPop: function() {
        var canPop = this.stack.length > 1,
            navigationBar = this.getNavigationBar();

        if (navigationBar && navigationBar.animating) {
            return false;
        }

        return canPop;
    },

    /**
     * Returns the previous item, if one exists.
     * @return {Mixed} The previous view
     */
    getPreviousItem: function() {
        var stack = this.stack;
        return stack[stack.length - 1];
    },

    /**
     * Updates the backbutton text accordingly in the {@link #navigationBar}
     * @private
     */
    updateUseTitleForBackButtonText: function(useTitleForBackButtonText) {
        var navigationBar = this.getNavigationBar();
        if (navigationBar) {
            navigationBar.setUseTitleForBackButtonText(useTitleForBackButtonText);
        }
    },

    // @private
    applyNavigationBar: function(config) {
        if (!config) {
            config = {
                hidden: true,
                docked: 'top'
            };
        }

        if (config.title) {
            delete config.title;
            //<debug>
            Ext.Logger.warn("Ext.navigation.View: The 'navigationBar' configuration does not accept a 'title' property. You " +
                            "set the title of the navigationBar by giving this navigation view's children a 'title' property.");
            //</debug>
        }

        return Ext.factory(config, Ext.navigation.Bar, this.getNavigationBar());
    },

    // @private
    updateNavigationBar: function(newNavigationBar, oldNavigationBar) {
        if (oldNavigationBar) {
            this.remove(oldNavigationBar);
        }

        if (newNavigationBar) {
            var layout = this.getLayout(),
                animation = (layout && layout.isLayout) ? layout.getAnimation() : false;

            if (animation && animation.isAnimation) {
                newNavigationBar.setAnimation(animation.config);
            }
            this.add(newNavigationBar);
        }
    },

    // @private
    doSetActiveItem: function(activeItem, oldActiveItem) {
        var me = this,
            navigationBar = me.getNavigationBar(),
            stack = me.stack,
            layout = me.getLayout(),
            animation = layout.getAnimation(),
            useAnimation = animation && animation.isAnimation && this.isPainted(),
            pushFn = (useAnimation) ? 'pushAnimated' : 'push',
            popFn = (useAnimation) ? 'popAnimated' : 'pop',
            title;

        if (!activeItem) {
            return;
        }

        title = activeItem.getInitialConfig().title;

        //if we are not popping a view, then add it to the stack
        if (!me.popping) {
            stack.push(activeItem);
            me.fireEvent('push', this, activeItem);
        } else {
            me.fireEvent('pop', this, oldActiveItem);
        }

        if (navigationBar) {
            //if there is a previous item in the stack, then we must show the backbutton
            //else we should just hide it
            if (stack.length > 1) {
                if (me.popping) {
                    navigationBar[popFn](title, animation.config);
                } else {
                    navigationBar[pushFn](title, animation.config);
                }
            } else {
                if (me.isPainted()) {
                    navigationBar[popFn](title, animation.config);
                } else {
                    navigationBar.setTitle(title);
                    navigationBar.backButtonStack.push(title);
                }
            }
        }

        me.callParent(arguments);
    },

    /**
     * Resets the view by removing all items between the first and last item.
     * @return {Ext.Component} The view that is now active
     */
    reset: function() {
        this.pop(this.stack.length);
    },

    /**
     * Resets the view by removing all items between the first and last item, and animates to that view
     * @return {Ext.Component} The view that is now active
     */
    resetAnimated: function() {
        this.popAnimated(this.stack.length);
    }
});