<template>
  <div class="area-cropper">
    <div class="cropper-img">
      <img
        v-if="paper.imgUrl"
        ref="imgDom"
        :src="paper.imgUrl"
        @load="imgLoad"
      />
    </div>
    <div
      class="cropper-areas"
      v-move-ele.prevent="{
        moveStart: ($event) => {
          boxMouseDown($event);
        },
        moveElement: (pos, $event) => {
          boxMove($event);
        },
        moveStop: () => {
          boxMoveStop();
        },
      }"
    >
      <area-item
        v-for="area in areas"
        :key="area.key"
        :data="area"
        :cur-element="curArea"
        @del-element="removeArea"
        @act-element="actCurArea"
        @resize-over="modifyArea"
      ></area-item>
      <!-- 拖动选框 -->
      <div
        v-if="selectionStyles.w"
        class="area-selection"
        :style="{
          width: selectionStyles.w + 'px',
          height: selectionStyles.h + 'px',
          top: selectionStyles.y + 'px',
          left: selectionStyles.x + 'px',
        }"
      ></div>
    </div>
  </div>
</template>

<script>
import MoveEle from "./move-ele";
import AreaItem from "./ElementItem.vue";
import { randomCode } from "@/plugins/utils";

export default {
  name: "area-cropper",
  components: { AreaItem },
  directives: { MoveEle },
  props: {
    paper: {
      type: Object,
      default() {
        return {
          imgUrl: "",
          areas: [],
        };
      },
    },
  },
  data() {
    return {
      areas: [],
      initArea: { id: null, i: 0, x: 0, y: 0, w: 0, h: 0, zIndex: 0 },
      curArea: {},
      selectionStartPos: { x: 0, y: 0 },
      selectionStyles: { x: 0, y: 0, w: 0, h: 0 },
      IS_SELECT_ACTION: false,
      imgDisplayWidth: 0,
      imgDisplayHeight: 0,
    };
  },
  mounted() {
    window.addEventListener("resize", this.windowResizeEvent);
    document.addEventListener("keydown", this.keyEvent);
  },
  methods: {
    getMax(arr) {
      return Math.max.apply(null, arr);
    },
    toFixed(num, precision = 2) {
      return num.toFixed(precision) * 1;
    },
    imgLoad() {
      this.transformPicConfig(this.paper.areas);
      this.$emit("paper-load");
    },
    windowResizeEvent() {
      const { clientWidth, clientHeight } = this.$refs.imgDom;
      const hRate = clientHeight / this.imgDisplayHeight;
      const wRate = clientWidth / this.imgDisplayWidth;
      this.areas = this.areas.map((area) => {
        return Object.assign({}, area, {
          key: `key-${randomCode()}`,
          x: area.x * wRate,
          y: area.y * hRate,
          w: area.w * wRate,
          h: area.h * hRate,
        });
      });
      this.imgDisplayWidth = clientWidth;
      this.imgDisplayHeight = clientHeight;
    },
    keyEvent(e) {
      if (
        e.code === "Delete" &&
        !e.ctrlKey &&
        !e.altKey &&
        !e.shiftKey &&
        !e.repeat
      ) {
        if (!this.curArea.id) return;
        e.preventDefault();
        this.removeArea(this.curArea);
        return;
      }
    },
    transformPicConfig(areas) {
      const { clientWidth, clientHeight } = this.$refs.imgDom;

      this.areas = areas.map((area, index) => {
        let narea = {
          id: `id-${randomCode()}`,
          key: `key-${randomCode()}`,
          zIndex: index + 99,
        };
        if (Object.keys(area).join("") === "i") {
          narea = {
            ...narea,
            x: 0,
            y: 0,
            w: clientWidth,
            h: clientHeight,
          };
        } else {
          narea = {
            ...narea,
            x: area.x * clientWidth,
            y: area.y * clientHeight,
            w: area.w * clientWidth,
            h: area.h * clientHeight,
          };
        }
        return narea;
      });
      this.imgDisplayWidth = clientWidth;
      this.imgDisplayHeight = clientHeight;
    },
    addArea(data) {
      let area = Object.assign({}, this.initArea, data);
      const maxZIndex = this.areas.length
        ? this.getMax(this.areas.map((elem) => elem.zIndex))
        : 0;
      area.id = `id-${randomCode()}`;
      area.key = `key-${randomCode()}`;
      area.zIndex = maxZIndex + 1;
      this.areas.push(area);
      this.actCurArea(area);
      this.emitChange();
    },
    modifyArea(area) {
      const pos = this.areas.findIndex((elem) => elem.id === area.id);
      this.areas.splice(pos, 1, area);
      this.actCurArea(area);
      this.emitChange();
    },
    removeArea(area) {
      const pos = this.areas.findIndex((elem) => elem.id === area.id);
      this.areas.splice(pos, 1);
      if (this.areas.length) this.actCurArea(this.areas[0]);
      this.emitChange();
    },
    actCurArea(area) {
      this.curArea = area;
      this.$emit("curarea-change", area);
    },
    curareaChange(area) {
      if (area.id !== this.curArea.id) this.curArea = {};
    },
    emitChange() {
      const { clientWidth, clientHeight } = this.$refs.imgDom;

      const areas = this.areas.map((item) => {
        return {
          id: item.id,
          x: this.toFixed(item.x / clientWidth, 4),
          y: this.toFixed(item.y / clientHeight),
          w: this.toFixed(item.w / clientWidth, 4),
          h: this.toFixed(item.h / clientHeight),
          // 是否覆盖整个页面，允许2px的误差
          isFull:
            item.x <= 2 &&
            item.y <= 2 &&
            item.w + 2 >= clientWidth &&
            item.h + 2 >= clientHeight,
        };
      });

      this.$emit("change", areas);
    },
    getOffsetInfo(dom, endParentClass = "cropper-areas") {
      let parentNode = dom;
      let parentNodeClass = parentNode.getAttribute("class") || "";
      let offsetTop = 0,
        offsetLeft = 0;
      while (!parentNodeClass.includes(endParentClass)) {
        if (parentNode.offsetParent) {
          offsetTop += parentNode.offsetTop;
          offsetLeft += parentNode.offsetLeft;
          parentNode = parentNode.offsetParent;
        } else {
          offsetTop += parentNode.clientTop;
          offsetLeft += parentNode.clientLeft;
          parentNode = parentNode.parentNode;
        }
        parentNodeClass = parentNode.getAttribute("class") || "";
      }
      return {
        offsetLeft,
        offsetTop,
      };
    },
    boxMouseDown($event) {
      const { offsetLeft: x, offsetTop: y } = this.getOffsetInfo($event.target);
      this.selectionStartPos.x = x + $event.offsetX;
      this.selectionStartPos.y = y + $event.offsetY;
    },
    boxMove($event) {
      const { offsetLeft: x, offsetTop: y } = this.getOffsetInfo($event.target);
      const selectionEndPos = {
        x: x + $event.offsetX,
        y: y + $event.offsetY,
      };
      const sPos = {
        x: Math.min(this.selectionStartPos.x, selectionEndPos.x),
        y: Math.min(this.selectionStartPos.y, selectionEndPos.y),
      };
      const ePos = {
        x: Math.max(this.selectionStartPos.x, selectionEndPos.x),
        y: Math.max(this.selectionStartPos.y, selectionEndPos.y),
      };

      this.selectionStyles = {
        ...sPos,
        w: ePos.x - sPos.x,
        h: ePos.y - sPos.y,
      };
      this.IS_SELECT_ACTION = true;
    },
    boxMoveStop() {
      if (
        this.IS_SELECT_ACTION &&
        this.selectionStyles.w > 20 &&
        this.selectionStyles.h > 20
      )
        this.addArea(this.selectionStyles);

      this.selectionStyles = { x: 0, y: 0, w: 0, h: 0 };
      this.selectionStartPos = { x: 0, y: 0 };
      this.IS_SELECT_ACTION = false;
    },
  },
  beforeDestroy() {
    window.removeEventListener("resize", this.windowResizeEvent);
    document.removeEventListener("keydown", this.keyEvent);
  },
};
</script>
