import { getActualPoint, getClickPoint, createSvg, createElement } from '../annotation/utils';
import { onClickOutsideUp, setCss, isMobileDevice } from '../ui_utils';

export class ImageEditor {
  constructor({
    eventBus,
    contentContainer,
    container,
    pagePtr,
    editPagePtr,
    editAreaPtr,
    editAreaIndex,
    viewport,
    scale,
    messageHandler,
    newAdd,
    pageViewer,
    $t,
    hidden,
    isUpdate = false
  }) {
    this.eventBus = eventBus
    this.contentContainer = contentContainer
    this.container = container
    this.pagePtr = pagePtr
    this.editPagePtr = editPagePtr
    this.editAreaPtr = editAreaPtr
    this.editAreaIndex = editAreaIndex
    this.viewport = viewport
    this.scale = scale
    this.messageHandler = messageHandler
    this.newAdd = newAdd
    this.pageViewer = pageViewer
    this.$t = $t
    this.hidden = hidden
    this.isUpdate = isUpdate

    this.type = 'image'
    this.deleteSvgStr = `<rect width="30" height="30" rx="2" fill="#DDE9FF"/>
      <path fill-rule="evenodd" clip-rule="evenodd" d="M19 6.5V9.5H24V10.5H21.5V23.5H8.5V10.5H6V9.5H11V6.5H19ZM9.5 10.5V22.5H20.5V10.5H9.5ZM18 7.5V9.5H12V7.5H18ZM13.5 13V20H12.5V13H13.5ZM17.5 20V13H16.5V20H17.5Z" fill="#333333"/>`
    this.frame = null
    this.borderWidth = 2
    this.pointWidth = 6
    this.pointHeight = 6
    this.activeCharPlace = null
    this.selectedRects = null
    this.selectedCharRange = null
    this.state = 0 // 0 未选中；1 已选中；2 裁剪状态
    this.mouseDown = false
    this.moving = false
    this.ratio = window.devicePixelRatio || 1
    this.opacity = 1
    this.imageRatio = 1
    this.imageUrl = null
    this.cropped = false
    this.removed = false
    this.listener = null

    this.start = null
    this.end = null
    this.newStart = null
    this.newEnd = null
    this.needUndoRec = false // 执行操作后，需要添加撤销记录

    this.mousedown = isMobileDevice ? 'touchstart' : 'mousedown'
    this.mouseup = isMobileDevice ? 'touchend' : 'mouseup'
    this.mousemove = isMobileDevice ? 'touchmove' : 'mousemove'

    this.onMousedown = this.handleMouseDown.bind(this)
    this.onMouseup = this.handleMouseUp.bind(this)
    this.onMousemove = this.handleMouseMove.bind(this)
    this.onHandlePropertyPanelChanged = this.handlePropertyPanelChanged.bind(this)
    this.onDelete = this.handleDelete.bind(this)
    this.onShowContentEditorType = this.handleShow.bind(this)

    this.render()
  }

  async render () {
    this.eventBus._on('imagePropertyChanged', this.onHandlePropertyPanelChanged)
    this.eventBus._on('showContentEditorType', this.onShowContentEditorType)

    if (this.isUpdate) {
      await this.updateCanvas()
    } else {
      await this.getRect()
    }

    this.opacity = await this.messageHandler.sendWithPromise('GetImageTransparency', this.editAreaPtr)

    let frameContainer = createElement(
      'div',
      {
        position: 'absolute',
        left: this.rect.left + 'px',
        top: this.rect.top + 'px',
        width: this.rect.width + 'px',
        height: this.rect.height + 'px',
        display: this.hidden ? 'none' : 'block'
      },
      {
        class: 'frame-container',
        index: this.editAreaIndex
      }
    )
    this.frameContainer = frameContainer
    this.container?.append(this.frameContainer)

    let imageContainer = createElement(
      'div',
      {
        position: 'absolute',
        left: 0,
        top: 0,
        width: '100%',
        height: '100%',
        borderWidth: this.borderWidth + 'px',
        borderColor: '#1460F3',
        cursor: 'default'
      },
      {
        class: 'image-container',
      }
    )
    this.imageContainer = imageContainer
    this.frameContainer.append(this.imageContainer)

    this.imageContainer.addEventListener(this.mousedown, this.onMousedown)
    this.imageContainer.addEventListener(this.mouseup, this.onMouseup)

    this.outerLineContainer = document.createElement('div')
    this.outerLineContainer.className = 'outline-container'
    this.outerLineContainer.style.position = 'absolute'

    this.deletetButton = createSvg(
      "svg",
      {
        width: "30",
        height: "30",
        viewBox: "0 0 30 30",
        fill: "none",
        class: 'delete-button'
      },
      {
        position: 'absolute',
        width: '30px',
        height: '30px',
        cursor: 'pointer',
        zIndex: 2
      }
    )
    this.deletetButton.addEventListener('click', this.onDelete)
    this.deletetButton.innerHTML = this.deleteSvgStr

    const left = (this.rect.left + this.rect.width - 30) + 'px'
    const top = (this.rect.top + this.rect.height + 8) + 'px'
    this.deletetButton.style.left = left
    this.deletetButton.style.top = top

    const outerLine = createSvg('svg', {
      class: 'outerline'
    }, {
      position: 'absolute',
      left: this.rect.left - this.borderWidth + 'px',
      top: this.rect.top - this.borderWidth + 'px',
      width: this.rect.width + this.borderWidth + this.pointWidth / 2 + 'px',
      height: this.rect.height + this.borderWidth + this.pointHeight / 2 + 'px',
      zIndex: 2
    })

    this.outerLine = outerLine

    this.moveRect = createSvg(
      "rect",
      {
        class: "move",
        'data-id': "move",
        stroke: "#1460F3",
        'stroke-width': this.borderWidth,
        'fill-opacity': 0,
        width: this.rect.width - this.borderWidth,
        height: this.rect.height - this.borderWidth,
        x: this.pointWidth / 2,
        y: this.pointHeight / 2
      }
    );

    this.topLeftRect = createSvg(
      "rect",
      {
        class: "nw-resize",
        'data-id': "nw-resize",
        fill: "#1460F3",
        stroke: "none",
        width: this.pointWidth,
        height: this.pointHeight,
        x: 0,
        y: 0
      }
    );

    this.LeftRect = createSvg(
      "rect",
      {
        class: "w-resize",
        'data-id': "w-resize",
        fill: "#1460F3",
        stroke: "none",
        width: this.pointWidth,
        height: this.pointHeight,
        x: 0,
        y: (this.rect.height - this.pointHeight + this.borderWidth) / 2
      }
    );

    this.bottomLeftRect = createSvg(
      "rect",
      {
        class: "sw-resize",
        'data-id': "sw-resize",
        fill: "#1460F3",
        stroke: "none",
        width: this.pointWidth,
        height: this.pointHeight,
        x: 0,
        y: this.rect.height - this.pointHeight / 2 + this.borderWidth / 2
      }
    );

    this.bottomRect = createSvg(
      "rect",
      {
        class: "s-resize",
        'data-id': "s-resize",
        fill: "#1460F3",
        stroke: "none",
        width: this.pointWidth,
        height: this.pointHeight,
        x: (this.rect.width - this.pointWidth + this.borderWidth) / 2,
        y: this.rect.height - this.pointHeight / 2 + this.borderWidth / 2
      }
    );
    
    this.bottomRightRect = createSvg(
      "rect",
      {
        class: "se-resize",
        'data-id': "se-resize",
        fill: "#1460F3",
        stroke: "none",
        width: this.pointWidth,
        height: this.pointHeight,
        x: this.rect.width - this.pointWidth / 2 + this.borderWidth / 2,
        y: this.rect.height - this.pointHeight / 2 + this.borderWidth / 2
      }
    );

    this.rightRect = createSvg(
      "rect",
      {
        class: "e-resize",
        'data-id': "e-resize",
        fill: "#1460F3",
        stroke: "none",
        width: this.pointWidth,
        height: this.pointHeight,
        x: this.rect.width - this.pointWidth / 2 + this.borderWidth / 2,
        y: (this.rect.height - this.pointHeight + this.borderWidth) / 2
      }
    );

    this.topRightRect = createSvg(
      "rect",
      {
        class: "ne-resize",
        'data-id': "ne-resize",
        fill: "#1460F3",
        stroke: "none",
        width: this.pointWidth,
        height: this.pointHeight,
        x: this.rect.width - this.pointWidth / 2 + this.borderWidth / 2,
        y: 0
      }
    );

    this.topRect = createSvg(
      "rect",
      {
        class: "n-resize",
        'data-id': "n-resize",
        fill: "#1460F3",
        stroke: "none",
        width: this.pointWidth,
        height: this.pointHeight,
        x: (this.rect.width - this.pointWidth + this.borderWidth) / 2,
        y: 0
      }
    );

    this.outerLine.append(this.moveRect)
    this.outerLine.append(this.topLeftRect)
    this.outerLine.append(this.LeftRect)
    this.outerLine.append(this.bottomLeftRect)
    this.outerLine.append(this.bottomRect)
    this.outerLine.append(this.bottomRightRect)
    this.outerLine.append(this.rightRect)
    this.outerLine.append(this.topRightRect)
    this.outerLine.append(this.topRect)
    this.outerLineContainer.append(this.outerLine)
    this.outerLineContainer.append(this.deletetButton)

    this.outerLine.addEventListener(this.mousedown, this.onMousedown)
    this.outerLine.addEventListener(this.mouseup, this.onMouseup)

    if (this.newAdd) {
      this.goEditing()
      this.addUndoHistory()
      this.newAdd = false
    }
  }

