<template>
  <div
    class="seatChart"
    ref="container">
    <v-stage
      ref="stage"
      :config="configKonva"
      style="background:#ddd"
      @click="handleStageClick"
      @wheel="handleWheel"
      @dragend="handleDragEnd"
      @mousedown="handleMouseDown"
      @mousemove="handleMouseMove"
      @mouseup="handleMouseUp">
      <v-layer>
        <v-group ref="group">
          <v-rect :config="bgConfig"></v-rect>

          <v-line :config="colLine"></v-line>
          <v-line :config="rowLine"></v-line>

          <v-circle
            v-for="(seat,index) in seats"
            :key="index"
            ref="seat"
            :config="seat"
            @mouseleave="handleSeatMouseLeave"
            @mouseenter="handleSeatMouseEnter"
          ></v-circle>

          <v-text
            v-for="(row,index) in seatsText"
            :key="'seat'+index"
            :config="row"></v-text>

          <v-text
            v-for="(row,index) in rowText1"
            :key="'a'+index"
            :config="row"></v-text>

          <v-text
            v-for="(row,index) in rowText2"
            :key="'b'+index"
            :config="row"></v-text>

          <v-text
            v-for="(col,index) in colText1"
            :key="'c'+index"
            :config="col"></v-text>

          <v-text
            v-for="(col,index) in colText2"
            :key="'d'+index"
            :config="col"></v-text>

          <v-rect
            :config="configSelection"
            ref="selection"></v-rect>
        </v-group>
      </v-layer>
    </v-stage>

    <div class="toolbar">
      <ul>
        <li
          :class="{active:currentTool===t.value}"
          @click="handleToolChange(t)"
          v-for="(t,i) in tools"
          :key="i">
          <el-tooltip
            :content="t.label"
            placement="left"
            effect="dark">
            <img
              :src="t.img"
              :width="t.size">
          </el-tooltip>
        </li>
      </ul>
    </div>

    <div
      class="popup"
      :class="{show:showPop}"
      :style="[popupStyle]">
      <div class="title">
        座位信息
      </div>
      <ul class="content">
        <li>排数：{{ popInfo.row_tag || '无' }}</li>
        <li>座位号：{{ popInfo.text || '无' }}</li>
        <li>座位名：{{ popInfo.seatName || '无' }}</li>
      </ul>
    </div>
  </div>
</template>

<script>
import Konva from 'konva'
import throttle from 'lodash/throttle'

const SEAT_SIZE = 16
const SEATS_DISTANCE = 30
const STAGE_PADDING = 50

const defautColor = 'rgba(220,220,220,0.5)'
// config.fill = 'rgba(74,144,226,0.5)' // blue
// config.fill = 'rgba(126,211,33,0.5)' // green
const configCircle = {
  x: 50,
  y: 50,
  radius: SEAT_SIZE / 2,
  fill: defautColor,
  stroke: 'red',
  // stroke: 'rgb(74, 144, 226)',
  strokeWidth: 0,
  name: 'seat',
  isSelected: false
}

const tools = [
  {
    label: '座位选择',
    value: 'seatSelect',
    img: require('./icons/select.png'),
    size: 30
  },
  {
    label: '空位选择',
    value: 'blankSelect',
    img: require('./icons/blankSelect.png'),
    size: 30
  },
  {
    label: '删除选择',
    value: 'removeSelect',
    img: require('./icons/delete.png'),
    size: 36
  },
  {
    label: '重置舞台',
    value: 'resetSize',
    img: require('./icons/resize.png'),
    size: 24
  }
]

