/**
 * @fileOverview 队列
 */
define([
    '../base',
    '../uploader',
    '../queue',
    '../file',
    '../lib/file',
    '../runtime/client',
    './widget'
], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) {

    var $ = Base.$,
        rExt = /\.\w+$/,
        Status = WUFile.Status;

    return Uploader.register({
        name: 'queue',

        init: function( opts ) {
            var me = this,
                deferred, len, i, item, arr, accept, runtime;

            if ( $.isPlainObject( opts.accept ) ) {
                opts.accept = [ opts.accept ];
            }

            // accept中的中生成匹配正则。
            if ( opts.accept ) {
                arr = [];

                for ( i = 0, len = opts.accept.length; i < len; i++ ) {
                    item = opts.accept[ i ].extensions;
                    item && arr.push( item );
                }

                if ( arr.length ) {
                    accept = '\\.' + arr.join(',')
                            .replace( /,/g, '$|\\.' )
                            .replace( /\*/g, '.*' ) + '$';
                }

                me.accept = new RegExp( accept, 'i' );
            }

            me.queue = new Queue();
            me.stats = me.queue.stats;

            // 如果当前不是html5运行时，那就算了。
            // 不执行后续操作
            if ( this.request('predict-runtime-type') !== 'html5' ) {
                return;
            }

            // 创建一个 html5 运行时的 placeholder
            // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。
            deferred = Base.Deferred();
            this.placeholder = runtime = new RuntimeClient('Placeholder');
            runtime.connectRuntime({
                runtimeOrder: 'html5'
            }, function() {
                me._ruid = runtime.getRuid();
                deferred.resolve();
            });
            return deferred.promise();
        },


        // 为了支持外部直接添加一个原生File对象。
        _wrapFile: function( file ) {
            if ( !(file instanceof WUFile) ) {

                if ( !(file instanceof File) ) {
                    if ( !this._ruid ) {
                        throw new Error('Can\'t add external files.');
                    }
                    file = new File( this._ruid, file );
                }

                file = new WUFile( file );
            }

            return file;
        },

        // 判断文件是否可以被加入队列
        acceptFile: function( file ) {
            var invalid = !file || !file.size || this.accept &&

                    // 如果名字中有后缀，才做后缀白名单处理。
                    rExt.exec( file.name ) && !this.accept.test( file.name );

            return !invalid;
        },


        /**
         * @event beforeFileQueued
         * @param {File} file File对象
         * @description 当文件被加入队列之前触发，此事件的handler返回值为`false`，则此文件不会被添加进入队列。
         * @for  Uploader
         */

        /**
         * @event fileQueued
         * @param {File} file File对象
         * @description 当文件被加入队列以后触发。
         * @for  Uploader
         */

        _addFile: function( file ) {
            var me = this;

            file = me._wrapFile( file );

            // 不过类型判断允许不允许，先派送 `beforeFileQueued`
            if ( !me.owner.trigger( 'beforeFileQueued', file ) ) {
                return;
            }

            // 类型不匹配，则派送错误事件，并返回。
            if ( !me.acceptFile( file ) ) {
                me.owner.trigger( 'error', 'Q_TYPE_DENIED', file );
                return;
            }

            me.queue.append( file );
            me.owner.trigger( 'fileQueued', file );
            return file;
        },

        getFile: function( fileId ) {
            return this.queue.getFile( fileId );
        },

        /**
         * @event filesQueued
         * @param {File} files 数组，内容为原始File(lib/File）对象。
         * @description 当一批文件添加进队列以后触发。
         * @for  Uploader
         */
        
        /**
         * @property {Boolean} [auto=false]
         * @namespace options
         * @for Uploader
         * @description 设置为 true 后，不需要手动调用上传，有文件选择即开始上传。
         * 
         */

        /**
         * @method addFiles
         * @grammar addFiles( file ) => undefined
         * @grammar addFiles( [file1, file2 ...] ) => undefined
         * @param {Array of File or File} [files] Files 对象 数组
         * @description 添加文件到队列
         * @for  Uploader
         */
        addFile: function( files ) {
            var me = this;

            if ( !files.length ) {
                files = [ files ];
            }

            files = $.map( files, function( file ) {
                return me._addFile( file );
            });

            me.owner.trigger( 'filesQueued', files );

            if ( me.options.auto ) {
                setTimeout(function() {
                    me.request('start-upload');
                }, 20 );
            }
        },

        getStats: function() {
            return this.stats;
        },

        /**
         * @event fileDequeued
         * @param {File} file File对象
         * @description 当文件被移除队列后触发。
         * @for  Uploader
         */

         /**
         * @method removeFile
         * @grammar removeFile( file ) => undefined
         * @grammar removeFile( id ) => undefined
         * @grammar removeFile( file, true ) => undefined
         * @grammar removeFile( id, true ) => undefined
         * @param {File|id} file File对象或这File对象的id
         * @description 移除某一文件, 默认只会标记文件状态为已取消，如果第二个参数为 `true` 则会从 queue 中移除。
         * @for  Uploader
         * @example
         *
         * $li.on('click', '.remove-this', function() {
         *     uploader.removeFile( file );
         * })
         */
        removeFile: function( file, remove ) {
            var me = this;

            file = file.id ? file : me.queue.getFile( file );

            this.request( 'cancel-file', file );

            if ( remove ) {
                this.queue.removeFile( file );
            }
        },

        /**
         * @method getFiles
         * @grammar getFiles() => Array
         * @grammar getFiles( status1, status2, status... ) => Array
         * @description 返回指定状态的文件集合，不传参数将返回所有状态的文件。
         * @for  Uploader
         * @example
         * console.log( uploader.getFiles() );    // => all files
         * console.log( uploader.getFiles('error') )    // => all error files.
         */
        getFiles: function() {
            return this.queue.getFiles.apply( this.queue, arguments );
        },

        fetchFile: function() {
            return this.queue.fetch.apply( this.queue, arguments );
        },

        /**
         * @method retry
         * @grammar retry() => undefined
         * @grammar retry( file ) => undefined
         * @description 重试上传，重试指定文件，或者从出错的文件开始重新上传。
         * @for  Uploader
         * @example
         * function retry() {
         *     uploader.retry();
         * }
         */
        retry: function( file, noForceStart ) {
            var me = this,
                files, i, len;

            if ( file ) {
                file = file.id ? file : me.queue.getFile( file );
                file.setStatus( Status.QUEUED );
                noForceStart || me.request('start-upload');
                return;
            }

            files = me.queue.getFiles( Status.ERROR );
            i = 0;
            len = files.length;

            for ( ; i < len; i++ ) {
                file = files[ i ];
                file.setStatus( Status.QUEUED );
            }

            me.request('start-upload');
        },

        /**
         * @method sort
         * @grammar sort( fn ) => undefined
         * @description 排序队列中的文件，在上传之前调整可以控制上传顺序。
         * @for  Uploader
         */
        sortFiles: function() {
            return this.queue.sort.apply( this.queue, arguments );
        },

        /**
         * @event reset
         * @description 当 uploader 被重置的时候触发。
         * @for  Uploader
         */

        /**
         * @method reset
         * @grammar reset() => undefined
         * @description 重置uploader。目前只重置了队列。
         * @for  Uploader
         * @example
         * uploader.reset();
         */
        reset: function() {
            this.owner.trigger('reset');
            this.queue = new Queue();
            this.stats = this.queue.stats;
        },

        destroy: function() {
            this.reset();
            this.placeholder && this.placeholder.destroy();
        }
    });

});