  getActualRect (viewport, s, frame) {
    const { left: x1, top: y1, right: x2, bottom: y2 } = frame
    
    const start = getActualPoint(
      {
        x: x1,
        y: y1
      },
      viewport,
      s
    )
    const end = getActualPoint(
      {
        x: x2,
        y: y2
      },
      viewport,
      s
    )
    return {
      start,
      end
    }
  }

  rectCalc (start, end, padding = 5) {
    return {
      top: (start.y < end.y ? start.y : end.y) - padding,
      left: (start.x < end.x ? start.x : end.x) - padding,
      width: Math.abs(end.x - start.x) + padding * 2,
      height: Math.abs(end.y - start.y) + padding * 2,
    }
  }
  
  getInitialPoint (start, end) {
    const s = this.scale
    const startX = start.x
    const startY = start.y
    const endX = end.x
    const endY = end.y

    const { width, height, rotation } = this.viewport;
    let x1, y1, x2, y2;
    if (rotation === 0) {
      x1 = startX / s;
      y1 = startY / s; 
      x2 = endX / s;
      y2 = endY / s;
    } else if (rotation === 90) {
      x1 = startY / s;
      y1 = startX / s;
      x2 = endY / s;
      y2 = endX / s;
    } else if (rotation === 180) {
      x1 =( width - startX) / s;
      y1 = startY / s;
      x2 = (width - endX) / s;
      y2 = endY / s;
    } else {
      x1 = (height - startY) / s
      y1 = (width - startX) / s
      x2 = (height - endY) / s
      y2 = (width - endX)  / s
    }
    return {
      start: {
        x: x1,
        y: y1,
      },
      end: {
        x: x2,
        y: y2,
      },
    };
  }

  handleMouseDown (e) {
    e.preventDefault()
    if (e.button === 2 || this.hidden) return // 右键点击不执行

    if (this.state > 0) {
      const operatorId = e.target.getAttribute('data-id')
    
      const { pageX, pageY } = getClickPoint(e)
      this.startState = {
        operator: operatorId,
        clickX: pageX,
        clickY: pageY,
      }

      document.addEventListener(this.mousemove, this.onMousemove)
    }

    document.addEventListener(this.mouseup, this.onMouseup)

    this.selectedCharRange = null
    this.mouseMoved = false
    this.mouseDown = true
  }

