/**
 * 更新可能なプログレスバーコンポーネント。プログレスバーは手動と自動の2つの異なるモードをサポートします。
 *
 * 手動モードでは、コードから必要に応じて、プログレスバーを表示、更新({@link #updateProgress}経由)およびクリアする必要があります。この方法は、コントロールを更新できる予測可能な注目ポイントのある操作全体で進捗を表示したい場合に最適です。
 *
 * 自動モードでは、{@link #wait}を呼び出して、プログレスバーを無制限に実行し、操作終了時にクリアするだけです。また、プログレスバーを一定時間待機させ、自動的にクリアさせることもできます。自動モードは、途中の進捗を表示する必要のない時間制限のある操作または非同期操作に最適です。
 *
 *     @example
 *     var p = Ext.create('Ext.ProgressBar', {
 *        renderTo: Ext.getBody(),
 *        width: 300
 *     });
 *
 *     // Wait for 5 seconds, then update the status el (progress bar will auto-reset)
 *     p.wait({
 *         interval: 500, //bar will move fast!
 *         duration: 50000,
 *         increment: 15,
 *         text: 'Updating...',
 *         scope: this,
 *         fn: function(){
 *             p.updateText('Done!');
 *         }
 *     });
 */
Ext.define('Ext.ProgressBar', {
    extend: 'Ext.Component',
    alias: 'widget.progressbar',

    requires: [
        'Ext.Template',
        'Ext.CompositeElement',
        'Ext.TaskManager',
        'Ext.layout.component.ProgressBar'
    ],

    uses: ['Ext.fx.Anim'],

    config: {
        /**
         * @cfg {Number} [value=0]
         * 0から1の間の浮動小数点値(たとえば、.5)
         */
        value: 0,

        /**
         * @cfg {String/Ext.XTemplate} [textTpl]
         * 指定された2つの値を使用してこのプログレスバーの背景テキストを作成するのに使用するテンプレート。
         *
         *    `value  ' - 0~1 'percent'の間の行の進捗値  - 0~100のパーセンテージで表される値
         */
        textTpl: null
    },

   /**
    * @cfg {String/HTMLElement/Ext.dom.Element} textEl
    * テキストをレンダリングする要素(デフォルトはプログレスバー内部のテキスト要素)
    */

   /**
    * @cfg {String} id
    * プログレスバー要素のid(デフォルトは自動生成されたid)。
    */

   /**
    * @cfg {String} [baseCls='x-progress']
    * プログレスバーのラッパー要素に適用する、ベースのCSSクラス。
    */
    baseCls: Ext.baseCSSPrefix + 'progress',

    /**
     * @cfg {Boolean/Object} animate
     * 遷移中にプログレスバーをアニメーション化するか、アニメーション設定(詳細は{@link #method-animate}メソッドを参照してください)の場合は、trueに設定します。
     */
    animate: false,

    /**
     * @cfg {String} text
     * プログレスバーに表示するテキスト。
     */
    text: '',

    // private
    waitTimer: null,

    childEls: [
        'bar'
    ],

    defaultBindProperty: 'value',

    renderTpl: [
        '<tpl if="internalText">',
            '<div class="{baseCls}-text {baseCls}-text-back">{text}</div>',
        '</tpl>',
        '<div id="{id}-bar" data-ref="bar" class="{baseCls}-bar {baseCls}-bar-{ui}" role="presentation" style="width:{percentage}%">',
            '<tpl if="internalText">',
                '<div class="{baseCls}-text">',
                    '<div>{text}</div>',
                '</div>',
            '</tpl>',
        '</div>'
    ],

    componentLayout: 'progressbar',
    
    ariaRole: 'progressbar',

    /**
     * @event update
     * 各更新間隔の後に発火します。
     * @param {Ext.ProgressBar} this
     * @param {Number} value 現在の進捗値
     * @param {String} text 現在の進捗テキスト
     */

    initRenderData: function() {
        var me = this;
        return Ext.apply(me.callParent(), {
            internalText : !me.hasOwnProperty('textEl'),
            text         : me.text || '&#160;',
            percentage   : me.value ? me.value * 100 : 0
        });
    },

    onRender : function() {
        var me = this;

        me.callParent(arguments);

        // External text display
        if (me.textEl) {
            me.textEl = Ext.get(me.textEl);
            me.updateText(me.text);
        }
        // Inline text display
        else {
            // This produces a composite w/2 el's (which is why we cannot use childEls or
            // renderSelectors):
            me.textEl = me.el.select('.' + me.baseCls + '-text');
        }
    },

    updateValue: function(value) {
        this.updateProgress(value, Math.round(value * 100) + '%');
    },

    /**
     * プログレスバーの値と、必要であればテキストを更新します。
     * 
     * テキストの引数が指定されていない場合、{@link #textTpl}を使用してテキストを生成します。`textTpl`がない場合は、既存のテキストの値は変更されません。既存のテキストを空にする場合は`""`を渡します。
     *
     * プログレスバーが1を超える場合であっても、これが自動的にリセットされることはありません。進捗がいつ完了したか判断し、コントロールをクリアまたは非表示にするために{@link #reset}を呼び出す必要があります。
     * @param {Number} [value=0] 0から1の間の浮動小数点値(たとえば、.5)
     * @param {String} [text=''] 進行状況テキスト要素に表示される文字列。
     * @param {Boolean} [animate=false] プログレスバーの遷移をアニメーション化するかどうか。この値が指定されていない場合、クラスのデフォルトが使用されます。
     * @return {Ext.ProgressBar} this
     */
    updateProgress: function(value, text, animate) {
        var me = this,
            oldValue = me.value,
            textTpl = me.getTextTpl();

        // Ensure value is not undefined.
        me.value = value || (value = 0);

        // Empty string (falsy) must blank out the text as per docs.
        if (text != null) {
            me.updateText(text);
        }
        // Generate text using template and progress values.
        else if (textTpl) {
            me.updateText(textTpl.apply({
                value: value,
                percent: value * 100
            }));
        }
        if (me.rendered && !me.isDestroyed) {
            if (animate === true || (animate !== false && me.animate)) {
                me.bar.stopAnimation();
                me.bar.animate(Ext.apply({
                    from: {
                        width: (oldValue * 100) + '%'
                    },
                    to: {
                        width: (value * 100) + '%'
                    }
                }, me.animate));
            } else {
                me.bar.setStyle('width', (value * 100) + '%');
            }
        }
        me.fireEvent('update', me, value, text);
        return me;
    },

    /**
     * プログレスバーのテキストを更新します。テキスト要素が指定されている場合はその要素が更新され、指定されていない場合はプログレスバー自体に表示されているテキストが更新されます。
     * @param {String} [text=''] 進行状況テキスト要素に表示される文字列。
     * @return {Ext.ProgressBar} this
     */
    updateText: function(text) {
        var me = this;
        
        me.text = text;
        if (me.rendered) {
            me.textEl.setHtml(me.text);
        }
        return me;
    },

    applyTextTpl: function(textTpl) {
        if (!textTpl.isTemplate) {
            textTpl = new Ext.XTemplate(textTpl);
        }
        return textTpl;
    },

    applyText : function(text) {
        this.updateText(text);
    },
    
    getText: function(){
        return this.text;    
    },

    /**
     * プログレスバーの自動更新を開始します。継続時間を指定することができ、その場合、プログレスバーは一定時間後に自動的にリセットされるか、必要に応じてコールバック関数が指定されている場合には呼び出してリセットされます。継続時間が渡されていない場合、プログレスバーは無制限に実行されるため、{@link #reset}を呼び出して手動でクリアする必要があります。
     *
     * 使用例:
     *
     *     var p = new Ext.ProgressBar({
     *        renderTo: 'my-el'
     *     });
     *
     *     //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
     *     var p = Ext.create('Ext.ProgressBar', {
     *        renderTo: Ext.getBody(),
     *        width: 300
     *     });
     *
     *     //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
     *     p.wait({
     *        interval: 500, //bar will move fast!
     *        duration: 50000,
     *        increment: 15,
     *        text: 'Updating...',
     *        scope: this,
     *        fn: function(){
     *           p.updateText('Done!');
     *        }
     *     });
     *
     *     //Or update indefinitely until some async action completes, then reset manually
     *     p.wait();
     *     myAction.on('complete', function(){
     *         p.reset();
     *         p.updateText('Done!');
     *     });
     *
     * @param {Object} config (optional) 設定オプション
     * @param {Number} config.duration プログレスバーをリセットするまで実行されるミリ秒単位の時間(デフォルトはundefinedです。この場合、resetが呼び出されるまで無制限に実行されます)
     * @param {Number} config.interval 各進行状況の更新間隔のミリ秒単位の時間(デフォルトは1000 ms)
     * @param {Boolean} config.animate プログレスバーの遷移をアニメーション化するかどうか。この値が指定されていない場合、クラスのデフォルトが使用されます。
     * @param {Number} config.increment プログレスバー内に表示する進行状況の更新セグメント数(デフォルトは10)。バーが最後に達しても更新を続けている場合、自動的に先頭に戻ります。
     * @param {String} config.text プログレスバー要素内に表示するオプションのテキスト(デフォルトは'')。
     * @param {Function} config.fn プログレスバーの自動更新が完了した後で実行されるコールバック関数。関数は引数なしで呼び出されます。継続時間が指定されていない場合、プログレスバーはプログラムによってのみ停止させることができるため、この関数は無視されます。 プログレスバーをリセットしたあと、必要な関数が同じコードによって呼び出される必要があります。
     * @param {Object} config.scope コールバック関数に渡されるスコープ(durationとfnの両方が渡される場合のみ適用されます)。
     * @return {Ext.ProgressBar} this
     */
    wait: function(o) {
        var me = this, scope;
            
        if (!me.waitTimer) {
            scope = me;
            o = o || {};
            me.updateText(o.text);
            me.waitTimer = Ext.TaskManager.start({
                run: function(i){
                    var inc = o.increment || 10;
                    i -= 1;
                    me.updateProgress(((((i+inc)%inc)+1)*(100/inc))*0.01, null, o.animate);
                },
                interval: o.interval || 1000,
                duration: o.duration,
                onStop: function(){
                    if (o.fn) {
                        o.fn.apply(o.scope || me);
                    }
                    me.reset();
                },
                scope: scope
            });
        }
        return me;
    },

    /**
     * プログレスバーが現在{@link #wait}操作中の場合にtrueを返します。
     * @return {Boolean} 待機状態の場合はtrue、それ以外はfalse。
     */
    isWaiting: function(){
        return this.waitTimer !== null;
    },

    /**
     * プログレスバーの値を0にし、テキストを空にしてリセットします。hideがtrueの場合、プログレスバーが非表示になります(内部で{@link #hideMode}プロパティを使用)。
     * @param {Boolean} [hide=false] プログレスバーを非表示にする場合はtrue。
     * @return {Ext.ProgressBar} this
     */
    reset: function(hide){
        var me = this;
        
        me.updateProgress(0);
        me.clearTimer();
        if (hide === true) {
            me.hide();
        }
        return me;
    },

    // private
    clearTimer: function(){
        var me = this;
        
        if (me.waitTimer) {
            me.waitTimer.onStop = null; //prevent recursion
            Ext.TaskManager.stop(me.waitTimer);
            me.waitTimer = null;
        }
    },

    onDestroy: function(){
        var me = this,
            bar = me.bar;
        
        me.clearTimer();
        if (me.rendered) {
            if (me.textEl.isComposite) {
                me.textEl.clear();
            }
            Ext.destroyMembers(me, 'textEl', 'progressBar');
            if (bar && me.animate) {
                bar.stopAnimation();
            }
        }
        me.callParent();
    }
});