<template>
    <el-upload ref="uploadRef" class="hc-file-upload-box"  :headers="getTokenHeader()" :data="uploadData" :disabled="uploadDisabled" multiple :show-file-list="false" :http-request="uploadFileHandle"
               :on-success="uploadSuccess" :on-exceed="uploadExceed" :on-error="uploadError" :before-upload="beforeUpload" :on-progress="uploadprogress">
        <slot></slot>
    </el-upload>
</template>

<script setup>
import {ref,watch,onMounted} from "vue";
import {getTokenHeader} from '~src/api/request/header';
import {isSize, deepClone, getObjValue} from "vue-utils-plus"
import md5 from 'js-md5' //引入MD5加密
import ossApi from "~api/oss";
const props = defineProps({
    datas: {
        type: Object,
        default: () => ({})
    },
    api: {
        type: String,
        default: "/api/blade-resource/oss/endpoint/"
    },
    action: {
        type: String,
        default: "upload-file2"
    },
    accept: {
        type: String,
        default: "image/png,image/jpg,image/jpeg,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel,application/pdf,.doc,.docx,application/msword"
    },
    size: {
        type: Number,
        default: 60
    }
})

//变量
const uploadRef = ref(null)
const uploadData = ref(props.datas)
const uploadDisabled = ref(false)

//监听
watch(() => [
    props.datas,
], ([datas]) => {
    uploadData.value = datas
})

//渲染完成
onMounted(()=> {
    beforeFileNum.value = 0
    finishFileNum.value = 0
    errorFileNum.value = 0
})

//事件
const emit = defineEmits(['change', 'progress'])

//上传前
const beforeFileNum = ref(0)
const beforeUpload = async (file) => {
    beforeFileNum.value ++;
    return true;
}

//超出限制时
const uploadExceed = () => {
    window?.$message?.warning(`请上传 ${props.accept} 格式的文件，文件大小不超过${props.size}M`);
}

//上传中
const uploadprogress = () => {
    uploadDisabled.value = true
    emit('progress', true)
}

//上传完成
const finishFileNum = ref(0)
const uploadSuccess = (response, uploadFile, uploadFiles) => {
  //console.log(response)
  //console.log(uploadFile)
  //console.log(uploadFiles)
    finishFileNum.value ++;
    if (beforeFileNum.value === finishFileNum.value) {
        const fileList = getUploadFile(deepClone(uploadFiles))
        uploadClearFiles()
        emit('change', {type: 'success', fileList})
        emit('progress', false)
    }
}

//上传失败
const errorFileNum = ref(0)
const uploadError = (error,uploadFile,uploadFiles) => {
    errorFileNum.value ++;
    window?.$message?.error('上传失败');
    console.log(error)
    const num = finishFileNum.value + errorFileNum.value;
    if (beforeFileNum.value === num) {
        const fileList = getUploadFile(deepClone(uploadFiles))
        uploadClearFiles()
        emit('change', {type: 'error', fileList})
        emit('progress', false)
    }
}

const uploadClearFiles = () => {
    finishFileNum.value = 0
    beforeFileNum.value = 0
    errorFileNum.value = 0
    uploadDisabled.value = false
    uploadRef.value?.clearFiles()
}

//获取文件
const getUploadFile = (fileList) => {
    let fileArr = [];
    for (let i = 0; i < fileList.length; i++) {
        const item = getObjValue(fileList[i]?.response?.data)
        fileArr.push(item)
    }
    return fileArr
}

const uploadFileHandle = (options) =>{
    console.log(options)
    uploadByPieces(options)
}