  async handleMouseUp (e) {
    if (e && !this.frameContainer.contains(e.target) && this.state === 0) return
    if (this.contentContainer.selectedFrameIndex !== -1 && this.contentContainer.selectedFrameIndex !== this.editAreaIndex && this.state === 0) return
    
    document.removeEventListener(this.mousemove, this.onMousemove)
    document.removeEventListener(this.mouseup, this.onMouseup)
    
    if (!this.mouseDown) return
    this.mouseDown = false
    this.moving = false
    this.frameContainer.classList.add('selected')

    if (e.type === 'touchend') {
      document.querySelector('.document-container').style.overflow = 'auto'
    }

    if (this.state === 0 && !this.imageUrl) this.updateOriginalImageUrl()
    if (this.state !== 0 && !this.mouseMoved) this.eventBus.dispatch('contentPropertyChange', { type: 'image', isOpen: true, opacity: this.opacity * 100 })

    if (this.state === 0) {
      this.state = 1
      this.contentContainer.selectedFrameIndex = this.editAreaIndex
      this.container.append(this.outerLineContainer)
      this.eventBus.dispatch('changeRightPanelBtnDisabled', false)
      this.eventBus.dispatch('contentBoxSelected', {
        type: 'image',
        pageNumber: this.pageViewer.pageIndex + 1,
      })
    }

    if (this.mouseMoved) {
      const oldPoint = {
        start: this.start,
        end: this.end
      }
      const newPoint = {
        start: this.newStart,
        end: this.newEnd
      }
  
      const { pageX, pageY } = getClickPoint(e)
      if (!(pageX === this.startState?.clickX && pageY === this.startState?.clickY) && this.newStart && this.newEnd) {
        this.start = this.newStart
        this.end = this.newEnd
      }

      const { start, end } = this.getInitialPoint(this.start, this.end)
      const rect = {
        left: start.x,
        top: start.y,
        right: end.x,
        bottom: end.y
      }

      let action = this.cropped ? 'SetClipRect' : 'SetFrame'
      if (this.state === 2) {
        action = 'CutWithRect'
        this.cropped = true
      }
      await this.messageHandler.sendWithPromise(action, {
        pagePtr: this.pagePtr,
        editAreaPtr: this.editAreaPtr,
        rect
      })

      this.needUndoRec = true
      this.updateCanvas({oldPoint, newPoint})
    }

    const elements = [this.imageContainer, this.outerLine, this.deletetButton]

    const editorPanel = document.querySelector('.editor-panel')
    editorPanel && elements.push(editorPanel)
    const propertyPanelButton = document.getElementById('propertyPanelButton')
    propertyPanelButton && elements.push(propertyPanelButton)
    const undo = document.getElementById('undo')
    undo && elements.push(undo)
    const redo = document.getElementById('redo')
    redo && elements.push(redo)

    this.listener && document.removeEventListener(isMobileDevice ? 'touchend' : 'mouseup', this.listener)
    this.listener = onClickOutsideUp(elements, this.handleOutside.bind(this))
  }

  async handleMouseMove (e) {
    if (this.contentContainer.selectedFrameIndex !== -1 && this.contentContainer.selectedFrameIndex !== this.editAreaIndex && this.state === 0) return

    this.moving = true
    
    if (e.type === 'touchmove') {
      document.querySelector('.document-container').addEventListener('scroll', event => event.preventDefault())
    }

    const { pageX, pageY } = getClickPoint(e)

    const start = this.start
    const end = this.end
    const startState = this.startState

    if (!startState) return
    if (startState.clickX === pageX && startState.clickY === pageY) return

    const { width, height } = this.viewport
    if (startState.operator === 'nw-resize') {
      let x = pageX - (startState.clickX - start.x)
      let y = pageY - (startState.clickY - start.y)

      x = Math.min(end.x - 2, x)
      x = Math.max(this.state === 2 ? start.x : 0, x)
      y = Math.min(end.y - 2, y)
      y = Math.max(this.state === 2 ? start.y : 0, y)

      if (this.state === 1) {
        let moveX = x - start.x
        let moveY = moveX * this.imageRatio
        y = start.y + moveY
        
        if (y < 0 || x < 0 || y >= end.y) return
      }

      this.updateOutline({
        start: { x, y },
        end: { x: end.x, y: end.y },
      });
    } else if (startState.operator === 'w-resize') {
      let x = pageX - (startState.clickX - start.x)

      x = Math.min(end.x - 2, x)
      x = Math.max(this.state === 2 ? start.x : 7, x)

      this.updateOutline({
        start: { x, y: start.y },
        end,
      });
    } else if (startState.operator === 'sw-resize') {
      let x = pageX - (startState.clickX - start.x)
      let y = pageY - (startState.clickY - end.y)

      x = Math.min(end.x - 2, x)
      x = Math.max(this.state === 2 ? start.x : 0, x)
      y = Math.min(this.state === 2 ? end.y : height, y)
      y = Math.max(start.y + 2, y)

      if (this.state === 1) {
        let moveX = x - start.x
        let moveY = moveX * this.imageRatio
        y = end.y - moveY
        if (y >= height || x < 0 || y <= start.y) return
      }

      this.updateOutline({
        start: { x, y: start.y },
        end: { x: end.x, y },
      });
    } else if (startState.operator === 's-resize') {
      let y = pageY - (startState.clickY - end.y)

      y = Math.min(this.state === 2 ? end.y : height, y)
      y = Math.max(start.y + 2, y)

      this.updateOutline({
        start,
        end: { x: end.x, y },
      });
    } else if (startState.operator === 'se-resize') {
      let x = pageX - (startState.clickX - end.x)
      let y = pageY - (startState.clickY - end.y)

      x = Math.min(this.state === 2 ? end.x : width, x)
      x = Math.max(start.x + 2, x)
      y = Math.min(this.state === 2 ? end.y : height, y)
      y = Math.max(start.y + 2, y)

      if (this.state === 1) {
        let moveX = end.x - x
        let moveY = moveX * this.imageRatio
        y = end.y - moveY

        if (y >= height || x >= width || y - start.y <= 0) return
      }

      this.updateOutline({
        start: { x: start.x, y: start.y },
        end: { x, y },
      });
    } else if (startState.operator === 'e-resize') {
      let x = pageX - (startState.clickX - end.x)
      
      x = Math.min(this.state === 2 ? end.x : width, x)
      x = Math.max(start.x + 2, x)

      this.updateOutline({
        start,
        end: { x, y: end.y },
      });
    } else if (startState.operator === 'ne-resize') {
      let x = pageX - (startState.clickX - end.x)
      let y = pageY - (startState.clickY - start.y)

      x = Math.min(this.state === 2 ? end.x : width, x)
      x = Math.max(start.x + 2, x)
      y = Math.min(end.y - 2, y)
      y = Math.max(this.state === 2 ? start.y : 0, y)

      if (this.state === 1) {
        let moveX = end.x - x
        let moveY = moveX * this.imageRatio
        y = start.y + moveY

        if (y <=0 || x >= width || end.y - y <= 0) return
      }

      this.updateOutline({
        start: { x: start.x, y },
        end: { x, y: end.y },
      });
    } else if (startState.operator === 'n-resize') {
      let y = pageY - (startState.clickY - start.y)

      y = Math.min(end.y - 2, y)
      y = Math.max(this.state === 2 ? start.y : 0, y)

      this.updateOutline({
        start: { x: start.x, y },
        end,
      });
    } else if (startState.operator === 'move') {
      if (this.state === 2) return

      let x1 = pageX - (startState.clickX - start.x);
      let y1 = pageY - (startState.clickY - start.y);
      let x2 = pageX - (startState.clickX - end.x);
      let y2 = pageY - (startState.clickY - end.y);

      const rect = {
        width: Math.abs(start.x - end.x),
        height: Math.abs(start.y - end.y)
      }
      
      if (x1 < x2) {
        x1 = Math.max(this.state === 2 ? start.x : 0, x1)
        x1 = Math.min(this.state === 2 ? end.x - rect.width : width - rect.width, x1)
        x2 = Math.max(this.state === 2 ? start.x + rect.width : rect.width, x2)
        x2 = Math.min(this.state === 2 ? end.x : width, x2)
      } else {
        x2 = Math.max(this.state === 2 ? start.x : 0, x2)
        x2 = Math.min(this.state === 2 ? end.x - rect.width : width - rect.width, x2)
        x1 = Math.max(this.state === 2 ? start.x + rect.width : rect.width, x1)
        x1 = Math.min(this.state === 2 ? end.x : width, x1)
      }

      if (y1 < y2) {
        y1 = Math.max(this.state === 2 ? start.y : 0, y1)
        y1 = Math.min(this.state === 2 ? end.y - rect.height : height - rect.height, y1)
        y2 = Math.max(this.state === 2 ? start.y + rect.height : rect.height, y2)
        y2 = Math.min(this.state === 2 ? end.y : height, y2)
      } else {
        y2 = Math.max(this.state === 2 ? start.y : 0, y2)
        y2 = Math.min(this.state === 2 ? end.y - rect.height : height - rect.height, y2)
        y1 = Math.max(this.state === 2 ? start.y + rect.height : rect.height, y1)
        y1 = Math.min(this.state === 2 ? end.y : height, y1)
      }

      this.updateOutline({
        start: { x: x1, y: y1 },
        end: { x: x2, y: y2 },
      });
    }

    this.mouseMoved = true
  }

