const COMP_ID = "extend-upload-chooseFile", COMP_TABLE_ID = "extend-uploader-form",
    STATUS = {
        added: 0,
        ready: 1,
        uploading: 2,
        merging: 3,
        uploadOK: 4,
        uploadError: 5,
        pause: 6,
        cancel: 7,
    };
layui.extend({
    webuploader: 'uploader/exts/webuploader'
}).define(['layer', 'laytpl', 'table', 'element', 'webuploader'], function (exports) {
    var $ = layui.$
        , webUploader = layui.webuploader
        , element = layui.element
        , layer = layui.layer
        , table = layui.table
        , rowData = []//保存上传文件属性集合,添加table用
        , upload
        , dirUpload
        , btnText;
    let fileBoxEle = "#fileBoxEle";
    let loading = new Loading(layer);
    let uploadIndexMap = new Map();

    //加载样式
    layui.link(ctx + '/js/common/layui/extend/uploader/exts/webuploader.css');


    let Class = function (options) {
        this.options = options;
        this.register();
        this.init();
        this.events();
    };
    Class.prototype.init = function () {
        let that = this, options = that.options;
        dirUpload = options.dirUpload;
        btnText = dirUpload ? '选择文件夹' : '选择文件';

        let fileBox = `
                    <div id="${COMP_ID}" style="margin-left: 5px;margin-top: 5px;height: 32px;line-height:22px; opacity: 0">${btnText}</div>
                    <div id="${COMP_ID}_2" class="layui-btn layui-btn-sm layui-btn-normal" style="height: 32px;position: absolute; top:0; margin: 5px; padding: 0 20px; font-size: 14px">${btnText}</div>
                    <table style="margin-top:-10px;" class="layui-table" id="${COMP_TABLE_ID}" lay-filter="${COMP_TABLE_ID}">
                      <thead>
                        <tr>
                          <th lay-data="{type:'numbers'}"></th>
                          <th lay-data="{field:'fileName'}">文件名称</th>
                          <th lay-data="{field:'progress', width:'35%',templet:'#processTemplate'}">进度</th>
                          <th lay-data="{field:'fileSize', width:120}">文件大小</th>
                          <th lay-data="{field:'oper', fixed: 'right', width: 130, align:'center', templet: '#rowTemplate'}">操作</th>
                        </tr>
                      </thead>
                    </table>
                    <script type="text/html" id="rowTemplate">
                        <a class="layui-btn layui-btn-danger layui-btn-xs t-remove {{d.status == 0 ? '' : 'layui-hide'}}" data-id="{{d.fileId}}" lay-event="del">移除</a>
                        <label class="t-label layui-hide" data-id="{{d.fileId}}">文本</label>
                        <a class="layui-btn layui-btn-xs t-retry layui-hide" data-id="{{d.fileId}}" lay-event="retry">重试</a>
                    </script>
                    <script type="text/html" id="processTemplate">
                        <div style="margin-top: 5px;" class="layui-progress layui-progress-big" lay-filter="{{d.fileId}}" lay-showPercent="true">
                            <div class="layui-progress-bar layui-bg-blue" lay-percent="0%"></div>
                        </div>
                    </script>`
        let fileBoxE = options.fileBoxEle || fileBoxEle;
        $(fileBoxE).html(fileBox);
        table.init(COMP_TABLE_ID, {
            height: options.height || ('full-' + 500),
            page: options.multiple,
            limit: 50,
            done: function () {
                that.tableHeader = $(`#${COMP_ID} .layui-table-header thead:first`);
                that.tableBody = $(`#${COMP_ID} .layui-table-body tbody:first`);
            },
        });

        upload = webUploader.create({
            // 不压缩image
            resize: false,
            // swf文件路径
            swf: 'uploader/Uploader.swf',
            // 默认文件接收服务端。
            server: options.url,
            pick: {
                id: `#${COMP_ID}`,//指定选择文件的按钮容器，不指定则不创建按钮。注意 这里虽然写的是 id, 不仅支持 id, 还支持 class, 或者 dom 节点。
                innerHTML: btnText,
                multiple: options.multiple //开启文件多选
            },
            fileNumLimit: undefined,//验证文件总数量, 超出则不允许加入队列,默认值：undefined,如果不配置，则不限制数量
            fileSizeLimit: undefined, //1kb=1024*1024,验证文件总大小是否超出限制, 超出则不允许加入队列。
            fileSingleSizeLimit: undefined, //验证单个文件大小是否超出限制, 超出则不允许加入队列。
            threads: 5,
            chunked: true,//是否开启分片上传
            chunkSize: 1024 * 1024 * 5,//默认文件片段 如果要分片，每一片的文件大小
            chunkRetry: 3, // 分片上传失败后的重试次数
            prepareNextFile: false//在上传当前文件时，准备好下一个文件,请设置成false，不然开启文件多选你浏览器会卡死
        });

        if (options.multiple && dirUpload) {
            setTimeout(function () {
                $(`#${COMP_ID} input[type="file"]`).attr('webkitdirectory', '');
            }, 100);
        }
    };

    /**
     * 文件上传事件集合监听
     */
    Class.prototype.events = function () {
        let that = this, option = this.options;
        let iii = true;
        //添加文件之前
        upload.on('beforeFileQueued', function (file) {
            if (file.name.startsWith('.')) {
                return false;
            }
        })

        // 单个文件被添加
        upload.on('fileQueued', function (file) {
            file.nameBak = file.name;
            file.name = dirUpload ? file.source.source.webkitRelativePath : file.name;
            file.status = STATUS.added;
            // 单文件上传先删除已选择的
            const existsFile = rowData[0]
            if (!option.multiple && existsFile) {
                upload.removeFile(existsFile.fileId, true);
                rowData = removeArray(rowData, existsFile.fileId);
            }
            rowData.push({
                fileId: file.id,
                fileName: file.name,
                fileSize: formatFileSize(file.size),
                validateMd5: '0%',
                progress: file.id,
                status: 0,
            });
        });

        // 一批文件被添加
        upload.on('filesQueued', function (files) {
            that.reloadData(rowData);
            element.render('progress');
        });

        //文件从队列移除触发
        upload.on('fileDequeued', function (file) {
        });

        // 文件上传之前触发
        upload.on('uploadStart', function (file) {
            file.status = STATUS.uploading;
            that.setFileStatus(file);
        })

        // 文件的分片上传之前触发
        upload.on('uploadBeforeSend', function (block, data, headers) {
            data.contentType = block.file.type;
            data.chunks = block.file.chunks;
            data.fileMd5 = block.file.fileMd5;
            data.chunkMd5 = block.zoneMd5;
            data.chunkTotal = block.chunks;
            data.chunkIndex = block.chunk;
            data.fileSize = block.total;
            data.startOffset = block.start;
            data.endOffset = block.end;
            data.id = '';
            data.fileName = block.file.nameBak;
            data.fileFullName = block.file.name;

            Object.assign(data, that.formData);
        });

        //当文件上传成功时触发。file {ArchivesFile} File对象, response {Object}服务端返回的数据
        upload.on('uploadSuccess', function (file, res) {
            file.status = STATUS.uploadOK;
            that.setFileStatus(file);
        })

        // 当文件上传出错时触发
        upload.on('uploadError', function (file, errorCode) {
            file.status = STATUS.uploadError;
            that.setFileStatus(file);
        });
        // 不管成功还是失败都触发
        upload.on('uploadComplete', function (file) {

        });

        //所有文件上传成功后
        upload.on('uploadFinished', function (obj) {//成功后
            rowData = [];
            loading.closeAll();
            let {interruptNum, queueNum, uploadFailNum} = upload.getStats();
            option.uploadFinished && option.uploadFinished(uploadFailNum === 0 && interruptNum === 0 && queueNum === 0);
        });

        //监听进度条,更新进度条信息
        upload.on('uploadProgress', function (file, percentage) {
            element.progress(file.id, (percentage * 100).toFixed(0) + '%');
        });

        //文件验证错误
        upload.on('error', function (handler) {
            if (handler === 'F_EXCEED_SIZE') {
                layer.alert('上传的单个太大! ', {icon: 5});
            } else if (handler === 'Q_TYPE_DENIED') {
                layer.alert('不允许上传此类文件!。<br>操作无法进行,如有需求请联系管理员', {icon: 5});
            } else if (handler === 'F_DUPLICATE') {
                //layer.alert('文件重复，已过滤');
            }
            option.error && option.error(handler);
        });

        //移除上传的文件
        table.on(`tool(${COMP_TABLE_ID})`, function (obj) {
            let data = obj.data;
            if (obj.event === 'del') {
                rowData = removeArray(rowData, data.fileId);
                upload.removeFile(data.fileId, true);
                obj.del();
            } else if (obj.event === 'stop') {//暂停、继续
                upload.stop(true);
            } else if (obj.event === 'retry') {
                upload.retry()
            }
        });

        // 通过在原始选择文件按钮上覆盖一个新的按钮，并监听其事件，
        // 点击后弹出loading，并添加鼠标移动事件的监听，
        // 然后触发文件选择的点击事件，并弹出文件选择窗口，此时浏览器处于阻塞状态所有事件不可用
        // 窗口关闭后，事件监听恢复，移动鼠标则会触发事件，并关闭loading
        $(`#${COMP_ID}_2`).on('click', function () {
            loading.show();
            let monitor = function () {
                document.removeEventListener('mousemove', monitor);
                loading.closeAll();
            }
            document.addEventListener('mousemove', monitor, false)
            $(`#${COMP_ID} input[type="file"]`).click();
        })
    };

    Class.prototype.reloadData = function (data) {
        table.reload(COMP_TABLE_ID, {data});
    };

    Class.prototype.register = function () {
        var that = this, options = that.options;

        //监控文件上传的三个时间点(注意：该段代码必须放在WebUploader.create之前)
        //"before-send-file：:所有分块进行上传之前（1.可以计算文件的唯一标记;2.可以判断是否秒传）
        //before-send： 如果分块上传，每个分块上传之前（1.询问后台该分块是否已经保存成功，用于断点续传）
        //after-send-file：所有分块上传成功之后（1.通知后台进行分块文件的合并工作）
        webUploader.Uploader.register({
            "before-send-file": function (file) {//利用md5File（）方法计算文件的唯一标记符
                return that.checkFile(file);
            },
            "before-send": function (block) {
                // 每个分块上传之前调用此函数
                let _block_file = block.file;
                let deferred = webUploader.Deferred();

                let indexes = uploadIndexMap.get(_block_file.fileMd5) || [];
                if (indexes && indexes.includes(block.chunk)) {
                    //分片索引小于已上传的分片索引则不上传
                    deferred.reject();
                } else {
                    block.zoneMd5 = _block_file.fileMd5 + "_" + block.chunk;
                    deferred.resolve();
                }
                return deferred.promise();
            },
            "after-send-file": function (file, res) {
                //时间点3：所有分块上传成功之后调用此函数
                let deferred = webUploader.Deferred();
                // 如果res响应不为空，说明是第一次上传
                if (res) {
                    if (res.flag) {
                        file.status = STATUS.merging;
                        that.setFileStatus(file);
                        let _file = res.data;
                        _file.entityId = that.formData.entityId;
                        _file.fileFullName = file.name;
                        _file.labelStr = that.formData.labelStr
                        if(that.formData.repairId){
                            _file.id = that.formData.repairId;
                        }
                        that.saveFileAndDir(_file, () => deferred.resolve())
                    } else {
                        deferred.reject();
                    }
                } else {
                    // 秒传
                    deferred.resolve()
                }
                return deferred.promise();
            }
        });
    };
    /**
     * 计算文件的MD5值
     * @param file
     * @returns {*}
     */
    Class.prototype.fileMd5 = function (file) {
        //1.计算文件的唯一标记，用于断点续传和秒传,获取文件前20m的md5值，越小越快，防止碰撞
        return upload.md5File(file, 0, 10 * 1024 * 1024);
    }
    /**
     * 检查文件是否上传
     * @param file
     * @returns {*}
     */
    Class.prototype.checkFile = function (file) {
        let that = this, options = that.options, fileCheckUrl = options.fileCheckUrl, deferred = webUploader.Deferred();
        that.fileMd5(file).then(md5Value => {
            file.fileMd5 = md5Value;
            let {fileMd5, source} = file;
            Util.post(fileCheckUrl, {fileMd5, name: ''}, false).then(res => {
                if (res.flag) {
                    if (res.code === 0) {
                        // 文件不存在
                        uploadIndexMap.set(fileMd5, res.data);
                    } else if (res.code === 1) {
                        // 文件存在，秒传
                        let existsFile = res.data;
                        existsFile.entityId = that.formData.entityId;
                        existsFile.fileFullName = file.name;
                        existsFile.fileName = source.name;
                        existsFile.labelStr = that.formData.labelStr;
                        element.progress(file.id, '100%');
                        upload.skipFile(file);
                        that.saveFileAndDir(existsFile);
                    }
                    deferred.resolve();
                } else {
                    showErrorMsg('上传失败，服务器异常')
                    deferred.reject();
                }
            }).catch(err => {
                console.error(err)
                showErrorMsg('上传失败，服务器异常')
                deferred.reject();
            })
        });
        return deferred.promise();
    }

    /**
     * 保存文件和目录数据
     * @param file
     * @param callback
     */
    Class.prototype.saveFileAndDir = function (file, callback) {
        let that = this, options = that.options;
        Util.post(options.fileMergeUrl, file, false).then((res) => {
            that.setFileStatus(file);
            callback && callback(res);
        })
    };
    /**
     * 设置文件上传的状态
     * @param file
     */
    Class.prototype.setFileStatus = function (file) {
        let $labelElem = $(`label.t-label[data-id="${file.id}"]`);
        switch (file.status) {
            case STATUS.added: // 刚加入队列, 默认显示移除按钮
                break;
            case STATUS.ready: // 等待上传
                $labelElem.html('等待上传');
                break
            case STATUS.uploading: // 正在上传
                $labelElem.html('正在上传');
                break;
            case STATUS.merging:
                $labelElem.html('正在合并文件');
                break;
            case STATUS.uploadOK: // 上传成功
                $labelElem.html('上传成功');
                break;
            case STATUS.uploadError: // 上传失败
                $labelElem.html('上传失败');
                $(`td[data-field="oper"] .t-retry[data-id="${file.id}"]`).removeClass('layui-hide')
                break;
            case STATUS.pause: // 上传暂停
                $labelElem.html('暫停');
                break
        }
    };

    Class.prototype.uploadToServer = function () {
        if (rowData.length <= 0) {
            layer.msg('没有上传的文件', {icon: 5});
            return;
        }
        $('td[data-field="oper"] .layui-btn').addClass('layui-hide');
        $(`label.t-label`).removeClass('layui-hide').html('等待上传');
        this.formData = this.options.getFormData() || {};
        loading.show(false);
        upload.upload();
    };

    Class.prototype.getFiles = function () {
        return upload.getFiles();
    }


    const component = {
        render: function (options) {
            return new Class(options);
        },
        startUpload: function () {
            upload.upload();
        },
    };
    exports('uploader', component);
});

