<template>
  <div>
    <div
      class="bx-uploader-btn"
      v-if="!listStyle"
      >
      <slot name="button" />
      <form
        ref="form"
        class="bx-uploader-btn-form"
        >
        <input
          ref="input"
          type="file"
          class="bx-uploader-btn-input"
          :accept="accept"
          :capture="capture"
          :disabled="disabled"
          @change="onChange"
          >
      </form>
    </div>

    <uploader-list
      ref="uploaderList"
      :disabled="disabled"
      :custom-upload="customUpload"
      :images="images"
      :list-style="listStyle"
      :message="message"
      :max-amount="maxAmount"
      :accept="accept"
      :capture="capture"
      :is-bad-android="isBadAndroid"
      @bad-android-input-change="onChange"
      @click-image="clickImage"
      @trigger="clickInput"
      @delete="deleteImage"
      @preview="preview"
      />
  </div>
</template>
<script>
/* eslint-disable no-param-reassign */
/* eslint-disable no-bitwise */
import Toast from '../toast'
import { create } from '../utils'
import { TASK_STATUS, PROXY_URL, OSS_INFO_LIST } from './static'
import props from './props'
import UploaderList from './uploader-list'
import taskMixins from './task-mixins'
import styleListMixins from './style-list-mixins'
import Emitter from '../mixins/emitter'