  async handleOutside () {
    if (this.moving) {
      const elements = [this.imageContainer, this.outerLine, this.deletetButton]

      const editorPanel = document.querySelector('.editor-panel')
      editorPanel && elements.push(editorPanel)
      const propertyPanelButton = document.getElementById('propertyPanelButton')
      propertyPanelButton && elements.push(propertyPanelButton)
      const undo = document.getElementById('undo')
      undo && elements.push(undo)
      const redo = document.getElementById('redo')
      redo && elements.push(redo)
      
      onClickOutsideUp(elements, this.handleOutside.bind(this))
      return
    }

    this.state = 0
    this.imageUrl = null
    if (!this.removed) this.contentContainer.selectedFrameIndex = -1
    this.moveRect.style.cursor = 'all-scroll'

    this.updateRectStyle()
    this.updateOutline({
      start: this.start,
      end: this.end
    })

    this.frameContainer?.classList.remove('editing')
    this.frameContainer?.classList.remove('selected')
    this.outerLineContainer.remove()
    this.outerLineContainer.append(this.deletetButton)

    setTimeout(() => {
      const hasItem = document.querySelector('.frame-container.selected')
      if (!hasItem) {
        this.eventBus.dispatch('contentPropertyChange', { type: 'image', isOpen: false })
        this.eventBus.dispatch('changeRightPanelBtnDisabled', true)

        this.eventBus.dispatch('contentBoxDeselected', {
          type: 'image',
          pageNumber: this.pageViewer.pageIndex + 1,
        })
      }
    }, 1)
  }

  // 获取rect
  async getRect () {
    let frame
    if (!this.cropped) {
      frame = await this.messageHandler.sendWithPromise('GetFrame', {
        pagePtr: this.pagePtr,
        editAreaPtr: this.editAreaPtr
      })
    }
    this.frame = await this.messageHandler.sendWithPromise('GetClipRect', {
      pagePtr: this.pagePtr,
      editAreaPtr: this.editAreaPtr
    })
    if (frame && (frame.left !== this.frame.left || frame.top !== this.frame.top || frame.right !== this.frame.right || frame.bottom !== this.frame.bottom)) this.cropped = true

    const { start, end } = this.getActualRect(
      this.viewport,
      this.scale,
      this.frame
    )
    this.start = start
    this.end = end

    this.oldRect = this.rect || null
    const rect = this.rectCalc(start, end)
    this.rect = rect
    this.imageRatio = this.rect.height / this.rect.width
  }