export default {
  props: {
    data: {
      type: Array,
      default: () => []
    },
    rows: {
      type: Number,
      default: 10
    },
    columns: {
      type: Number,
      default: 10
    }
  },
  created () {
    // this.reset()
  },
  computed: {
    selectionEl () {
      return this.$refs.selection.getNode()
    },
    stage () {
      return this.$refs.stage.getNode()
    },
    group () {
      return this.$refs.group.getNode()
    },
    selected () {
      return this.seats.filter(item => {
        return item.isSelected
      })
    },
    seatsText () {
      return this.seats.map(seat => {
        const s = Object.assign({}, seat)

        return {
          x: s.x - SEAT_SIZE / 2,
          y: s.y - SEAT_SIZE / 2,
          width: SEAT_SIZE,
          height: SEAT_SIZE,
          fill: '#fff',
          align: 'center',
          text: s.text,
          verticalAlign: 'middle',
          fontSize: SEAT_SIZE / 2,
          listening: false
        }
      })
    }
  },
  data () {
    return {
      tools,
      scaleBy: 1.1,
      newScale: 1,
      currentTool: 'seatSelect',
      configKonva: {
        width: 1000,
        height: 800,
        scaleX: 1,
        scaleY: 1,
        x: 0,
        y: 0,
        draggable: false
      },
      colLine: {
        points: [0, 0, 0, 0],
        stroke: 'red',
        strokeWidth: 1,
        visible: false
      },
      rowLine: {
        points: [0, 0, 0, 0],
        stroke: 'red',
        strokeWidth: 1,
        visible: false
      },
      bgConfig: {
        x: 0,
        y: 0,
        width: 300,
        height: 300,
        fill: 'rgba(255,255,255,0.8)'
      },
      seats: [],
      colText1: [],
      colText2: [],
      rowText1: [],
      rowText2: [],
      selectPosition: {
        x1: 0,
        y1: 0,
        x2: 0,
        y2: 0
      },
      configSelection: {
        width: 0,
        height: 0,
        visible: false,
        fill: 'rgba(48,77,121,0.5)'
      },
      showPop: false,
      popupStyle: {
        left: 0,
        top: 0
      },
      popInfo: {}
    }
  },
  methods: {
    reset () {
      this.createSeats()

      this.setRowText()
      this.setColText()
    },
    initSize () {
      this.bgConfig.width = SEATS_DISTANCE * (this.columns - 1) + STAGE_PADDING * 2 + SEAT_SIZE
      this.bgConfig.height = SEATS_DISTANCE * (this.rows - 1) + STAGE_PADDING * 2 + SEAT_SIZE
      this.configKonva.width = 0
      this.configKonva.height = 0

      this.$nextTick(() => {
        const { offsetWidth: width, offsetHeight: height } = this.$refs.container

        this.configKonva.width = width
        this.configKonva.height = height

        const clientRect = this.stage.getClientRect({ skipTransform: true })

        this.clientRect = clientRect

        // 根据容器进行缩放
        if (width / height >= clientRect.width / clientRect.height) {
          this.newScale = height / (clientRect.height)
          this.configKonva.y = 0
          this.configKonva.x = (width - clientRect.width * this.newScale) / 2
        } else {
          this.newScale = width / (clientRect.width)
          this.configKonva.x = 0
          this.configKonva.y = (height - clientRect.height * this.newScale) / 2
        }

        this.configKonva.scaleX = this.newScale
        this.configKonva.scaleY = this.newScale

        this.initStageSize = JSON.parse(JSON.stringify(this.configKonva))
      })
    },
    createSeats () {
      const seats = Array(this.columns * this.rows).fill(configCircle)
      this.seatMap = {}

      this.seats = seats.map((item, index) => {
        const seat = Object.assign({}, item)
        const xIndex = (index) % this.columns
        const yIndex = Math.floor(index / this.columns)

        seat.location = {
          x: xIndex,
          y: yIndex
        }

        seat.x = STAGE_PADDING + SEATS_DISTANCE * xIndex + SEAT_SIZE / 2
        seat.y = STAGE_PADDING + SEATS_DISTANCE * yIndex + SEAT_SIZE / 2
        seat.id = index

        // 座位定位表
        this.seatMap[`seat_${xIndex}_${yIndex}`] = seat

        return seat
      })
    },
    setRowText () {
      const rowText = Array(this.rows).fill({
        x: 0,
        y: 0,
        text: 0,
        fill: 'rgb(48,77,121)'
      })

      this.rowText1 = rowText.map((item, index) => {
        const text = Object.assign({}, item)

        text.text = index + 1
        text.x = STAGE_PADDING - SEATS_DISTANCE
        text.y = STAGE_PADDING + SEATS_DISTANCE * index
        text.height = SEAT_SIZE
        text.fontSize = SEAT_SIZE
        text.verticalAlign = 'middle'

        return text
      })

      this.rowText2 = rowText.map((item, index) => {
        const text = Object.assign({}, item)

        text.text = index + 1
        text.x = STAGE_PADDING + SEATS_DISTANCE * this.columns
        text.y = STAGE_PADDING + SEATS_DISTANCE * index
        text.height = SEAT_SIZE
        text.fontSize = SEAT_SIZE
        text.verticalAlign = 'middle'

        return text
      })
    },
    setColText () {
      const colText = Array(this.columns).fill({
        x: 0,
        y: 0,
        text: 0,
        fill: 'rgb(48,77,121)'
      })

      this.colText1 = colText.map((item, index) => {
        const text = Object.assign({}, item)

        text.text = index + 1
        text.x = STAGE_PADDING + SEATS_DISTANCE * index + SEAT_SIZE / 2 - SEATS_DISTANCE / 2
        text.y = STAGE_PADDING + SEATS_DISTANCE * this.rows
        text.fontSize = SEAT_SIZE
        text.height = SEATS_DISTANCE
        text.width = SEATS_DISTANCE
        text.align = 'center'

        return text
      })

      this.colText2 = colText.map((item, index) => {
        const text = Object.assign({}, item)

        text.text = index + 1
        text.x = STAGE_PADDING + SEATS_DISTANCE * index + SEAT_SIZE / 2 - SEATS_DISTANCE / 2
        text.y = STAGE_PADDING - SEATS_DISTANCE
        text.fontSize = SEAT_SIZE
        text.height = SEATS_DISTANCE
        text.width = SEATS_DISTANCE
        text.align = 'center'

        return text
      })
    },
    handleWheel (e) {
      if (this.configSelection.visible) return

      const scaleBy = this.scaleBy
      e.evt.preventDefault()

      var oldScale = this.stage.scaleX()

      var pointer = this.stage.getPointerPosition()

      var mousePointTo = {
        x: (pointer.x - this.stage.x()) / oldScale,
        y: (pointer.y - this.stage.y()) / oldScale
      }

      this.newScale =
          e.evt.deltaY > 0 ? oldScale * scaleBy : oldScale / scaleBy

      this.configKonva = Object.assign({}, this.configKonva, {
        x: pointer.x - mousePointTo.x * this.newScale,
        y: pointer.y - mousePointTo.y * this.newScale,
        scaleX: this.newScale,
        scaleY: this.newScale
      })
    },
    handleStageClick (e) {
      const { x1, y1, x2, y2 } = this.selectPosition

      // 区分点击还是画框
      if (Math.abs(x1 - x2) >= 5 || Math.abs(y1 - y2) >= 5) {
        return
      }

      this.seats.forEach(seat => {
        seat.isSelected = false
        seat.strokeWidth = 0
      })

      if (e.target.className !== 'Circle') {

      } else {
        const target = this.seats.find(i => e.target.attrs.id === i.id)
        target.isSelected = true
        target.strokeWidth = 3
      }
    },
    handleMouseDown (e) {
      if (this.configKonva.draggable) return

      const pos = this.stage.getPointerPosition()

      this.selectPosition = {
        x1: pos.x,
        y1: pos.y,
        x2: pos.x,
        y2: pos.y
      }

      this.configSelection = Object.assign({}, this.configSelection, {
        // visible: true,
        width: 0,
        height: 0
      })

      this.selectIsStart = true
    },
    handleMouseMove: throttle(function () {
      // 参考线
      // if (this.configSelection.visible) {
      //   this.colLine.visible = false
      //   this.rowLine.visible = false
      // } else {
      //   this.setLinePosition()
      // }

      if (this.configKonva.draggable) return

      this.selectPosition.x2 = this.stage.getPointerPosition().x
      this.selectPosition.y2 = this.stage.getPointerPosition().y

      const { x1, y1, x2, y2 } = this.selectPosition
      // 舞台偏移
      const { x, y } = this.stage.absolutePosition()

      if (!this.selectIsStart) return

      if (Math.abs(x1 - x2) >= 3 && Math.abs(y1 - y2) >= 3) {
        this.configSelection.visible = true
      }

      if (!this.configSelection.visible) return

      this.configSelection = Object.assign({}, this.configSelection, {
        x: (Math.min(x1, x2) - x) / this.newScale,
        y: (Math.min(y1, y2) - y) / this.newScale,
        width: Math.abs(x2 - x1) / this.newScale,
        height: Math.abs(y2 - y1) / this.newScale
      })
    }, 50),
    // 设置参考线位置
    setLinePosition: throttle(function () {
      // 舞台偏移
      const { x, y } = this.stage.absolutePosition()

      const { x: currentX, y: currentY } = this.stage.getPointerPosition()

      this.colLine.points = [(currentX - x) / this.newScale, 0, (currentX - x) / this.newScale, this.clientRect.height]

      if (currentX - x <= 0 || (currentX - x) >= this.clientRect.width * this.newScale) {
        this.colLine.visible = false
      } else {
        this.colLine.visible = true
      }

      this.rowLine.points = [0, (currentY - y) / this.newScale, this.clientRect.width, (currentY - y) / this.newScale]

      if (currentY - y <= 0 || (currentY - y) >= this.clientRect.height * this.newScale) {
        this.rowLine.visible = false
      } else {
        this.rowLine.visible = true
      }
    }, 50),
    handleMouseUp () {
      this.selectIsStart = false

      if (!this.configSelection.visible) return

      setTimeout(() => {
        this.configSelection.visible = false
        this.selectPosition = {
          x1: 0, y1: 0, x2: 0, y2: 0
        }
      }, 50)

      const { x1, y1, x2, y2 } = this.selectPosition

      if (Math.abs(x1 - x2) <= 5 && Math.abs(y1 - y2) <= 5) {
        return
      }
      const seatsEl = this.$refs.seat.map(item => {
        return item.getNode()
      })

      const box = this.selectionEl.getClientRect()

      // 选中的元素
      const selected = seatsEl.filter(seat => {
        return Konva.Util.haveIntersection(box, seat.getClientRect())
      })

      selected.forEach(el => {
        let config

        config = this.seats.find(i => i.id === el.attrs.id)

        // 查找绑定的座位配置，更新视图，选择空座位
        if (this.currentTool === 'blankSelect') {
          config = this.seats.find(i => i.id === el.attrs.id && !i.area_id)
        }

        // 选择现存座位
        if (this.currentTool === 'seatSelect') {
          config = this.seats.find(i => i.id === el.attrs.id && i.area_id)
        }

        if (!config) return

        // 框选移除选择
        if (this.currentTool === 'removeSelect') {
          // 切换选中
          config.isSelected = false
          config.strokeWidth = 0
          // config.fill = defautColor
          return
        }

        // 切换选中
        config.isSelected = true
        config.strokeWidth = 3
        // config.fill = 'rgba(74,144,226,0.5)' // blue
        // config.fill = 'rgba(126,211,33,0.5)' // green
      })
    },
    handleKeyDown (e) {
      if (e.code === 'Space') {
        e.preventDefault()
        this.configKonva.draggable = true
      }
    },
    handleKeyUp (e) {
      if (e.code === 'Space') {
        e.preventDefault()
        this.configKonva.draggable = false
      }
    },
    handleSeatMouseEnter (e) {
      this.popTimer && clearTimeout(this.popTimer)

      // 无区域id的不显示浮层
      if (!e.target.attrs.area_id) return

      const position = e.target.getAbsolutePosition()

      this.popupStyle = {
        left: 20 + position.x + 'px',
        top: 20 + position.y + 'px'
      }

      this.showPop = true

      this.popInfo = e.target.attrs
    },
    handleSeatMouseLeave (e) {
      this.popTimer && clearTimeout(this.popTimer)

      this.showPop = false

      this.popTimer = setTimeout(() => {
        this.popInfo = {}
      }, 500)
    },
    handleDragEnd (e) {
      this.configKonva.x = this.stage.attrs.x
      this.configKonva.y = this.stage.attrs.y
    },
    handleToolChange (t) {
      this.currentTool = t.value

      switch (t.value) {
        case 'blankSelect':
          this.seats.forEach(seat => {
            if (seat.area_id) {
              seat.strokeWidth = 0
              seat.isSelected = false
            }
          })
          break
        case 'seatSelect':
          this.seats.forEach(seat => {
            if (!seat.area_id) {
              seat.strokeWidth = 0
              seat.isSelected = false
            }
          })
          break
        case 'resetSize':
          this.configKonva = Object.assign({}, this.configKonva, this.initStageSize)
      }
    }
  },
  mounted () {
    this.initSize()

    window.addEventListener('resize', this.initSize)

    document.addEventListener('keydown', this.handleKeyDown)

    document.addEventListener('keyup', this.handleKeyUp)
  },
  beforeDestroy () {
    window.removeEventListener('resize', this.initSize)

    document.removeEventListener('keydown', this.handleKeyDown)

    document.removeEventListener('keyup', this.handleKeyUp)
  },
  watch: {
    data: {
      handler (val) {
        this.reset()
        this.initStageSize = null

        this.$nextTick(() => {
          this.initSize()
        })

        if (val.length) {
          val.forEach(seat => {
            const target = this.seatMap[`seat_${seat.x}_${seat.y}`]

            if (target) {
              target.fill = seat.fill
              target.area_id = seat.area_id
              target.seat_id = seat.id
              target.row_tag = seat.row_tag
              target.seatName = seat.seatName
              target.text = seat.name
              target.isSelected = false
              target.strokeWidth = 0
            }
          })
        }
      },
      immediate: true
    }
  },
  selected (val) {
    this.$nextTick(() => {
      this.group.cache()
      this.group.getLayer().batchDraw()
    })
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
.seatChart {
  position: relative;  width: 100%;height: 100%; overflow: hidden;
}

.toolbar {
  position: absolute; top: 50%;right: 10px;transform: translateY(-50%); background: #fff; border-radius: 5px; box-shadow: 0 0 10px #ddd;
  li{
    border-bottom: 1px solid #ddd; width: 50px;height: 50px; display: flex; align-items: center; justify-content: center;
  }
  li.active{
    background: rgba(74, 144, 226,0.5);
  }
}

.popup {
  position: absolute; z-index: 999;  min-width: 150px; min-height: 50px;overflow: hidden;line-height: 1.8; pointer-events: none; background: #fff; border: 1px solid rgb(158, 158, 158);border-radius: 5px ; box-shadow: 0 0 10px rgba(0,0,0,.5); opacity: 0;transition: all .3s;
}
.popup .title {
  padding: 0 5px; color: #555; text-align: center;  background: #eee; border-bottom: 1px solid #ddd;
}
.popup .content {
  padding: 5px 10px;
}
.popup.show {
  opacity: 1;
}

</style>