/**
 * 从数组中移除元素
 * @param array
 * @param fileId
 * @returns {*}
 */
function removeArray(array, fileId) {
    for (let i = 0; i < array.length; i++) {
        if (array[i].fileId == fileId) {
            array.splice(i, 1);
        }
    }
    return array;
};

/**
 * 格式化文件大小, 输出成带单位的字符串
 * @param {Number} size 文件大小
 * @param {Number} [pointLength=2] 精确到的小数点数。
 * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节，到千字节，一直往上指定。
 * 如果单位数组里面只指定了到了K(千字节)，同时文件大小大于M, 此方法的输出将还是显示成多少K.
 */
function formatFileSize(size, pointLength, units) {
    var unit;
    units = units || ['B', 'K', 'M', 'G', 'TB'];
    while ((unit = units.shift()) && size > 1024) {
        size = size / 1024;
    }
    return (unit === 'B' ? size : size.toFixed(pointLength === undefined ? 2 : pointLength)) + unit;
}


class Loading {
    constructor(layer) {
        this.layer = layer;
        this.index = -1;
        this.countDownLatch = 0;
    }

    show(shade = true) {
        if (this.index === -1) {
            this.index = this.layer.load(0, {shade: shade ? 0.3 : 0});
        }
        this.countDownLatch += 1;
    }

    close() {
        this.countDownLatch -= 1;
        if (this.index !== -1 && this.countDownLatch <= 0) {
            this.layer.close(this.index);
            this.index = -1;
        }
    }

    closeAll() {
        this.layer.close(this.index);
        this.countDownLatch = 0;
        this.index = -1;
    }
}