export default create({
  name: 'uploader',
  mixins: [taskMixins, styleListMixins, Emitter],
  components: {
    UploaderList,
  },
  props,
  data() {
    return {
      currentKey: null,
      reading: false,
      isBadIos: false, // ios8及以下 canvas不支持旋转，做降级处理
      isBadAndroid: false, // 安卓4.4以下判定为true（不包括4.4）；
      isDevelop: null, // 是否是本地开发环境
      isCustomUploading: false, // 是否正在自定义上传
    }
  },
  model: {
    prop: 'images',
    event: 'change',
  },
  computed: {
    ossInfo() {
      return OSS_INFO_LIST.find(item => item.type === this.ossType) || {}
    },
    authUrl() {
      const hostName = window.location.hostname
      if (
        (/^.*\.com$/.test(hostName) && hostName !== 'test.winbaoxian.com') ||
        /^.*\.net$/.test(hostName)
      ) {
        return this.ossInfo.authUrlProd
      }
      return this.ossInfo.authUrlTest
    },
    message() {
      if (this.errorMessage) {
        return this.errorMessage
      }
      const failedImages = this.images.filter(
        item => item.status === TASK_STATUS.FAILED,
      )
      let message = failedImages.length
        ? `上传失败${failedImages.length}张`
        : null
      if (message && (this.isRows || this.isRow)) {
        message += '，滑动查看更多'
      }
      return message
    },
    maxSizeText() {
      if (this.maxSize !== Number.MAX_VALUE) {
        if (this.maxSize < 1024) {
          return `${this.maxSize}b`
        }
        if (this.maxSize < 1024 * 1024) {
          return `${Math.floor(this.maxSize / 1024)}kb`
        }
        if (this.maxSize < 1024 * 1024 * 1024) {
          return `${Math.floor(this.maxSize / (1024 * 1024))}M`
        }
        return `${Math.floor(this.maxSize / (1024 * 1024 * 1024))}G`
      }
      return null
    },
  },
  created() {
    this.getUserAgentInfo()
    this.setIsDevelop()
  },
  methods: {
    setIsDevelop() {
      if (window.location.port) {
        this.isDevelop = true
      } else {
        this.isDevelop = false
      }

      if (this.isDev === true || this.isDev === false) {
        this.isDevelop = this.isDev
      }
    },
    getUserAgentInfo() {
      const str = navigator.userAgent.toLowerCase()
      const ver = str.match(/cpu iphone os (.*?) like mac os/)
      if (ver && ver[1]) {
        const version = ver[1]
        if (version && Number(version.split('_')[0]) <= 8) {
          this.isBadIos = true
        } else {
          this.isBadIos = false
        }
      }

      if (str.indexOf('android') > 0) {
        const reg = /android [\d._]+/gi
        const vInfo = str.match(reg)
        const versionAndr = vInfo
          .toString()
          .replace(/[^0-9|_.]/gi, '')
          .split('.')
        if (versionAndr && versionAndr.length) {
          if (versionAndr[0] && Number(versionAndr[0]) < 4) {
            this.isBadAndroid = true
          } else if (
            Number(versionAndr[0]) === 4 &&
            Number(versionAndr[1]) < 4
          ) {
            this.isBadAndroid = true
          }
        }
      }
    },
    getSuffix(filename) {
      const pos = filename.lastIndexOf('.')
      let suffix = ''
      if (pos !== -1) {
        suffix = filename.substring(pos)
      }
      return suffix
    },
    onChange(event) {
      let { files } = event.target
      if (this.disabled || !files.length) {
        this.clearInput()
        return
      }

      files = [].slice.call(files, 0)
      if (!files || (this.beforeRead && !this.beforeRead(files))) {
        this.clearInput()
        return
      }

      let oversize = false
      files.forEach(file => {
        if (file.size > this.maxSize) {
          oversize = true
        }
      })
      if (oversize) {
        if (this.oversize) {
          this.oversize(files, this.maxSize)
        } else {
          Toast(`图片不得大于${this.maxSizeText}`)
        }
        this.clearInput()
        return
      }

      if (
        !this.staticKey &&
        files.length + this.images.length > this.maxAmount
      ) {
        if (this.overAmount) {
          this.overAmount(this.maxAmount, files.length)
        } else {
          Toast(`最多可传${this.maxAmount}张`)
        }
        this.clearInput()
        return
      }

      this.reading = true

      Promise.all(
        files.map(this.handleFile ? this.customReadFile : this.readFile),
      )
        .then(result => {
          this.reading = false
          const payload = files.map((file, index) => {
            const { fileInfo, orientation = null } = result[index]
            if (orientation === -1 || orientation === -2 || orientation === 1) {
              // 如果不用旋转，则把orientation设置为null
              fileInfo.orientation = null
            } else {
              fileInfo.orientation = orientation
            }
            return {
              file: files[index],
              newFile: this.handleFile ? fileInfo.file : null,
              content: this.handleFile
                ? fileInfo.content
                : this.getCanvasDataUrl(fileInfo),
              currentKey: fileInfo.currentKey,
              naturalWidth: fileInfo.naturalWidth,
              naturalHeight: fileInfo.naturalHeight,
              orientation,
            }
          })

          this.onAfterRead(payload)
        })
        .catch(() => {
          this.reading = false
        })
      this.dispatch('bxs-form-item', 'form.item.reset')
    },
    readFile(file) {
      return Promise.all([
        this.getFileInfo(file),
        this.getOrientation(file),
      ]).then(([fileInfo, orientation]) => ({ fileInfo, orientation }))
    },
    customReadFile(file) {
      return new Promise((resolve, reject) => {
        this.handleFile(file).then(res => {
          const reader = new FileReader()
          reader.onload = event => {
            resolve({
              fileInfo: {
                file: res,
                currentKey: this.currentKey,
                content: event.target.result,
              },
            })
          }
          reader.onerror = event => {
            reject(event)
          }
          reader.readAsDataURL(res)
        })
      })
    },
    getOrientation(file) {
      return new Promise((resolve, reject) => {
        if (this.isBadIos || this.isBadAndroid) {
          resolve(null)
        }

        try {
          const reader = new FileReader()
          reader.onload = function(e) {
            const view = new DataView(e.target.result)
            if (view.getUint16(0, false) !== 0xffd8) {
              resolve(null) // 不是jpeg
            }
            const length = view.byteLength
            let offset = 2
            while (offset < length) {
              if (view.getUint16(offset + 2, false) <= 8) {
                resolve(-1) // 不包含旋转信息
              }
              const marker = view.getUint16(offset, false)
              offset += 2
              if (marker === 0xffe1) {
                offset += 2
                if (view.getUint32(offset, false) !== 0x45786966) {
                  resolve(null) // 不包含旋转信息
                }
                const little = view.getUint16((offset += 6), false) === 0x4949
                offset += view.getUint32(offset + 4, little)
                const tags = view.getUint16(offset, little)
                offset += 2
                for (let i = 0; i < tags; i += 1) {
                  if (view.getUint16(offset + i * 12, little) === 0x0112) {
                    resolve(view.getUint16(offset + i * 12 + 8, little))
                  }
                }
              } else if ((marker & 0xff00) !== 0xff00) {
                break
              } else {
                offset += view.getUint16(offset, false)
              }
            }
            resolve(null) // 不包含旋转信息
          }
          reader.readAsArrayBuffer(file)
        } catch (e) {
          reject(e)
        }
      })
    },
    getFileInfo(file) {
      return new Promise((resolve, reject) => {
        const self = this
        if (window.URL && (this.isBadIos || this.isBadAndroid)) {
          const url = window.URL.createObjectURL(file)
          resolve({
            file,
            currentKey: self.currentKey,
            content: url,
          })
        } else {
          const reader = new FileReader()
          reader.onload = event => {
            if (this.isBadIos || this.isBadAndroid) {
              resolve({
                file,
                currentKey: self.currentKey,
              })
            } else {
              const img = new Image()
              img.onload = function() {
                const { naturalWidth, naturalHeight } = this
                resolve({
                  file,
                  img,
                  currentKey: self.currentKey,
                  content: event.target.result,
                  naturalWidth,
                  naturalHeight,
                })
              }
              img.src = event.target.result
            }
          }

          reader.onerror = event => {
            reject(event)
          }

          reader.readAsDataURL(file)
        }
      })
    },
    onAfterRead(files) {
      if (this.beforeUpload) {
        const result = this.beforeUpload(files)
        if (!result) {
          this.clearInput()
          return
        }
      }
      if (this.staticKey) {
        files[0].key = this.currentKey
      }
      this.addTasks(files)

      this.getAuthInfo()
        .then(authInfo => {
          this.tasks.forEach(task => {
            if (task.status === TASK_STATUS.READY) {
              this.taskStart(task)
              this.submit(task, authInfo)
            }
          })
        })
        .catch(() => {
          this.taskFailed()
        })
      this.clearInput()
    },
    clearInput() {
      if (this.$refs.form) {
        this.$refs.form.reset()
      }
      if (this.$refs.uploaderList) {
        this.$refs.uploaderList.clearInput()
      }
    },
    getAuthInfo() {
      const promise = new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()
        xhr.timeout = this.timeout
        xhr.open('GET', `${this.authUrl}?fileDir=${this.fileDir}`, true)
        xhr.onload = e => {
          if (e.currentTarget.status === 200) {
            try {
              const data = JSON.parse(xhr.responseText)
              resolve(data.data)
            } catch (error) {
              reject(error)
            }
          } else {
            reject(e)
          }
        }
        xhr.onerror = event => {
          reject(event)
        }
        xhr.onabort = event => {
          reject(event)
        }
        xhr.ontimeout = event => {
          reject(event)
        }
        xhr.send()
      })
      return promise
    },
    submit(task, authInfo) {
      task.host = authInfo.host
      const key = `${authInfo.dir}${new Date().getTime()}_${this.randomString(
        15,
      )}${this.getSuffix(task.file.name)}`
      const formData = new FormData()
      formData.append('success_action_status', 200)
      formData.append('signature', authInfo.signature)
      formData.append('policy', authInfo.policy)
      formData.append('OSSAccessKeyId', authInfo.accessid)
      formData.append('key', key)
      const protocol = authInfo.cdnMediaHost.indexOf('//') === 0 ? 'https:' : ''
      task.imageUrl = protocol + authInfo.cdnMediaHost + key

      formData.append(
        'file',
        this.handleFile ? task.newFile : this.getBlobByDataURI(task),
      )
      task.formData = formData
      this.upload(task)
    },
    upload(task) {
      const xhr = task.xhr || new XMLHttpRequest()
      xhr.timeout = this.timeout
      xhr.onload = e => {
        if (e.currentTarget.status === 200) {
          this.taskSuccess(task, xhr)
        } else {
          this.taskFailed(task)
        }
      }
      xhr.onerror = () => {
        this.taskFailed(task)
      }
      xhr.ontimeout = () => {
        this.taskFailed(task)
      }
      const uploadUrl = this.isDevelop
        ? `${PROXY_URL}?host=${encodeURI(task.host)}`
        : task.host
      try {
        xhr.open('POST', uploadUrl, true)
        xhr.send(task.formData)
      } catch (e) {
        this.taskFailed(task)
      }
    },
    getCanvasDataUrl({
      file,
      img,
      orientation,
      naturalWidth,
      naturalHeight,
      content,
    }) {
      if (!orientation || this.isBadIos || this.isBadAndroid) {
        return content
      }
      const canvas = document.createElement('canvas')
      const context = canvas.getContext('2d')
      canvas.width = naturalWidth
      canvas.height = naturalHeight
      let drawWidth = naturalWidth
      let drawHeight = naturalHeight
      let degree = 0

      switch (orientation) {
        case 3:
          degree = 180
          drawWidth = -naturalWidth
          drawHeight = -naturalHeight
          break
        case 6:
          canvas.width = naturalHeight
          canvas.height = naturalWidth
          degree = 90
          drawWidth = naturalWidth
          drawHeight = -naturalHeight
          break
        case 8:
          canvas.width = naturalHeight
          canvas.height = naturalWidth
          degree = 270
          drawWidth = -naturalWidth
          drawHeight = naturalHeight
          break
        default:
          break
      }

      context.rotate((degree * Math.PI) / 180)
      context.drawImage(img, 0, 0, drawWidth, drawHeight)
      return canvas.toDataURL(file.type, 0.92)
    },
    getBlobByDataURI({ content, file }) {
      if (this.isBadIos || this.isBadAndroid) {
        return file
      }
      const binary = atob(content.split(',')[1])
      const array = []
      for (let i = 0; i < binary.length; i += 1) {
        array.push(binary.charCodeAt(i))
      }
      return new Blob([new Uint8Array(array)], { type: file.type })
    },
    clickInput(index) {
      if (this.reading) {
        return
      }
      if (this.staticKey) {
        const image = this.images[index]
        if (!image.key) {
          image.key = this.randomString(15)
        }
        this.currentKey = image.key
      } else {
        this.currentKey = null
      }

      if (this.customUpload && !this.disabled && !this.isCustomUploading) {
        this.isCustomUploading = true
        setTimeout(() => {
          this.isCustomUploading = false
        }, 1000)
        const cu = this.customUpload()
        if (cu) {
          if (typeof cu.then === 'function') {
            cu.then(res => {
              this.addSuccessImage(res)
            })
          } else {
            this.addSuccessImage(cu)
          }
        }
      }
    },
    clickImage(index) {
      const image = this.images[index]
      if (image.url) {
        this.preview(index)
      } else if (!image.status) {
        this.clickInput(index)
      }
    },
    deleteImage(index) {
      const image = this.images[index]
      this.removeTask(image.key)
      if (this.staticKey) {
        image.url = ''
        image.status = null
        image.orientation = null
        image.naturalWidth = null
        image.naturalHeight = null
        image.content = null
        this.$set(this.images, index, image)
      } else {
        this.images.splice(index, 1)
      }
      this.$emit('delete', {
        image,
        images: this.images,
      })
      this.dispatch('bxs-form-item', 'form.item.validate')
    },
    preview(index) {
      if (this.images[index].status) {
        return
      }
      this.$emit('preview', {
        url: this.images[index].url,
        index,
        images: this.images,
      })
    },
  },
})
</script>
<style>
@import './index.css';
</style>
