<template>
  <div
    :class="classes"
    :style="styles"
    v-move-ele.prevent.stop="{
      moveStart,
      moveElement,
      moveStop: moveElementOver
    }"
  >
    <slot></slot>
    <div class="resize-control">
      <div
        v-for="(control, index) in controlPoints"
        :key="index"
        :class="control.classes"
        v-move-ele.prevent.stop="{
          moveElement: control.movePoint,
          moveStop: control.movePointOver
        }"
      ></div>
      <div class="control-line control-line-left"></div>
      <div class="control-line control-line-right"></div>
      <div class="control-line control-line-top"></div>
      <div class="control-line control-line-bottom"></div>
    </div>
  </div>
</template>

<script>
import MoveEle from "../../directives/move-ele";

export default {
  name: "element-resize",
  directives: { MoveEle },
  props: {
    value: {
      type: Object,
      required: true
    },
    active: {
      type: Array,
      default() {
        return ["r", "rb", "b", "lb", "l", "lt", "t", "rt"];
      }
    },
    move: {
      type: Boolean,
      default: true
    },
    minWidth: {
      type: Number,
      default: 0,
      validator(val) {
        return val >= 0;
      }
    },
    maxWidth: {
      type: Number,
      default: 0,
      validator(val) {
        return val >= 0;
      }
    },
    minHeight: {
      type: Number,
      default: 0,
      validator(val) {
        return val >= 0;
      }
    },
    maxHeight: {
      type: Number,
      default: 0,
      validator(val) {
        return val >= 0;
      }
    },
    fitParent: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      sizePosOrigin: {
        x: 0,
        y: 0,
        w: 0,
        h: 0
      },
      sizePos: {
        x: 0,
        y: 0,
        w: 0,
        h: 0
      },
      initOver: false,
      controlPoints: [],
      positionType: "static",
      parentNodeSize: {
        w: 0,
        h: 0
      }
    };
  },
  computed: {
    styles() {
      return this.initOver
        ? {
            left: this.sizePos.x + "px",
            top: this.sizePos.y + "px",
            width: this.sizePos.w + "px",
            height: this.sizePos.h + "px",
            position: this.positionType
          }
        : {};
    },
    classes() {
      return [
        "element-resize",
        {
          "element-resize-move": this.move,
          "element-resize-init": this.initOver
        }
      ];
    }
  },
  created() {
    this.initControlPoints();
  },
  mounted() {
    this.initSize();
  },
  methods: {
    initControlPoints() {
      const posName = {
        l: "Left",
        r: "Right",
        t: "Top",
        b: "Bottom"
      };
      this.controlPoints = this.active.map(type => {
        const posFullName = type
          .split("")
          .map(item => {
            return posName[item];
          })
          .join("");
        return {
          classes: ["control-point", `control-point-${type}`],
          movePoint: this[`move${posFullName}Point`],
          movePointOver: this.moveOver
        };
      });
    },
    initSize() {
      const resizeDom = this.$el.childNodes[0];
      this.positionType = window.getComputedStyle(resizeDom).position;
      this.sizePos = { ...this.value };
      this.sizePosOrigin = { ...this.sizePos };
      this.initOver = true;
    },
    checkValidSizePos(sizePos) {
      if (
        sizePos.w < this.minWidth ||
        (this.maxWidth !== 0 && sizePos.w > this.maxWidth)
      )
        return false;

      if (
        sizePos.h < this.minHeight ||
        (this.maxHeight !== 0 && sizePos.h > this.maxHeight)
      )
        return false;

      // 不同的定位方式，计算方式有差异
      const elOffsetTop =
        this.positionType === "relative" ? this.$el.offsetTop : sizePos.y;
      this.parentNodeSize = {
        w: this.$el.offsetParent.offsetWidth,
        h: this.$el.offsetParent.offsetHeight
      };

      if (this.fitParent) {
        if (sizePos.x < 0 || elOffsetTop < 0) return false;

        if (
          sizePos.x + sizePos.w > this.parentNodeSize.w ||
          elOffsetTop + sizePos.h > this.parentNodeSize.h
        )
          return false;
      }

      return true;
    },
    getLeftSize(left) {
      return {
        w: -left + this.sizePosOrigin.w,
        x: left + this.sizePosOrigin.x
      };
    },
    getRightSize(left) {
      return {
        w: left + this.sizePosOrigin.w
      };
    },
    getTopSize(top) {
      return {
        h: -top + this.sizePosOrigin.h,
        y: top + this.sizePosOrigin.y
      };
    },
    getBottomSize(top) {
      return {
        h: top + this.sizePosOrigin.h
      };
    },
    moveLeftPoint({ left }) {
      const sp = { ...this.sizePos, ...this.getLeftSize(left) };
      if (this.checkValidSizePos(sp)) {
        this.sizePos = { ...sp };
        this.emitChange();
      }
    },
    moveRightPoint({ left }) {
      const sp = { ...this.sizePos, ...this.getRightSize(left) };
      if (this.checkValidSizePos(sp)) {
        this.sizePos = { ...sp };
        this.emitChange();
      }
    },
    moveTopPoint({ top }) {
      const sp = { ...this.sizePos, ...this.getTopSize(top) };
      if (this.checkValidSizePos(sp)) {
        this.sizePos = { ...sp };
        this.emitChange();
      }
    },
    moveBottomPoint({ top }) {
      const sp = { ...this.sizePos, ...this.getBottomSize(top) };
      if (this.checkValidSizePos(sp)) {
        this.sizePos = { ...sp };
        this.emitChange();
      }
    },
    moveLeftTopPoint({ left, top }) {
      const sp = {
        ...this.sizePos,
        ...this.getLeftSize(left),
        ...this.getTopSize(top)
      };
      if (this.checkValidSizePos(sp)) {
        this.sizePos = { ...sp };
        this.emitChange();
      }
    },
    moveRightTopPoint({ left, top }) {
      const sp = {
        ...this.sizePos,
        ...this.getRightSize(left),
        ...this.getTopSize(top)
      };
      if (this.checkValidSizePos(sp)) {
        this.sizePos = { ...sp };
        this.emitChange();
      }
    },
    moveLeftBottomPoint({ left, top }) {
      const sp = {
        ...this.sizePos,
        ...this.getLeftSize(left),
        ...this.getBottomSize(top)
      };
      if (this.checkValidSizePos(sp)) {
        this.sizePos = { ...sp };
        this.emitChange();
      }
    },
    moveRightBottomPoint({ left, top }) {
      const sp = {
        ...this.sizePos,
        ...this.getRightSize(left),
        ...this.getBottomSize(top)
      };
      if (this.checkValidSizePos(sp)) {
        this.sizePos = { ...sp };
        this.emitChange();
      }
    },
    moveOver() {
      this.sizePosOrigin = { ...this.sizePos };
      this.$emit("resize-over");
    },
    moveStart() {
      this.$emit("on-click");
    },
    moveElement({ left, top }) {
      if (!this.move) return;
      const sp = {
        ...this.sizePos,
        ...{
          x: left + this.sizePosOrigin.x,
          y: top + this.sizePosOrigin.y
        }
      };
      if (this.checkValidSizePos(sp)) {
        this.sizePos = { ...sp };
        this.emitChange();
      }
    },
    moveElementOver() {
      if (!this.move) return;
      this.moveOver();
    },
    emitChange() {
      this.$emit("input", this.sizePos);
      this.$emit("change", this.sizePos);
    }
  }
};
</script>

