/**
 * 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 cls
         * @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 defaultType
         * @hide
         * @accessor
         */
        defaultType: 'button',
 
        /**
         * @cfg layout
         * @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} android2Transforms
         * 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
        }
    },
 
    /**
     * @property baseCls
     * @inheritdoc
     */
    baseCls: Ext.baseCSSPrefix + 'toolbar',
 
    /**
     * @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 (= 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 (= 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) {
        var i;
 
        count--;
 
        for (= 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
        };
    }
});