<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 "./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: 60,
      validator(val) {
        return val >= 0;
      }
    },
    maxWidth: {
      type: Number,
      default: 0,
      validator(val) {
        return val >= 0;
      }
    },
    minHeight: {
      type: Number,
      default: 40,
      validator(val) {
        return val >= 0;
      }
    },
    maxHeight: {
      type: Number,
      default: 0,
      validator(val) {
        return val >= 0;
      }
    },
    fitParent: {
      type: Array,
      default() {
        return ["w", "h"];
      }
    },
    isCompact: {
      type: Boolean,
      default: false
    },
    elementPk: {
      type: String,
      default: ""
    }
  },
  data() {
    return {
      sizePosOrigin: {
        x: 0,
        y: 0,
        w: 0,
        h: 0
      },
      offsetTopOrigin: 0,
      sizePos: {
        x: 0,
        y: 0,
        w: 0,
        h: 0
      },
      lastSizePos: {},
      initOver: false,
      controlPoints: [],
      positionType: "static",
      parentNodeSize: {
        w: 0,
        h: 0
      },
      validSizePos: {},
      points: {}
    };
  },
  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,
          "element-resize-compact": this.isCompact
        }
      ];
    },
    fitParentTypeWidth() {
      return this.fitParent.includes("w");
    },
    fitParentTypeHeight() {
      return this.fitParent.includes("h");
    }
  },
  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.lastSizePos = { ...this.value };
      this.sizePosOrigin = { ...this.value };
      if (this.positionType === "relative")
        this.offsetTopOrigin = this.$el.offsetTop;
      this.initValidSizePos();
      this.initOver = true;
    },
    initValidSizePos() {
      const s = this.sizePosOrigin;
      const points = {
        rt: {
          x: s.x + s.w,
          y: s.y
        },
        lt: {
          x: s.x,
          y: s.y
        },
        lb: {
          x: s.x,
          y: s.y + s.h
        },
        rb: {
          x: s.x + s.w,
          y: s.y + s.h
        }
      };
      const action = {
        rt: () => {
          const point = points.rt;
          return {
            min: {
              x: point.x - this.minWidth,
              y: point.y
            },
            max: {
              x: point.x - this.maxWidth,
              y: point.y
            }
          };
        },
        lt: () => {
          const point = points.lt;
          return {
            min: {
              x: point.x,
              y: point.y
            },
            max: {
              x: point.x,
              y: point.y
            }
          };
        },
        lb: () => {
          const point = points.lb;
          return {
            min: {
              x: point.x,
              y: point.y - this.minHeight
            },
            max: {
              x: point.x,
              y: point.y - this.maxHeight
            }
          };
        },
        rb: () => {
          const point = points.rb;
          return {
            min: {
              x: point.x - this.minWidth,
              y: point.y - this.minHeight
            },
            max: {
              x: point.x - this.maxWidth,
              y: point.y - this.maxHeight
            }
          };
        }
      };

      this.validSizePos = {
        rt: action.rt(),
        lt: action.lt(),
        lb: action.lb(),
        rb: action.rb()
      };
    },
    fetchValidSizePos(sizePos, actionType) {
      const staticPointConfig = {
        left: "rt",
        "left-bottom": "rt",
        bottom: "lt",
        "right-bottom": "lt",
        right: "lb",
        "right-top": "lb",
        top: "rb",
        "left-top": "rb"
      };
      const validSizePos = this.validSizePos[staticPointConfig[actionType]];

      if (sizePos.w <= this.minWidth) {
        sizePos.w = this.minWidth;
        sizePos.x = validSizePos.min.x;
      } else if (this.maxWidth !== 0 && sizePos.w >= this.maxWidth) {
        sizePos.w = this.maxWidth;
        sizePos.x = validSizePos.max.x;
      }

      if (sizePos.h <= this.minHeight) {
        sizePos.h = this.minHeight;
        sizePos.y = validSizePos.min.y;
      } else if (this.maxHeight !== 0 && sizePos.h >= this.maxHeight) {
        sizePos.h = this.maxHeight;
        sizePos.y = validSizePos.max.y;
      }

      if (!this.fitParent.length) {
        this.lastSizePos = { ...sizePos };
        return sizePos;
      }

      // 不同的定位方式，计算方式有差异
      this.parentNodeSize = {
        w: this.$el.offsetParent.offsetWidth,
        h: this.$el.offsetParent.offsetHeight
      };

      if (this.fitParentTypeWidth) {
        if (sizePos.x <= 0) {
          sizePos.x = 0;
          if (actionType.includes("left")) sizePos.w = this.lastSizePos.w;
        }

        if (sizePos.x + sizePos.w > this.parentNodeSize.w) {
          sizePos.x = this.lastSizePos.x;
          sizePos.w = this.parentNodeSize.w - sizePos.x;
        }
      }

      if (this.fitParentTypeHeight) {
        if (this.positionType === "relative") {
          const elOffsetTop = this.$el.offsetTop;
          if (this.sizePosOrigin.y - sizePos.y >= this.offsetTopOrigin) {
            sizePos.h = this.lastSizePos.h;
            sizePos.y = this.sizePosOrigin.y - this.offsetTopOrigin;
          }
          if (elOffsetTop + sizePos.h >= this.parentNodeSize.h) {
            sizePos.y = this.lastSizePos.y;
            sizePos.h = this.lastSizePos.h;
          }
        } else {
          if (sizePos.y <= 0) {
            sizePos.y = 0;
            if (actionType.includes("top")) sizePos.h = this.lastSizePos.h;
          }
          if (sizePos.y + sizePos.h > this.parentNodeSize.h) {
            sizePos.y = this.lastSizePos.y;
            sizePos.h = this.parentNodeSize.h - sizePos.y;
          }
        }
      }
      this.lastSizePos = { ...sizePos };
      return sizePos;
    },
    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 }) {
      console.log(this.sizePosOrigin);
      const sp = { ...this.sizePos, ...this.getLeftSize(left) };
      this.sizePos = { ...this.fetchValidSizePos(sp, "left") };
      this.emitChange();
    },
    moveRightPoint({ left }) {
      const sp = { ...this.sizePos, ...this.getRightSize(left) };
      this.sizePos = { ...this.fetchValidSizePos(sp, "right") };
      this.emitChange();
    },
    moveTopPoint({ top }) {
      const sp = { ...this.sizePos, ...this.getTopSize(top) };
      this.sizePos = { ...this.fetchValidSizePos(sp, "top") };
      this.emitChange();
    },
    moveBottomPoint({ top }) {
      const sp = { ...this.sizePos, ...this.getBottomSize(top) };
      this.sizePos = { ...this.fetchValidSizePos(sp, "bottom") };
      this.emitChange();
    },
    moveLeftTopPoint({ left, top }) {
      const sp = {
        ...this.sizePos,
        ...this.getLeftSize(left),
        ...this.getTopSize(top)
      };
      this.sizePos = { ...this.fetchValidSizePos(sp, "left-top") };
      this.emitChange();
    },
    moveRightTopPoint({ left, top }) {
      const sp = {
        ...this.sizePos,
        ...this.getRightSize(left),
        ...this.getTopSize(top)
      };
      this.sizePos = { ...this.fetchValidSizePos(sp, "right-top") };
      this.emitChange();
    },
    moveLeftBottomPoint({ left, top }) {
      const sp = {
        ...this.sizePos,
        ...this.getLeftSize(left),
        ...this.getBottomSize(top)
      };
      this.sizePos = { ...this.fetchValidSizePos(sp, "left-bottom") };
      this.emitChange();
    },
    moveRightBottomPoint({ left, top }) {
      const sp = {
        ...this.sizePos,
        ...this.getRightSize(left),
        ...this.getBottomSize(top)
      };
      this.sizePos = { ...this.fetchValidSizePos(sp, "right-bottom") };
      this.emitChange();
    },
    moveOver() {
      this.sizePosOrigin = { ...this.sizePos };
      this.lastSizePos = { ...this.sizePos };
      if (this.positionType === "relative")
        this.offsetTopOrigin = this.$el.offsetTop < 0 ? 0 : this.$el.offsetTop;

      this.initValidSizePos();
      this.$emit("resize-over", this.sizePos);
    },
    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
        }
      };
      this.sizePos = { ...this.fetchValidSizePos(sp, "move") };
      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: #3a5ae5;
    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 #3a5ae5;
    }
    &-right {
      height: 100%;
      right: -1px;
      top: 0;
      border-left: 1px solid #3a5ae5;
    }
    &-top {
      width: 100%;
      left: 0;
      top: -1px;
      border-top: 1px solid #3a5ae5;
    }
    &-bottom {
      width: 100%;
      left: 0;
      bottom: -1px;
      border-top: 1px solid #3a5ae5;
    }
  }

  &-compact {
    .control-line {
      &-left {
        left: 0;
        border-left: 1px solid #bbb;
      }
      &-right {
        right: 0;
        border-left: 1px solid #bbb;
      }
      &-top {
        top: 0;
        border-top: 1px solid #bbb;
      }
      &-bottom {
        bottom: 0;
        border-top: 1px solid #bbb;
      }
    }
  }
}
</style>