<style lang="scss" scope>
.element-resize {
  position: static;
  z-index: auto;
  background: #fff;
  box-sizing: content-box;

  &-move {
    cursor: move;
  }

  &-init {
    > div:first-child {
      width: 100% !important;
      height: 100% !important;
      position: relative !important;
      top: 0 !important;
      left: 0 !important;
      overflow: hidden;
    }
  }
  .control-point {
    position: absolute;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #00a2fe;
    z-index: 99;
    &-l {
      left: 0;
      top: 50%;
      width: 5px;
      height: 20px;
      margin-top: -10px;
      margin-left: -3px;
      border-radius: 0;
      padding-top: 3px;
      cursor: w-resize;
      text-align: center;
      color: #fff;

      &::before {
        content: ".";
        display: block;
        font-size: 16px;
        line-height: 1;
        margin-top: -9px;
      }
      &::after {
        content: ".";
        display: block;
        font-size: 16px;
        line-height: 1;
        margin-top: -9px;
      }
    }
    &-lt {
      left: 0;
      top: 0;
      margin-top: -5px;
      margin-left: -5px;
      cursor: nw-resize;
    }
    &-lb {
      left: 0;
      bottom: 0;
      margin-bottom: -5px;
      margin-left: -5px;
      cursor: sw-resize;
    }
    &-r {
      right: 0;
      top: 50%;
      width: 5px;
      height: 20px;
      margin-top: -10px;
      margin-right: -3px;
      cursor: e-resize;
      border-radius: 0;
      padding-top: 3px;
      text-align: center;
      color: #fff;

      &::before {
        content: ".";
        display: block;
        font-size: 16px;
        line-height: 1;
        margin-top: -9px;
      }
      &::after {
        content: ".";
        display: block;
        font-size: 16px;
        line-height: 1;
        margin-top: -9px;
      }
    }
    &-rt {
      right: 0;
      top: 0;
      margin-top: -5px;
      margin-right: -5px;
      cursor: ne-resize;
    }
    &-rb {
      right: 0;
      bottom: 0;
      margin-bottom: -5px;
      margin-right: -5px;
      cursor: se-resize;
    }
    &-t {
      left: 50%;
      top: 0;
      width: 30px;
      height: 5px;
      border-radius: 0;
      margin-top: -3px;
      margin-left: -15px;
      cursor: n-resize;
      text-align: center;
      color: #fff;
      &::before {
        content: "...";
        display: inline-block;
        vertical-align: top;
        font-size: 16px;
        line-height: 1;
        margin-top: -10px;
      }
    }
    &-b {
      left: 50%;
      bottom: 0;
      width: 30px;
      height: 5px;
      border-radius: 0;
      margin-bottom: -3px;
      margin-left: -15px;
      cursor: s-resize;
      text-align: center;
      color: #fff;

      &::before {
        content: "...";
        display: inline-block;
        vertical-align: top;
        font-size: 16px;
        line-height: 1;
        margin-top: -10px;
      }
    }
  }
  .control-line {
    position: absolute;
    z-index: 98;

    &-left {
      height: 100%;
      left: -1px;
      top: 0;
      border-left: 1px solid #4794b3;
    }
    &-right {
      height: 100%;
      right: -1px;
      top: 0;
      border-left: 1px solid #4794b3;
    }
    &-top {
      width: 100%;
      left: 0;
      top: -1px;
      border-top: 1px solid #4794b3;
    }
    &-bottom {
      width: 100%;
      left: 0;
      bottom: -1px;
      border-top: 1px solid #4794b3;
    }
  }
}
</style>
