/**
 * This component is used in {@link Ext.navigation.View} to control animations in the toolbar. You should never need to
 * interact with the component directly, unless you are subclassing it.
 * @private
 */
Ext.define('Ext.navigation.Bar', {
    extend: 'Ext.TitleBar',

    requires: [
        'Ext.Button',
        'Ext.Spacer'
    ],

    /**
     * @private
     */
    isToolbar: true,

    config: {
        /**
         * @cfg
         * @inheritdoc
         */
        baseCls: Ext.baseCSSPrefix + 'toolbar',

        /**
         * @cfg
         * @inheritdoc
         */
        cls: Ext.baseCSSPrefix + 'navigation-bar',

        /**
         * @cfg {String} ui
         * Style options for Toolbar. Either 'light' or 'dark'.
         * @accessor
         */
        ui: 'dark',

        /**
         * @cfg {String} title
         * The title of the toolbar. You should NEVER set this, it is used internally. You set the title of the
         * navigation bar by giving a navigation views children a title configuration.
         * @private
         * @accessor
         */
        title: null,

        /**
         * @cfg
         * @hide
         * @accessor
         */
        defaultType: 'button',

        /**
         * @cfg
         * @ignore
         * @accessor
         */
        layout: {
            type: 'hbox'
        },

        /**
         * @cfg {Array/Object} items The child items to add to this NavigationBar. The {@link #cfg-defaultType} of
         * a NavigationBar is {@link Ext.Button}, so you do not need to specify an `xtype` if you are adding
         * buttons.
         *
         * You can also give items a `align` configuration which will align the item to the `left` or `right` of
         * the NavigationBar.
         * @hide
         * @accessor
         */

        /**
         * @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.
         * @private
         * @accessor
         */
        defaultBackButtonText: 'Back',

        /**
         * @cfg {Object} animation
         * @private
         * @accessor
         */
        animation: {
            duration: 300
        },

        /**
         * @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.
         * @private
         * @accessor
         */
        useTitleForBackButtonText: null,

        /**
         * @cfg {Ext.navigation.View} view A reference to the navigation view this bar is linked to.
         * @private
         * @accessor
         */
        view: null,

        /**
         * @cfg {Boolean} androidAnimation Optionally enable CSS transforms on Android 2
         * for NavigationBar animations.  Note that this may cause flickering if the
         * NavigationBar is hidden.
         * @accessor
         */
        android2Transforms: false,

        /**
         * @cfg {Ext.Button/Object} backButton The configuration for the back button
         * @private
         * @accessor
         */
        backButton: {
            align: 'left',
            ui: 'back',
            hidden: true
        }
    },

    /**
     * @event back
     * Fires when the back button was tapped.
     * @param {Ext.navigation.Bar} this This bar
     */

    constructor: function (config) {
        config = config || {};

        if (!config.items) {
            config.items = [];
        }

        this.backButtonStack = [];
        this.activeAnimations = [];

        this.callParent([config]);
    },

    /**
     * @private
     */
    applyBackButton: function (config) {
        return Ext.factory(config, Ext.Button, this.getBackButton());
    },

    /**
     * @private
     */
    updateBackButton: function (newBackButton, oldBackButton) {
        if (oldBackButton) {
            this.remove(oldBackButton);
        }

        if (newBackButton) {
            this.add(newBackButton);

            newBackButton.on({
                scope: this,
                tap: this.onBackButtonTap
            });
        }
    },

    onBackButtonTap: function () {
        this.fireEvent('back', this);
    },

    /**
     * @private
     */
    updateView: function (newView) {
        var me = this,
            backButton, innerItems, i, backButtonText, item, title, titleText;

        // Need to have items initialized before getting the backButton
        me.getItems();
        backButton = me.getBackButton();

        if (newView) {
            //update the back button stack with the current inner items of the view
            innerItems = newView.getInnerItems();
            for (i = 0; i < innerItems.length; i++) {
                item = innerItems[i];
                title = (item.getTitle) ? item.getTitle() : item.config.title;

                me.backButtonStack.push(title || '&nbsp;');
            }

            titleText = me.getTitleText();

            if (titleText === undefined) {
                titleText = '';
            }

            me.setTitle(titleText);

            backButtonText = me.getBackButtonText();
            if (backButtonText) {
                backButton.setText(backButtonText);
                backButton.show();
            }
        }
    },

    /**
     * @private
     */
    onViewAdd: function (view, item) {
        var me = this,
            backButtonStack = me.backButtonStack,
            hasPrevious, title;

        me.endAnimation();

        title = (item.getTitle) ? item.getTitle() : item.config.title;

        backButtonStack.push(title || '&nbsp;');
        hasPrevious = backButtonStack.length > 1;

        me.doChangeView(view, hasPrevious, false);
    },

    /**
     * @private
     */
    onViewRemove: function (view) {
        var me = this,
            backButtonStack = me.backButtonStack,
            hasPrevious;

        me.endAnimation();
        backButtonStack.pop();
        hasPrevious = backButtonStack.length > 1;

        me.doChangeView(view, hasPrevious, true);
    },

    /**
     * @private
     */
    doChangeView: function (view, hasPrevious, reverse) {
        var me = this,
            leftBox = me.leftBox,
            leftBoxElement = leftBox.element,
            titleComponent = me.titleComponent,
            titleElement = titleComponent.element,
            backButton = me.getBackButton(),
            titleText = me.getTitleText(),
            backButtonText = me.getBackButtonText(),
            animation = me.getAnimation() && view.getLayout().getAnimation(),
            animated = animation && animation.isAnimation && view.isPainted(),
            properties, leftGhost, titleGhost, leftProps, titleProps;

        if (animated) {
            leftGhost = me.createProxy(leftBox.element);
            leftBoxElement.setStyle('opacity', '0');
            backButton.setText(backButtonText);
            backButton[hasPrevious ? 'show' : 'hide']();

            titleGhost = me.createProxy(titleComponent.element.getParent());
            titleElement.setStyle('opacity', '0');
            me.setTitle(titleText);

            properties = me.measureView(leftGhost, titleGhost, reverse);
            leftProps = properties.left;
            titleProps = properties.title;

            me.isAnimating = true;
            me.animate(leftBoxElement, leftProps.element);
            me.animate(titleElement, titleProps.element, function () {
                titleElement.setLeft(properties.titleLeft);
                me.isAnimating = false;
                me.refreshTitlePosition();
            });

            me.animate(leftGhost.ghost, leftProps.ghost);
            me.animate(titleGhost.ghost, titleProps.ghost, function () {
                leftGhost.ghost.destroy();
                titleGhost.ghost.destroy();
            });
        }
        else {
            if (hasPrevious) {
                backButton.setText(backButtonText);
                backButton.show();
            }
            else {
                backButton.hide();
            }
            me.setTitle(titleText);
        }
    },

    /**
     * Calculates and returns the position values needed for the back button when you are pushing a title.
     * @private
     */
    measureView: function (oldLeft, oldTitle, reverse) {
        var me = this,
            barElement = me.element,
            newLeftElement = me.leftBox.element,
            titleElement = me.titleComponent.element,
            minOffset = Math.min(barElement.getWidth() / 3, 200),
            newLeftWidth = newLeftElement.getWidth(),
            barX = barElement.getX(),
            barWidth = barElement.getWidth(),
            titleX = titleElement.getX(),
            titleLeft = titleElement.getLeft(true),
            titleWidth = titleElement.getWidth(),
            oldLeftX = oldLeft.x,
            oldLeftWidth = oldLeft.width,
            oldLeftLeft = oldLeft.left,
            newOffset, oldOffset, leftAnims, titleAnims, omega, theta;

        theta = barX - oldLeftX - oldLeftWidth;
        if (reverse) {
            newOffset = theta;
            oldOffset = Math.min(titleX - oldLeftWidth, minOffset);
        } else {
            oldOffset = theta;
            newOffset = Math.min(titleX - barX, minOffset);
        }

        leftAnims = {
            element: {
                from: {
                    transform: {
                        translateX: newOffset
                    },
                    opacity: 0
                },
                to: {
                    transform: {
                        translateX: 0
                    },
                    opacity: 1
                }
            },
            ghost: {
                to: {
                    transform: {
                        translateX: oldOffset
                    },
                    opacity: 0
                }
            }
        };

        theta = barX - titleX + newLeftWidth;
        if ((oldLeftLeft + titleWidth) > titleX) {
            omega = barX - titleX - titleWidth;
        }

        if (reverse) {
            titleElement.setLeft(0);

            oldOffset = barX + barWidth - titleX - titleWidth;

            if (omega !== undefined) {
                newOffset = omega;
            } else {
                newOffset = theta;
            }
        } else {
            newOffset = barX + barWidth - titleX - titleWidth;

            if (omega !== undefined) {
                oldOffset = omega;
            } else {
                oldOffset = theta;
            }

            newOffset = Math.max(titleLeft, newOffset);
        }

        titleAnims = {
            element: {
                from: {
                    transform: {
                        translateX: newOffset
                    },
                    opacity: 0
                },
                to: {
                    transform: {
                        translateX: titleLeft
                    },
                    opacity: 1
                }
            },
            ghost: {
                to: {
                    transform: {
                        translateX: oldOffset
                    },
                    opacity: 0
                }
            }
        };

        return {
            left: leftAnims,
            title: titleAnims,
            titleLeft: titleLeft
        };
    },

    /**
     * Helper method used to animate elements.
     * You pass it an element, objects for the from and to positions an option onEnd callback called when the animation is over.
     * Normally this method is passed configurations returned from the methods such as #measureTitle(true) etc.
     * It is called from the #pushLeftBoxAnimated, #pushTitleAnimated, #popBackButtonAnimated and #popTitleAnimated
     * methods.
     *
     * If the current device is Android, it will use top/left to animate.
     * If it is anything else, it will use transform.
     * @private
     */
    animate: function (element, config, callback) {
        var me = this,
            animation;

        //reset the left of the element
        element.setLeft(0);

        config = Ext.apply(config, {
            element: element,
            easing: 'ease-in-out',
            duration: me.getAnimation().duration || 250,
            preserveEndState: true
        });

        animation = new Ext.fx.Animation(config);
        animation.on('animationend', function () {
            if (callback) {
                callback.call(me);
            }
        }, me);

        Ext.Animator.run(animation);
        me.activeAnimations.push(animation);
    },

    endAnimation: function () {
        var activeAnimations = this.activeAnimations,
            animation, i, ln;

        if (activeAnimations) {
            ln = activeAnimations.length;
            for (i = 0; i < ln; i++) {
                animation = activeAnimations[i];
                if (animation.isAnimating) {
                    animation.stopAnimation();
                }
                else {
                    animation.destroy();
                }
            }
            this.activeAnimations = [];
        }
    },

    refreshTitlePosition: function () {
        if (!this.isAnimating) {
            this.callParent();
        }
    },

    /**
     * Returns the text needed for the current back button at anytime.
     * @private
     */
    getBackButtonText: function () {
        var text = this.backButtonStack[this.backButtonStack.length - 2],
            useTitleForBackButtonText = this.getUseTitleForBackButtonText();

        if (!useTitleForBackButtonText) {
            if (text) {
                text = this.getDefaultBackButtonText();
            }
        }

        return text;
    },

    /**
     * Returns the text needed for the current title at anytime.
     * @private
     */
    getTitleText: function () {
        return this.backButtonStack[this.backButtonStack.length - 1];
    },

    /**
     * Handles removing back button stacks from this bar
     * @private
     */
    beforePop: function (count) {
        count--;
        for (var i = 0; i < count; i++) {
            this.backButtonStack.pop();
        }
    },

    /**
     * We override the hidden method because we don't want to remove it from the view using display:none. Instead we just position it off
     * the screen, much like the navigation bar proxy. This means that all animations, pushing, popping etc. all still work when if you hide/show
     * this bar at any time.
     * @private
     */
    updateHidden: function (hidden) {
        if (!hidden) {
            this.element.setStyle({
                position: 'relative',
                top: 'auto',
                left: 'auto',
                width: 'auto'
            });
        } else {
            this.element.setStyle({
                position: 'absolute',
                top: '-1000px',
                left: '-1000px',
                width: this.element.getWidth() + 'px'
            });
        }
    },

    /**
     * Creates a proxy element of the passed element, and positions it in the same position, using absolute positioning.
     * The createNavigationBarProxy method uses this to create proxies of the backButton and the title elements.
     * @private
     */
    createProxy: function (element) {
        var ghost, x, y, left, width;

        ghost = element.dom.cloneNode(true);
        ghost.id = element.id + '-proxy';

        //insert it into the toolbar
        element.getParent().dom.appendChild(ghost);

        //set the x/y
        ghost = Ext.get(ghost);
        x = element.getX();
        y = element.getY();
        left = element.getLeft(true);
        width = element.getWidth();
        ghost.setStyle('position', 'absolute');
        ghost.setX(x);
        ghost.setY(y);
        ghost.setHeight(element.getHeight());
        ghost.setWidth(width);

        return {
            x: x,
            y: y,
            left: left,
            width: width,
            ghost: ghost
        };
    }
});