/**
 * 文件分片上传
 * @params file {File} 文件
 * @params pieceSize {Number} 分片大小 默认3MB
 * @params concurrent {Number} 并发数量 默认2
 * @params process {Function} 进度回调函数
 * @params success {Function} 成功回调函数
 * @params error {Function} 失败回调函数
 */
 const uploadByPieces = ({
  file,
  pieceSize = 5,
  concurrent = 3,
  onSuccess:success,
  onProgress:process,
  onError:error
}) => {
  // 如果文件传入为空直接 return 返回
  if (!file || file.length < 1) {
    return error('文件不能为空')
  }
  let fileMD5 = '' // 总文件列表
  const chunkSize = pieceSize * 1024 * 1024 // 1MB一片
  const chunkCount = Math.ceil(file.size / chunkSize) // 总片数
  const chunkList = [] // 分片列表
  let uploaded = [] // 已经上传的
  let fileType = '' // 文件类型
  // 获取md5
  /***
   * 获取md5
   **/
  const readFileMD5 = () => {
    // 读取视频文件的md5
    fileType = file.name.substring(file.name.lastIndexOf('.') + 1, file.name.length)
    console.log('获取文件的MD5值')
    let fileRederInstance = new FileReader()
    console.log('file', file)
    fileRederInstance.readAsBinaryString(file)
    fileRederInstance.addEventListener('load', e => {
      let fileBolb = e.target.result
      fileMD5 = md5(fileBolb)
      var index = file.name.lastIndexOf('.')
      var tp = file.name.substring(index + 1, file.name.length)
      let form = new FormData()
      form.append('filename', file.name)
      //form.append('file', new File([], 'filename'))
      form.append('identifier', fileMD5)
      form.append('objectType', fileType)
      form.append('chunkNumber', 1)
      ossApi.uploadChunk(form).then(res => {
        if (res.skipUpload) {
          console.log('文件已被上传')
          success && success(res)
        } else {
          // 判断是否是断点续传
          if (res.uploaded && res.uploaded.length != 0) {
            uploaded = [].concat(res.uploaded)
          }
          console.log('已上传的分片：' + uploaded)
          // 判断是并发上传或顺序上传
          if (concurrent == 1 || chunkCount == 1) {
            console.log('顺序上传')
            sequentialUplode(0)
          } else {
            console.log('并发上传')
            concurrentUpload()
          }
        }
      }).catch((e) => {
        console.log('文件合并错误')
        console.log(e)
      })
    })
  }
  /***
   * 获取每一个分片的详情
   **/
  const getChunkInfo = (file, currentChunk, chunkSize) => {
    let start = currentChunk * chunkSize
    let end = Math.min(file.size, start + chunkSize)
    let chunk = file.slice(start, end)
    return {
      start,
      end,
      chunk
    }
  }
  /***
   * 针对每个文件进行chunk处理
   **/
  const readChunkMD5 = () => {
    // 针对单个文件进行chunk上传
    for (var i = 0; i < chunkCount; i++) {
      const {
        chunk
      } = getChunkInfo(file, i, chunkSize)

      // 判断已经上传的分片中是否包含当前分片
      if (uploaded.indexOf(i + '') == -1) {
        uploadChunk({
          chunk,
          currentChunk: i,
          chunkCount
        })
      }
    }
  }
  /***
   * 原始上传
   **/
  const uploadChunk = (chunkInfo) => {
    var sd = parseInt((chunkInfo.currentChunk / chunkInfo.chunkCount) * 100)
    console.log(sd, '进度')
    process(sd)
    console.log(chunkInfo, '分片大小')
    let inde = chunkInfo.currentChunk + 1
    if (uploaded.indexOf(inde + '') > -1) {
      const {
        chunk
      } = getChunkInfo(file, chunkInfo.currentChunk + 1, chunkSize)
      uploadChunk({
        chunk,
        currentChunk: inde,
        chunkCount
      })
    } else {
      var index = file.name.lastIndexOf('.')
      var tp = file.name.substring(index + 1, file.name.length)
      // 构建上传文件的formData
      let fetchForm = new FormData()
      fetchForm.append('identifier', fileMD5)
      fetchForm.append('chunkNumber', chunkInfo.currentChunk + 1)
      fetchForm.append('chunkSize', chunkSize)
      fetchForm.append('currentChunkSize', chunkInfo.chunk.size)
      const chunkfile = new File([chunkInfo.chunk], file.name)
      fetchForm.append('file', chunkfile)
      // fetchForm.append('file', chunkInfo.chunk)
      fetchForm.append('filename', file.name)
      fetchForm.append('relativePath', file.name)
      fetchForm.append('totalChunks', chunkInfo.chunkCount)
      fetchForm.append('totalSize', file.size)
      fetchForm.append('objectType', tp)
      // 执行分片上传
      let config = {
        headers: {
          'Content-Type': 'application/json',
          'Accept': '*/*'
        }
      }

      ossApi.uploadChunk(fetchForm, config).then(res => {

        if (res.code == 200) {
          console.log('分片上传成功')
          uploaded.push(chunkInfo.currentChunk + 1)
          // 判断是否全部上传完
          if (uploaded.length == chunkInfo.chunkCount) {
            console.log('全部完成')
            success(res)
            process(100)
          } else {
            const {
              chunk
            } = getChunkInfo(file, chunkInfo.currentChunk + 1, chunkSize)
            uploadChunk({
              chunk,
              currentChunk: chunkInfo.currentChunk + 1,
              chunkCount
            })
          }

        } else {
          console.log(res.msg)
        }

      }).catch((e) => {
        error && error(e)
      })
      // if (chunkInfo.currentChunk < chunkInfo.chunkCount) {
      //   setTimeout(() => {
      //
      //   }, 1000)
      // }
    }
  }
  /***
   * 顺序上传
   **/
  const sequentialUplode = (currentChunk) => {
    const {
      chunk
    } = getChunkInfo(file, currentChunk, chunkSize)
    let chunkInfo = {
      chunk,
      currentChunk,
      chunkCount
    }
    var sd = parseInt((chunkInfo.currentChunk / chunkInfo.chunkCount) * 100)
    process(sd)
    console.log('当前上传分片：' + currentChunk)
    let inde = chunkInfo.currentChunk + 1
    if (uploaded.indexOf(inde + '') > -1) {
      console.log('分片【' + currentChunk + '】已上传')
      sequentialUplode(currentChunk + 1)
    } else {
      let uploadData = createUploadData(chunkInfo)
      let config = {
        headers: {
          'Content-Type': 'application/json',
          'Accept': '*/*'
        }
      }
      // 执行分片上传
      ossApi.uploadChunk(uploadData, config).then(res => {
        if (res.code == 200) {
          console.log('分片【' + currentChunk + '】上传成功')
          uploaded.push(chunkInfo.currentChunk + 1)
          // 判断是否全部上传完
          if (uploaded.length == chunkInfo.chunkCount) {
            console.log('全部完成')
            success(res)
            process(100)
          } else {
            sequentialUplode(currentChunk + 1)
          }

        } else {
          console.log(res.msg)
        }

      }).catch((e) => {
        error && error(e)
      })
    }
  }
  /***
   * 并发上传
   **/
  const concurrentUpload = () => {
    for (var i = 0; i < chunkCount; i++) {
      chunkList.push(Number(i))
    }
    console.log('需要上传的分片列表：' + chunkList)
    concurrentExecution(chunkList, concurrent, (curItem) => {
      return new Promise((resolve, reject) => {
        const {
          chunk
        } = getChunkInfo(file, curItem, chunkSize)
        let chunkInfo = {
          chunk,
          currentChunk: curItem,
          chunkCount
        }
        var sd = parseInt((chunkInfo.currentChunk / chunkInfo.chunkCount) * 100)
        process(sd)
        console.log('当前上传分片：' + curItem)
        let inde = chunkInfo.currentChunk + 1
        if (uploaded.indexOf(inde + '') == -1) {
          // 构建上传文件的formData
          let uploadData = createUploadData(chunkInfo)
          // 请求头
          let config = {
            headers: {
              'Content-Type': 'application/json',
              'Accept': '*/*'
            }
          }
          ossApi.uploadChunk(uploadData, config).then(res => {
            if (res.code == 200) {
              uploaded.push(chunkInfo.currentChunk + 1)
              console.log('已经上传完成的分片：' + uploaded)
              // 判断是否全部上传完
              // if (uploaded.length == chunkInfo.chunkCount) {
              //   success(res)
              //   process(100)
              // }
              if(typeof res.data == 'object'){
                success(res)
                process(100)
              }
              resolve()
            } else {
              reject(res)
              console.log(res.msg)
            }

          }).catch((e) => {
            reject(res)
            error && error(e)
          })
        } else {
          console.log('分片【' + chunkInfo.currentChunk + '】已上传')
          resolve()
        }
      })
    }).then(res => {
      console.log('finish', res)
    }).catch((e)=>{
      error && error(e)
    })
  }
  /***
   * 创建文件上传参数
   **/
  const createUploadData = (chunkInfo) => {
    let fetchForm = new FormData()
    fetchForm.append('identifier', fileMD5)
    fetchForm.append('chunkNumber', chunkInfo.currentChunk + 1)
    fetchForm.append('chunkSize', chunkSize)
    fetchForm.append('currentChunkSize', chunkInfo.chunk.size)
    const chunkfile = new File([chunkInfo.chunk], file.name)
    fetchForm.append('file', chunkfile)
    // fetchForm.append('file', chunkInfo.chunk)
    fetchForm.append('filename', file.name)
    fetchForm.append('relativePath', file.name)
    fetchForm.append('totalChunks', chunkInfo.chunkCount)
    fetchForm.append('totalSize', file.size)
    fetchForm.append('objectType', fileType)
    return fetchForm
  }

  readFileMD5() // 开始执行代码
  
}
/**
 * 并发执行
 * @params list {Array} - 要迭代的数组
 * @params limit {Number} - 并发数量控制数,最好小于3
 * @params asyncHandle {Function} - 对`list`的每一个项的处理函数，参数为当前处理项，必须 return 一个Promise来确定是否继续进行迭代
 * @return {Promise} - 返回一个 Promise 值来确认所有数据是否迭代完成
 */
const  concurrentExecution = (list, limit, asyncHandle)=>{
  // 递归执行
  let recursion = (arr) => {
    // 执行方法 arr.shift() 取出并移除第一个数据
    return asyncHandle(arr.shift()).then(() => {
      // 数组还未迭代完，递归继续进行迭代
      if (arr.length !== 0) {
        return recursion(arr)
      } else {
        return 'finish'
      }
    })
  }
  // 创建新的并发数组
  let listCopy = [].concat(list)
  // 正在进行的所有并发异步操作
  let asyncList = []
  limit = limit > listCopy.length ? listCopy.length : limit
  console.log(limit)
  while (limit--) {
    asyncList.push(recursion(listCopy))
  }
  // 所有并发异步操作都完成后，本次并发控制迭代完成
  return Promise.all(asyncList)
}

</script>

<style lang="scss">
.hc-file-upload-box .el-upload-list .el-upload-list__item {
    .el-upload-list__item-status-label, .el-icon--close-tip {
        display: none;
    }
}
</style>