  // 更新canvas图
  async updateCanvas(whole, oldRect) {
    if (!this.removed) await this.getRect()

    if (this.frameContainer && this.outerLineContainer) {
      this.updateOutline({
        start: this.start,
        end: this.end
      })

      setCss(this.frameContainer, {
        left: this.rect.left + 'px',
        top: this.rect.top + 'px',
        width: this.rect.width + 'px',
        height: this.rect.height + 'px'
      })
    }

    if (whole) {
      const { oldPoint, newPoint } = whole
      const wholeArea = this.getEntireArea(oldPoint, newPoint)

      const { start, end } = this.getActualRect(
        this.viewport,
        this.scale,
        wholeArea
      )
      let wholeAreaRect = this.rectCalc(start, end)

      const oldRect = this.rectCalc(oldPoint.start, oldPoint.end)
      const newWholeArea = this.getEntireArea(oldRect, this.rect)
      const newWholeAreaRect = {
        left: newWholeArea.left,
        top: newWholeArea.top,
        width: newWholeArea.right - newWholeArea.left,
        height: newWholeArea.bottom - newWholeArea.top
      }
      if (
        parseInt(wholeAreaRect.left) !== parseInt(newWholeArea.left) ||
        parseInt(wholeAreaRect.top) !== parseInt(newWholeArea.top) ||
        parseInt(wholeAreaRect.width) !== parseInt(newWholeArea.width) ||
        parseInt(wholeAreaRect.height) !== parseInt(newWholeArea.height)
      ) {
        wholeAreaRect = newWholeAreaRect
      }

      const imgRect = {
        left: parseInt(wholeAreaRect.left * this.ratio),
        top: parseInt(wholeAreaRect.top * this.ratio),
        right: parseInt(wholeAreaRect.left * this.ratio) + parseInt(wholeAreaRect.width * this.ratio),
        bottom: parseInt(wholeAreaRect.top * this.ratio) + parseInt(wholeAreaRect.height * this.ratio),
        width: parseInt(wholeAreaRect.width * this.ratio),
        height: parseInt(wholeAreaRect.height * this.ratio),
      }
      this.imgRect = imgRect

    } else if (oldRect) {
      const wholeAreaRect = this.getEntireArea(this.oldRect, this.rect)

      const imgRect = {
        left: parseInt(wholeAreaRect.left * this.ratio),
        top: parseInt(wholeAreaRect.top * this.ratio),
        right: parseInt(wholeAreaRect.right * this.ratio),
        bottom: parseInt(wholeAreaRect.bottom * this.ratio),
        width: parseInt(wholeAreaRect.right * this.ratio) - parseInt(wholeAreaRect.left * this.ratio),
        height: parseInt(wholeAreaRect.bottom * this.ratio) - parseInt(wholeAreaRect.top * this.ratio),
      }
      this.imgRect = imgRect

    } else {
      const imgRect = {
        left: parseInt(this.rect.left * this.ratio),
        top: parseInt(this.rect.top * this.ratio),
        right: parseInt(this.rect.left * this.ratio) + parseInt(this.rect.width * this.ratio),
        bottom: parseInt(this.rect.top * this.ratio) + parseInt(this.rect.height * this.ratio),
        width: parseInt(this.rect.width * this.ratio),
        height: parseInt(this.rect.height * this.ratio),
      }
      this.imgRect = imgRect
    }

    let { imageArray } = await this.messageHandler.sendWithPromise('PushRenderTask', {
      pagePtr: this.pagePtr,
      scale: this.scale * this.ratio,
      left: this.imgRect.left,
      right: this.imgRect.right,
      bottom: this.imgRect.bottom,
      top: this.imgRect.top
    })
    this.imageArray = imageArray

    this.drawCanvas()
    this.saveEdit()
  }

  // 绘制canvas
  drawCanvas () {
    if (!this.imgRect) {
      this.updateCanvas()
      return
    }
    if (!this.pageViewer.canvas) return
    const pageCtx = this.pageViewer.canvas.getContext('2d')
    const imageData = pageCtx.createImageData(parseInt(this.imgRect.width), parseInt(this.imgRect.height))
    imageData.data.set(this.imageArray)
    pageCtx.putImageData(imageData, this.imgRect.left, this.imgRect.top)
  }

  // 保存编辑
  async saveEdit () {
    await this.messageHandler.sendWithPromise('EndEdit', this.editPagePtr)
    if (this.needUndoRec) {
      this.addUndoHistory()
      this.needUndoRec = false
    }
  }

  // 更新outerline框的位置大小
  updateOutline ({ start, end }) {
    const rect = this.rectCalc(start, end)

    this.newStart = start
    this.newEnd = end

    setCss(this.outerLine, {
      left: rect.left - this.borderWidth + 'px',
      top: rect.top - this.borderWidth + 'px',
      width: rect.width + this.borderWidth + this.pointWidth / 2 + 'px',
      height: rect.height + this.borderWidth + this.pointHeight / 2 + 'px',
    })

    const left = (rect.left + rect.width - 30) + 'px'
    const top = (rect.top + rect.height + 8) + 'px'
    this.deletetButton.style.left = left
    this.deletetButton.style.top = top

    this.moveRect.setAttribute('width', rect.width - this.borderWidth)
    this.moveRect.setAttribute('height', rect.height - this.borderWidth)

    this.LeftRect.setAttribute('y', (rect.height - this.pointHeight + this.borderWidth) / 2)

    this.bottomRect.setAttribute('x', (rect.width - this.pointWidth + this.borderWidth) / 2)
    this.bottomRect.setAttribute('y', rect.height - this.pointHeight / 2 + this.borderWidth / 2)

    this.rightRect.setAttribute('x', rect.width - this.pointWidth / 2 + this.borderWidth / 2)
    this.rightRect.setAttribute('y', (rect.height - this.pointHeight + this.borderWidth) / 2)

    this.topRect.setAttribute('x', (rect.width - this.pointWidth + this.borderWidth) / 2)

    if (this.state === 2) {
      const long = 15

      this.LeftRect.setAttribute('y', (rect.height - long + this.borderWidth) / 2)
      this.topRect.setAttribute('x', (rect.width - long + this.borderWidth) / 2)
      this.rightRect.setAttribute('y', (rect.height - long + this.borderWidth) / 2)
      this.bottomRect.setAttribute('x', (rect.width - long + this.borderWidth) / 2)

      this.topRightRect.setAttribute('d', `M${rect.width + this.borderWidth / 2 - long + this.pointWidth / 2},${this.pointWidth / 2} H${rect.width + this.borderWidth / 2} V${long}`)
      
      this.bottomLeftRect.setAttribute('d', `M${this.pointWidth / 2},${rect.height + this.borderWidth / 2 - long + this.pointWidth / 2} V${rect.height + this.borderWidth / 2} H${long}`)

      this.bottomRightRect.setAttribute('d', `M${rect.width + this.borderWidth / 2},${rect.height + this.borderWidth / 2 - long + this.pointWidth / 2} V${rect.height + this.borderWidth / 2} H${rect.width + this.borderWidth / 2 - long + this.pointWidth / 2}`)

      const offsetLeft = rect.left - this.rect.left
      const offsetTop = rect.top - this.rect.top
      const offsetRight = this.rect.left + this.rect.width - rect.left - rect.width
      const offsetBottom = this.rect.top + this.rect.height - rect.top - rect.height

      switch (this.startState.operator) {
        case 'sw-resize':
          this.frameContainer.style['box-shadow'] = `inset ${offsetLeft}px ${-offsetBottom}px rgba(0, 0, 0, 0.5)`
          break;

        case 'ne-resize':
          this.frameContainer.style['box-shadow'] = `inset ${-offsetRight}px ${offsetTop}px rgba(0, 0, 0, 0.5)`
          break;

        case 'nw-resize':
        case 'n-resize':
        case 'w-resize':
          this.frameContainer.style['box-shadow'] = `inset ${offsetLeft}px ${offsetTop}px rgba(0, 0, 0, 0.5)`
          break;
      
        case 'se-resize':
        case 's-resize':
        case 'e-resize':
          this.frameContainer.style['box-shadow'] = `inset ${-offsetRight}px ${-offsetBottom}px rgba(0, 0, 0, 0.5)`
          break;
      }
    } else {
      this.bottomLeftRect.setAttribute('y', rect.height - this.pointHeight / 2 + this.borderWidth / 2)

      this.bottomRightRect.setAttribute('x', rect.width - this.pointWidth / 2 + this.borderWidth / 2)
      this.bottomRightRect.setAttribute('y', rect.height - this.pointHeight / 2 + this.borderWidth / 2)

      this.topRightRect.setAttribute('x', rect.width - this.pointWidth / 2 + this.borderWidth / 2)
    }
  }

