/**
 * A 'Toast' is a simple message that is displayed on the screen and then automatically closed by a
 * timeout. Think about it like a text only alert box that will self destruct. **A Toast should not
 * be instantiated manually** but creating by calling 'Ext.toast(message, timeout)'. This will
 * create one reusable toast container and content will be swapped out as toast messages are queued
 * or displayed.
 *
 * # Simple Toast
 *
 * ```javascript
 * @example({ framework: 'extjs' })
 *  // Toast will close in 1000 milliseconds (default)
 *  Ext.toast('Hello Sencha!');
 * ```
 *
 * # Toast with Timeout
 *
 * ```javascript
 * @example({ framework: 'extjs' })
 *  // Toast will close in 5000 milliseconds
 *  Ext.toast('Hello Sencha!', 5000);
 * ```
 *
 * # Toast with config
 *
 * ```javascript
 * @example({ framework: 'extjs' })
 *  // Toast will close in 2000 milliseconds
 *  Ext.toast({message: 'Hello Sencha!', timeout: 2000});
 * ```
 *
 * # Multiple Toasts queued
 *
 * ```javascript
 * @example({ framework: 'extjs' })
 *  Ext.toast('Hello Sencha!');
 *  Ext.toast('Hello Sencha Again!');
 *  Ext.toast('Hello Sencha One More Time!');
 * ```
 * ```html
 * @example({framework: 'ext-web-components', packages:['ext-web-components'], tab: 1 })
 * <ext-container layout='{"type": "vbox", "align": "left"}'>
 *     <ext-button
 *          ui="action"
 *          ontap="toast.onTap"
 *          text="Show Toast"
 *     >
 *      </ext-button>
 * </ext-container>
 *       
 * ```
 * ```javascript
 * @example({framework: 'ext-web-components', tab: 2, packages: ['ext-web-components']})
 *
 * import '@sencha/ext-web-components/dist/ext-button.component';
 * import '@sencha/ext-web-components/dist/ext-container.component';
 *  
 * Ext.require('Ext.Toast');
 *  
 * export default class ToastComponent {
 *   onTap = () => {
 *      Ext.toast('Hello World!')
 *    }
 * }
 *  window.toast = new ToastComponent();
 * ```
 * 
 * ```javascript
 * @example({framework: 'ext-react', packages:['ext-react']})
 *  import React, { Component } from 'react';
 *  import { ExtContainer, ExtButton } from '@sencha/ext-react';
 *
 *  Ext.require('Ext.Toast');
 *
 *  export default class MyExample extends Component {
 *      render() {
 *          return (
 *              <ExtContainer layout={{type: 'vbox', align: 'left'}}>
 *                  <ExtButton
 *                      ui="action"
 *                      handler={() => Ext.toast('Hello World!')}
 *                      text="Show Toast"
 *                  />
 *              </ExtContainer>
 *          )
 *      }
 *  }
 * ```
 * 
 * ```javascript
 * @example({framework: 'ext-angular', packages:['ext-angular']}
 *  import { Component } from '@angular/core'
 *  declare var Ext: any;
 *
 *  @Component({
 *      selector: 'app-root-1',
 *      styles: [`
 *              `],
 *      template: `
 *              <ExtContainer [layout]="{type: 'vbox', align: 'left'}">
 *                  <ExtButton
 *                      ui="action"
 *                      [handler]="buttonHandler"
 *                      text="Show Toast"
 *                  ></ExtButton>
 *              </ExtContainer>
 *              `
 *  })
 *  export class AppComponent {
 *      buttonHandler = function() {
 *          Ext.toast('Hello World!');
 *      }
 *  }
 * ```
 */