  async setProperty(props) {
    for (const item in props) {

      if (item === 'alignType') {
        if (!this.selectedCharRange) {
          await this.messageHandler.sendWithPromise('SetTextAligningSection', {
            editAreaPtr: this.editAreaPtr,
            alignType: props.alignType,
            char: activeCharPlace = {
              SectionIndex: 0,
              LineIndex: 0,
              RunIndex: 0,
              CharIndex: -1
            }
          })
        } else {
          await this.messageHandler.sendWithPromise('SetTextAligningRange', {
            editAreaPtr: this.editAreaPtr,
            alignType: props.alignType,
            start: this.selectedCharRange.start,
            end: this.selectedCharRange.end,
          })
        }
      }

      if (!this.selectedCharRange && item !== 'alignType') {
        await this.selectAllText()
      }

      if (item === 'opacity') {
        await this.messageHandler.sendWithPromise('SetCharsFontTransparency', {
          editAreaPtr: this.editAreaPtr,
          start: this.selectedCharRange.start,
          end: this.selectedCharRange.end,
          opacity: props.opacity / 100
        })
      }

      if (item === 'fontSize') {
        await this.messageHandler.sendWithPromise('SetCharsFontSize', {
          editAreaPtr: this.editAreaPtr,
          start: this.selectedCharRange.start,
          end: this.selectedCharRange.end,
          fontSize: props.fontSize
        })
      }

      if (item === 'fontFamily') {
        await this.messageHandler.sendWithPromise('SetFontFromNativeTrueTypeFont', {
          editAreaPtr: this.editAreaPtr,
          start: this.selectedCharRange.start,
          end: this.selectedCharRange.end,
          fontFamily: props.fontFamily
        })
      }
    }

    await this.updateCanvas(null, true)
  }

  // 属性面板 修改属性
  async handlePropertyPanelChanged (props) {
    if (this.state === 0) return

    for (const item in props) {
      if (item === 'rotate') {
        await this.messageHandler.sendWithPromise('RotateImage', {
          editAreaPtr: this.editAreaPtr,
          angle: props[item]
        })
        this.needUndoRec = true
      }

      if (item === 'flip') {
        if (props[item] === 'x') {
          await this.messageHandler.sendWithPromise('HorizontalMirrorImage', this.editAreaPtr)
        } else if (props[item] === 'y') {
          await this.messageHandler.sendWithPromise('VerticalMirrorImage', this.editAreaPtr)
        }
        this.needUndoRec = true
      }

      if (item === 'opacity') {
        if (this.opacity * 100 === props.opacity) return

        const opacity = await this.messageHandler.sendWithPromise('GetImageTransparency', this.editAreaPtr)
        if (opacity * 100 === props.opacity) return

        await this.messageHandler.sendWithPromise('SetImageTransparency', {
          editAreaPtr: this.editAreaPtr,
          opacity: props.opacity / 100
        })
        this.opacity = props.opacity / 100
        this.needUndoRec = true
      }

      if (item === 'tool' && props.tool === 'replace') {
        await this.uploadFile()
        .then(async (data) => {
          const { imageBase64, width, height } = data
          const rect = {
            top: this.frame.top,
            left: this.frame.left,
            width: this.frame.right - this.frame.left,
            height: this.frame.bottom - this.frame.top
          }

          const imageRatio = width / height
          const rectRatio = rect.width / rect.height
          let scaledWidth, scaledHeight

          if (imageRatio > rectRatio) {
            scaledWidth = rect.width
            scaledHeight = rect.width / imageRatio
          } else {
            scaledWidth = rect.height * imageRatio
            scaledHeight = rect.height
          }
          
          let left, right, bottom, top
          const centerPoint = {
            x: rect.left + rect.width / 2,
            y: rect.top + rect.height / 2
          }

          left = centerPoint.x - scaledWidth / 2
          right = centerPoint.x + scaledWidth / 2
          bottom = centerPoint.y + scaledHeight / 2
          top = centerPoint.y - scaledHeight / 2

          this.editAreaPtr = await this.messageHandler.sendWithPromise('ReplaceImageAreaByStream', {
            pagePtr: this.pagePtr,
            editPagePtr: this.editPagePtr,
            editAreaPtr: this.editAreaPtr,
            rect: {
              left,
              right,
              bottom,
              top
            },
            imageBase64
          })

          this.opacity = await this.messageHandler.sendWithPromise('GetImageTransparency', this.editAreaPtr)
          this.eventBus.dispatch('contentPropertyChange', { type: 'image', opacity: this.opacity * 100 })

          this.needUndoRec = true
          this.saveEdit()
        })
        .catch((error) => {
          console.error(error)
        })
      }

      if (item === 'tool' && props.tool === 'export') {
        this.exportImage(this.imageUrl)
        return
      }

      if (item === 'tool' && props.tool === 'crop') {
        if (this.state === 2) return
        this.state = 2
        this.frameContainer.classList.add('editing')
        this.deletetButton.remove()
        this.moveRect.style.cursor = 'auto'
        this.updateRectStyle()
        return
      }
    }

    await this.updateCanvas(null, true)
    this.updateOriginalImageUrl()
  }