Ext.define('Ext.Toast', {
    extend: 'Ext.Sheet',
    requires: [
        'Ext.util.InputBlocker'
    ],
 
    config: {
        /**
         * @cfg {String} alignment
         * The alignment for the {@link Ext.Toast}.
         * @accessor
         */
        alignment: 't-t',
 
        /**
         * @cfg centered
         * @inheritdoc
         */
        centered: false,
 
        /**
         * @cfg showAnimation
         * @inheritdoc
         */
        showAnimation: {
            type: 'popIn',
            duration: 250,
            easing: 'ease-out'
        },
 
        /**
         * @cfg hideAnimation
         * @inheritdoc
         */
        hideAnimation: {
            type: 'popOut',
            duration: 250,
            easing: 'ease-out'
        },
 
        /**
         * Override the default `zIndex` so it is normally always above positioned components.
         */
        zIndex: 999,
 
        /**
         * @cfg {String} message
         * The message to be displayed in the {@link Ext.Toast}.
         * @accessor
         */
        message: '',
 
        /**
         * @cfg {Number} timeout
         * The amount of time in milliseconds to wait before destroying the toast automatically
         */
        timeout: 1000,
 
        /**
         * @cfg {Number} maxQueue
         * The the maximum number of toasts that can be queued up.  When there are this many toasts
         * queued up and a new call to Ext.toast() is made, the oldest queued Toast that is not yet
         * displayed will be dropped from the queue.
         */
        maxQueue: 3,
 
        /**
         * @cfg {Boolean/Object} messageAnimation
         * The animation that should be used between toast messages when they are queued up
         */
        messageAnimation: true,
 
        /**
         * @cfg hideOnMaskTap
         * @inheritdoc
         */
        hideOnMaskTap: true,
 
        /**
         * @cfg modal
         * @hide
         */
        modal: false,
 
        /**
         * @cfg layout
         * @inheritdoc
         */
        layout: {
            type: 'vbox',
            pack: 'center'
        }
    },
 
    /**
     * @property classCls
     * @inheritdoc
     */
    classCls: Ext.baseCSSPrefix + 'toast',
 
    /**
     * @private
     */
    applyMessage: function(value) {
        var config = {
            html: value,
            cls: this.baseCls + '-text'
        };
 
        return Ext.factory(config, Ext.Component, this._message);
    },
 
    /**
     * @private
     */
    updateMessage: function(newMessage) {
        if (newMessage) {
            this.add(newMessage);
        }
    },
 
    /**
     * @private
     */
    startTimer: function() {
        var timeout = this.getTimeout();
 
        if (this._timeoutID) {
            Ext.undefer(this._timeoutID);
        }
 
        if (!Ext.isEmpty(timeout)) {
            this._timeoutID = Ext.defer(this.onTimeout.bind(this), timeout);
        }
        else {
            this.onTimeout();
        }
    },
 
    stopTimer: function() {
        Ext.undefer(this._timeoutID);
        this._timeoutID = null;
    },
 
    /**
     * @method
     * @private
     */
    next: Ext.emptyFn,
 
    getIsAnimating: function() {
        var messageContainer = this.getMessage();
 
        return (messageContainer && Ext.Animator.hasRunningAnimations(
            messageContainer)) || Ext.Animator.hasRunningAnimations(this);
    },
 
    /**
     * @private
     */
    showToast: function(config) {
        var me = this,
            message = config.message,
            timeout = config.timeout || this.getTimeout(),
            messageContainer = me.getMessage(),
            msgAnimation = me.getMessageAnimation();
 
        // If the toast has already been rendered and is visible on the screen
        if (me.isRendered() && me.isHidden() === false) {
            messageContainer.onAfter({
                // After the hide is complete
                hiddenchange: function() {
                    me.setMessage(message);
                    me.setTimeout(timeout);
                    messageContainer.onAfter({
                        scope: me,
                        // After the show is complete
                        hiddenchange: function() {
                            me.startTimer();
                        },
                        single: true
                    });
                    messageContainer.show(msgAnimation);
                },
                scope: me,
                single: true
            });
 
            messageContainer.hide(msgAnimation);
        }
        else {
            Ext.util.InputBlocker.blockInputs();
 
            // if it has not been added to a container, add it to the Viewport.
            if (!me.getParent() && Ext.Viewport) {
                Ext.Viewport.add(me);
            }
 
            me.setMessage(message);
            me.setTimeout(timeout);
            me.startTimer();
            me.show({
                animation: null,
                alignment: {
                    component: document.body,
                    alignment: config.alignment || me.getAlignment(),
                    options: {
                        offset: [0, 20]
                    }
                }
            });
        }
    },
 
    /**
     * @private
     */
    beforeHide: function(animation) {
        this.callParent(arguments);
 
        // If the message is animating cancel this hide
        if (this.getIsAnimating()) {
            return false;
        }
 
        this.stopTimer();
 
        if (!this.next()) {
            return false;
        }
    },
 
    /**
     * @private
     */
    onTimeout: function() {
        if (this._timeoutID !== null) {
            this.hide();
        }
    },
 
    doDestroy: function() {
        this.stopTimer();
        this.callParent();
    }
}, function(Toast) {
    var _queue = [];
 
    function getInstance() {
        if (!Ext.Toast._instance) {
            Ext.Toast._instance = Ext.create('Ext.Toast');
        }
 
        return Ext.Toast._instance;
    }
 
    //<debug>
    /**
     * @member Ext.Toast
     * @method getQueueCount
     * @private
     * Provided for unit tests
     * @returns {Number} 
     */
    Toast.prototype.getQueueCount = function() {
        return _queue.length;
    };
    //</debug>
 
    Toast.prototype.next = function() {
        var config = _queue.shift();
 
        if (config) {
            this.showToast(config);
        }
 
        return !config;
    };
 
    /**
     * Destroys any Toast components and elements, freeing the resources.
     *
     * They will be created again upon calling Ext.toast().
     */
    Ext.Toast.destroy = function() {
        if (Ext.Toast._instance) {
            Ext.Toast._instance.destroy();
            Ext.Toast._instance = null;
        }
    };
 
    Ext.toast = function(message, timeout) {
        var toast = getInstance(),
            maxQueue = Ext.Toast.prototype.config.maxQueue,
            config = message;
 
        if (Ext.isString(message)) {
            config = {
                message: message,
                timeout: timeout
            };
        }
 
        //<debug>
        if (!config) {
            throw new Error("Toast requires a message");
        }
        //</debug>
 
        _queue.push(config);
 
        if (_queue.length > maxQueue) {
            _queue.shift();
        }
 
        if (!toast.isRendered() || toast.isHidden()) {
            toast.next();
        }
 
        return toast;
    };
});