  // 计算编辑区域大小变化后，影响的整个区域的位置
  getEntireArea (oldPoint, newPoint) {
    let area = null
    if (oldPoint.start && newPoint.start) {
      let oldP = this.getInitialPoint(oldPoint.start, oldPoint.end)
      let newP = this.getInitialPoint(newPoint.start, newPoint.end)

      let left = Math.min(oldP.start.x, newP.start.x)
      let top = Math.min(oldP.start.y, newP.start.y)
      let right = Math.max(oldP.end.x, newP.end.x)
      let bottom = Math.max(oldP.end.y, newP.end.y)

      area = {
        left,
        top,
        right,
        bottom
      }

    } else if (oldPoint.left && newPoint.left) {
      let left = Math.min(oldPoint.left, newPoint.left)
      let top = Math.min(oldPoint.top, newPoint.top)
      let right = Math.max(oldPoint.left + oldPoint.width, newPoint.left + newPoint.width)
      let bottom = Math.max(oldPoint.top + oldPoint.height, newPoint.top + newPoint.height)

      area = {
        left,
        top,
        right,
        bottom
      }
    }
    
    return area
  }

  // 直接进入编辑模式
  async goEditing () {
    this.state = 1
    this.contentContainer.selectedFrameIndex = this.editAreaIndex
    this.frameContainer.classList.add('selected')
    this.container.append(this.outerLineContainer)
    if (!this.imageUrl) this.updateOriginalImageUrl()

    const elements = [this.imageContainer, this.outerLine, this.deletetButton]

    const editorPanel = document.querySelector('.editor-panel')
    editorPanel && elements.push(editorPanel)
    const propertyPanelButton = document.getElementById('propertyPanelButton')
    propertyPanelButton && elements.push(propertyPanelButton)
    const undo = document.getElementById('undo')
    undo && elements.push(undo)
    const redo = document.getElementById('redo')
    redo && elements.push(redo)
    
    onClickOutsideUp(elements, this.handleOutside.bind(this))
  }

  // 删除区域
  async handleDelete () {
    await this.handleOutside()
    
    this.removed = true
    this.outerLineContainer.remove()
    this.frameContainer.remove()
    this.frameContainer = null
    this.contentContainer.selectedFrameIndex = -1

    await this.messageHandler.send('RemoveEditArea', {
      editPagePtr: this.editPagePtr,
      editAreaPtr: this.editAreaPtr
    })
    
    this.needUndoRec = true
    this.updateCanvas()
    this.contentContainer.removeEditor(this.editAreaIndex)
    this.eventBus.dispatch('contentPropertyChange', { type: 'image', isOpen: false })
  }

  remove() {
    this.handleDelete()
  }

  removeViewer () {
    this.state = 0
    // await this.handleOutside()
    
    this.removed = true
    this.outerLineContainer.remove()
    this.frameContainer.remove()
    this.frameContainer = null
    this.contentContainer.selectedFrameIndex = -1
    this.eventBus.dispatch('contentPropertyChange', { type: 'image', isOpen: false })
  }

  // 上传图片并获取宽高
  uploadFile () {
    return new Promise((resolve, reject) => {
      const fileInput = document.createElement('input')
      fileInput.type = 'file'
      fileInput.accept = '.jpg, .jpeg, .png'

      fileInput.onchange = () => {
        const file = fileInput.files[0]

        if (file.size > 2 * 1024 * 1024) {
          reject(this.$t('editorPanel.maximum'))
          fileInput.remove()
          window.$message.error(this.$t('editorPanel.maximum'), {
            duration: 3000,
            // icon: () => h('img', { src: MessageError })
          })
          return
        }

        if (file) {
          const reader = new FileReader()
          reader.onload = function (e) {
            const image = new Image()
            image.onload = function () {
              resolve({
                // file,
                imageBase64: e.target.result,
                width: image.width,
                height: image.height
              })
            }
            image.onerror = function (error) {
              reject(error)
            }
            image.src = e.target.result
          }
          reader.onerror = function (error) {
            reject(error)
          }
          reader.readAsDataURL(file)
          fileInput.remove()
        }
      }
      fileInput.click()
    })
  }

  // 更新原始图片链接
  async updateOriginalImageUrl() {
    const { width, height, stride } = await this.messageHandler.sendWithPromise('GetImageAreaSize', this.editAreaPtr)
    const { imageArray } = await this.messageHandler.sendWithPromise('ExtractOriginalImage', {
      editAreaPtr: this.editAreaPtr,
      width,
      height,
      stride
    })

    this.imageUrl = this.imageArrayToUrl(imageArray, width, height)
    this.eventBus.dispatch('contentPropertyChange', { type: 'image', imageUrl: this.imageUrl, opacity: this.opacity * 100 })
  }

  // 更新点的位置
  updateRectStyle () {
    this.topLeftRect.remove()
    this.topRightRect.remove()
    this.bottomLeftRect.remove()
    this.bottomRightRect.remove()

    if (this.state === 2) {
      const long = 15

      this.LeftRect.setAttribute('height', long)
      this.LeftRect.setAttribute('y', (this.rect.height - long + this.borderWidth) / 2)
      this.LeftRect.style.cursor = 'e-resize'

      this.topRect.setAttribute('width', long)
      this.topRect.setAttribute('x', (this.rect.width - long + this.borderWidth) / 2)
      this.topRect.style.cursor = 's-resize'

      this.rightRect.setAttribute('height', long)
      this.rightRect.setAttribute('y', (this.rect.height - long + this.borderWidth) / 2)
      this.rightRect.style.cursor = 'w-resize'

      this.bottomRect.setAttribute('width', long)
      this.bottomRect.setAttribute('x', (this.rect.width - long + this.borderWidth) / 2)
      this.bottomRect.style.cursor = 'n-resize'

      this.topLeftRect = createSvg(
        "path",
        {
          class: "nw-resize",
          'data-id': "nw-resize",
          fill: "none",
          stroke: "#1460F3",
          'stroke-width': this.pointWidth,
          // d: 'M15,3 H3 V15',
          d: `M${long},${this.pointWidth / 2} H${this.pointWidth / 2} V${long}`
        },
        {
          cursor: 'se-resize'
        }
      )
      this.topRightRect = createSvg(
        "path",
        {
          class: "ne-resize",
          'data-id': "ne-resize",
          fill: "none",
          stroke: "#1460F3",
          'stroke-width': this.pointWidth,
          d: `M${this.rect.width + this.borderWidth / 2 - long + this.pointWidth / 2},${this.pointWidth / 2} H${this.rect.width + this.borderWidth / 2} V${long}`
        },
        {
          cursor: 'sw-resize'
        }
      )
      this.bottomLeftRect = createSvg(
        "path",
        {
          class: "sw-resize",
          'data-id': "sw-resize",
          fill: "none",
          stroke: "#1460F3",
          'stroke-width': this.pointWidth,
          d: `M${this.pointWidth / 2},${this.rect.height + this.borderWidth / 2 - long + this.pointWidth / 2} V${this.rect.height + this.borderWidth / 2} H${long}`
        },
        {
          cursor: 'ne-resize'
        }
      )
      this.bottomRightRect = createSvg(
        "path",
        {
          class: "se-resize",
          'data-id': "se-resize",
          fill: "none",
          stroke: "#1460F3",
          'stroke-width': this.pointWidth,
          d: `M${this.rect.width + this.borderWidth / 2},${this.rect.height + this.borderWidth / 2 - long + this.pointWidth / 2} V${this.rect.height + this.borderWidth / 2} H${this.rect.width + this.borderWidth / 2 - long + this.pointWidth / 2}`
        },
        {
          cursor: 'nw-resize'
        }
      )
    } else {
      this.LeftRect.setAttribute('height', this.pointHeight)
      this.LeftRect.style.cursor = 'w-resize'
      this.topRect.setAttribute('width', this.pointWidth)
      this.topRect.style.cursor = 'n-resize'
      this.rightRect.setAttribute('height', this.pointHeight)
      this.rightRect.style.cursor = 'e-resize'
      this.bottomRect.setAttribute('width', this.pointWidth)
      this.bottomRect.style.cursor = 's-resize'

      this.topLeftRect = createSvg(
        "rect",
        {
          class: "nw-resize",
          'data-id': "nw-resize",
          fill: "#1460F3",
          stroke: "none",
          width: this.pointWidth,
          height: this.pointHeight,
          x: 0,
          y: 0
        }
      )
      this.topRightRect = createSvg(
        "rect",
        {
          class: "ne-resize",
          'data-id': "ne-resize",
          fill: "#1460F3",
          stroke: "none",
          width: this.pointWidth,
          height: this.pointHeight,
          x: this.rect.width - this.pointWidth / 2 + this.borderWidth / 2,
          y: 0
        }
      )
      this.bottomLeftRect = createSvg(
        "rect",
        {
          class: "sw-resize",
          'data-id': "sw-resize",
          fill: "#1460F3",
          stroke: "none",
          width: this.pointWidth,
          height: this.pointHeight,
          x: 0,
          y: this.rect.height - this.pointHeight / 2 + this.borderWidth / 2
        }
      )
      this.bottomRightRect = createSvg(
        "rect",
        {
          class: "se-resize",
          'data-id': "se-resize",
          fill: "#1460F3",
          stroke: "none",
          width: this.pointWidth,
          height: this.pointHeight,
          x: this.rect.width - this.pointWidth / 2 + this.borderWidth / 2,
          y: this.rect.height - this.pointHeight / 2 + this.borderWidth / 2
        }
      )
    }
    this.outerLine.append(this.topLeftRect)
    this.outerLine.append(this.topRightRect)
    this.outerLine.append(this.bottomLeftRect)
    this.outerLine.append(this.bottomRightRect)
  }

  // 图片Unit8Array数据转为图片链接
  imageArrayToUrl (imageArray, width, height) {
    let canvas = document.createElement('canvas')
    const context = canvas.getContext('2d')

    canvas.width = width
    canvas.height = height
    
    const imageData = context.createImageData(width, height)
    const imageDataArray = new Uint8ClampedArray(imageArray.buffer)

    imageData.data.set(imageDataArray)
    context.putImageData(imageData, 0, 0)
    const url = canvas.toDataURL('image/png', 1)

    canvas.remove()
    canvas = null

    return url
  }

  // 下载图片
  exportImage(url, filename) {
    const a = document.createElement('a')
    a.href = url
    a.download = filename || 'image'
    document.body.appendChild(a)
    a.click()
    document.body.removeChild(a)
  }

  // 文本模式下隐藏
  handleShow (type) {
    if (type === 'text') {
      this.hidden = true
      this.frameContainer && (this.frameContainer.style.display = 'none')
      if (this.state) {
        this.handleOutside()
      }
    } else {
      this.hidden = false
      this.frameContainer && (this.frameContainer.style.display = 'block')
    }
  }

  // 添加undo记录
  addUndoHistory () {
    this.contentContainer.handleOperateList({
      editPagePtr: this.editPagePtr,
      editAreaIndex: this.editAreaIndex,
      editAreaPtr: this.editAreaPtr,
      pageIndex: this.pageViewer.pageIndex
    })
  }

  // undo/redo后 更新canvas图
  async updateCanvasAfterUndoRedo() {
    await this.getRect()

    if (this.frameContainer && this.outerLineContainer) {
      this.updateOutline({
        start: this.start,
        end: this.end
      })

      setCss(this.frameContainer, {
        left: this.rect.left + 'px',
        top: this.rect.top + 'px',
        width: this.rect.width + 'px',
        height: this.rect.height + 'px'
      })
    }

    const wholeAreaRect = this.getEntireArea(this.oldRect, this.rect)

    const imgRect = {
      left: parseInt(wholeAreaRect.left * this.ratio),
      top: parseInt(wholeAreaRect.top * this.ratio),
      right: parseInt(wholeAreaRect.right * this.ratio),
      bottom: parseInt(wholeAreaRect.bottom * this.ratio),
      width: parseInt(wholeAreaRect.right * this.ratio) - parseInt(wholeAreaRect.left * this.ratio),
      height: parseInt(wholeAreaRect.bottom * this.ratio) - parseInt(wholeAreaRect.top * this.ratio),
    }
    this.imgRect = imgRect

    let { imageArray } = await this.messageHandler.sendWithPromise('PushRenderTask', {
      pagePtr: this.pagePtr,
      scale: this.scale * this.ratio,
      left: this.imgRect.left,
      right: this.imgRect.right,
      bottom: this.imgRect.bottom,
      top: this.imgRect.top
    })
    this.imageArray = imageArray

    this.drawCanvas()
    this.saveEdit()

    if (this.contentContainer.selectedFrameIndex === this.editAreaIndex) {
      this.updateOriginalImageUrl()
      const opacity = await this.messageHandler.sendWithPromise('GetImageTransparency', this.editAreaPtr)
      this.opacity = Math.round(opacity * 100) / 100
      this.eventBus.dispatch('contentPropertyChange', { type: 'image', opacity: this.opacity * 100 })
    }
  }
}
