<!--
 * @FileDescription: vue-konva
 * @Author: hcy
 * @Date: 2023-03-29
-->
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
import { getBoundingBox } from 'src/common/utils';
import { scalePolygon } from './utils';
import Konva from 'konva';
import Decimal from 'decimal.js';

const startSelected = ref(true);

const state = reactive({
  stage: null as any,
  layer: null as any,
  group: new Map(),
  myMap: new Map(),
  // shape: null as any,
  //   boundingBox: null as any,
  //   box: null as any,
  shapePath: [
    {
      name: 'test1',
      color: '#00D2FF',
      selected: true,
      path: [
        { x: 100, y: 100, gap: 10 },
        { x: 200, y: 100, gap: 20 },
        { x: 200, y: 200, gap: 30 },
        { x: 0, y: 300, gap: 40 },
      ],
    },
    // {
    //   name: 'test2',
    //   color: '#4CAF50',
    //   selected: true,
    //   path: [
    //     { x: 300, y: 200 },
    //     { x: 200, y: 400 },
    //     { x: 100, y: 600 },
    //     { x: 400, y: 600 },
    //     { x: 500, y: 200 },
    //   ],
    // },
    // {
    //   name: 'test3',
    //   color: '#FF8A80',
    //   selected: true,
    //   path: [
    //     { x: 600, y: 200 },
    //     { x: 500, y: 400 },
    //     { x: 300, y: 100 },
    //   ],
    // },
    // {
    //   name: 'test4',
    //   color: '#FFC107',
    //   selected: true,
    //   path: [
    //     { x: 1000, y: 0 },
    //     { x: 1100, y: 0 },
    //     { x: 1100, y: 100 },
    //     { x: 1000, y: 100 },
    //   ],
    // },
  ] as any[],
});

const stageSize = reactive({
  width: 1200,
  height: 800,
  gridGap: 100,
});

onMounted(() => {
  // const hit = Collide2D.collidePointPoint(100, 100, 100, 100);
  // console.log('hit', Collide2D);

  drawGrid();
  handleData();

  console.log('数据', state.shapePath);

  state.stage = new Konva.Stage({
    container: 'stage-container',
    width: stageSize.width,
    height: stageSize.height,
  });

  state.layer = new Konva.Layer();
  state.stage.add(state.layer);

  let index = 0;
  for (const item of state.shapePath) {
    let shape = createShape(item);
    if (shape) {
      state.layer.add(shape);
      state.group.set(shape._id, index); // 储存 state.shapePath的下标
      state.myMap.set(item.name, shape._id);
    }

    index++;
  }

  state.layer.on('dragmove', layerDragmove);
});

function createShape(itemData: any) {
  const path = itemData.path || [];
  const fillColor = itemData.color || '#000000';
  const name = itemData.name;
  const boundingBox = itemData.boundingBox || {};
  const boundingBoxPath = itemData.boundingBox?.path || [];

  if (boundingBoxPath.length < 2) {
    console.warn('小于两个点,不能构成多边形,没有边界框');
  } else {
    let group = new Konva.Group({
      x: 0,
      y: 0,
      draggable: true,
    });

    let rectBox = new Konva.Rect({
      x: boundingBoxPath[0].x,
      y: boundingBoxPath[0].y,
      width: boundingBox.width,
      height: boundingBox.height,
      stroke: 'red',
      // fill: 'grey',
      strokeWidth: 2,
    });

    // 如果添加到group,则坐标的相对位置时基于group的位置
    let shape: any = new Konva.Shape({
      sceneFunc: function (context, shape) {
        context.beginPath();

        let index = 0;
        for (const i of path) {
          const x = i.x;
          const y = i.y;
          if (index === 0) {
            context.moveTo(x, y);
          } else {
            context.lineTo(x, y);
          }
          //   context.quadraticCurveTo(150, 100, 260, 170); 弧线
          index++;
        }
        context.closePath();
        // (!) Konva specific method, it is very important
        context.fillStrokeShape(shape);
      },
      fill: fillColor,
      name,
      // stroke: 'white',
      // strokeWidth: 10,
    });

    /* 向外扩大 start */
    const extraPath = scalePolygon(path) as any[];
    // 扩展后的轮廓线
    const shape2: any = new Konva.Shape({
      sceneFunc: function (context, shape) {
        context.beginPath();

        let index = 0;
        for (const i of extraPath) {
          const x = i.x;
          const y = i.y;
          if (index === 0) {
            context.moveTo(x, y);
          } else {
            context.lineTo(x, y);
          }
          //   context.quadraticCurveTo(150, 100, 260, 170); 弧线
          index++;
        }
        context.closePath();
        // (!) Konva specific method, it is very important
        context.fillStrokeShape(shape);
      },
      // fill: fillColor,
      name,
      stroke: '#21BA45',
      strokeWidth: 2,
    });
    /* 向外扩大 end */

    // 先添加的边界框矩形,再添加的自定义图像,
    // 顺序和下面layerDragmove时获取children的顺序保持一致
    group.add(rectBox);
    group.add(shape);

    group.add(shape2);

    return group;
  }
}
function layerDragmove(e: any) {
  const target = e.target;
  const targetRectAttr = target.children[0].attrs;
  // const targetShapeAttr = target.children[1].attrs;

  const diffX = target.attrs.x;
  const diffY = target.attrs.y;

  const taIndex = state.group.get(target._id);
  const ta = state.shapePath[taIndex];

  const _boundingBox = ta.boundingBox;
  _boundingBox.min_x = targetRectAttr.x + diffX;
  _boundingBox.min_y = targetRectAttr.y + diffY;
  // _boundingBox.max_x += diffX;
  // _boundingBox.min_y += diffY;
  // _boundingBox.max_y += diffY;

  // state.layer.children.forEach(function (group: any) {
  //   // do not check intersection with itself
  //   // 不检查与自身相交
  //   if (group._id == target._id) {
  //     return;
  //   }
  // });
}

function handleData() {
  for (const item of state.shapePath) {
    item.path = removeAngleRepeat(item.path || []);
    item.boundingBox = getBoundingBox(item.path || []);
  }
}

/**
 * 1.一条线上有多个点,只保留起点和终点
 * 2.计算边距上的点
 * 注意!path的最后一个点不与第一个点重合
 */
function removeAngleRepeat(path: any[]) {
  let length = path.length;

  if (length >= 2) {
    let myMap = new Map();

    // 计算A点两条邻边相交的点A'
    // 计算过A点垂直于边距线上的点,这样的点计算两个,也就是A点两条邻边上各一点
    //
    let index = 1;
    for (const item of path) {
      let x1, y1, x2, y2;
      // let last_x, last_y, gap_1, gap_2;
      if (index < length) {
        x1 = item.x;
        y1 = item.y;
        x2 = path[index].x;
        y2 = path[index].y;

        // 边距 start1
        // gap_1 = item.gap || 0;
        // if (index === 1) {
        //   // 第一个点,另一条邻边是与最后一个点组成
        //   last_x = path[length - 1].x;
        //   last_y = path[length - 1].y;
        //   gap_2 = path[length - 1].gap;
        // } else {
        //   last_x = path[index - 2].x;
        //   last_y = path[index - 2].y;
        //   gap_2 = path[index - 2].gap;
        // }
        // 边距 end1
      } else {
        // 最后一个点连第一个点
        x1 = item.x;
        y1 = item.y;
        x2 = path[0].x;
        y2 = path[0].y;

        // 边距 start2
        // gap_1 = item.gap || 0;
        // gap_2 = path[index - 2].gap;
        // last_x = path[index - 2].x;
        // last_y = path[index - 2].y;
        // 边距 end2
      }
      // toAngle:false弧度 true角度
      let du = getAngle(x1, y1, x2, y2, true);
      item.du = du;

      if (!myMap.has(du)) {
        myMap.set(du, item);

        // 边距 start3
        // const du2 = getAngle(x1, y1, last_x, last_y, true);
        // const gapParams = {
        //   x1,
        //   y1,
        //   gap_1,
        //   x2,
        //   y2,
        //   last_x,
        //   last_y,
        //   gap_2,
        //   du1: du,
        //   du2,
        // };
        // handleGap(gapParams);
        // 边距 end3
      }

      // console.log('du', du);
      index++;
    }

    let newArr: any[] = [];
    for (const value of myMap.values()) {
      newArr.push(value);
    }

    return newArr;
  } else {
    console.warn('小于两个点,不能形成夹角');
    return path;
  }
}

/**
 * 计算从A点[x1,y1]到B点[x2,y2]的直线,与水平线形成的夹角
 * 计算规则为以A为旋转点,将AB线顺时针旋转到-X轴形成的夹角
 * @param {Object} x1
 * @param {Object} y1
 * @param {Object} x2
 * @param {Object} y2
 * @param {Boolean} toAngle 默认false【弧度值】,true【角度值】
 */
function getAngle(
  x1: number,
  y1: number,
  x2: number,
  y2: number,
  toAngle = false
) {
  let x = Decimal.sub(x1, x2);
  let y = Decimal.sub(y1, y2);
  if (!x.toNumber() && !y.toNumber()) {
    return 0;
  }

  // 弧度 radian = 角度 * Math.PI / 180
  // 角度 angle = 弧度 * 180 / Math.PI

  let res;

  // 角度值
  // let angle = (180 + (Math.atan2(-y, -x) * 180) / Math.PI + 360) % 360;
  let _atan2 = Decimal.atan2(y.negated(), x.negated());
  let _angle = Decimal.div(Decimal.mul(_atan2, 180), Math.PI);
  let angle = Decimal.mod(Decimal.add(180, _angle).plus(360), 360);

  res = Decimal.sub(360, angle);

  if (!toAngle) {
    // 弧度值
    res = Decimal.mul(res, Math.PI).div(180);
  }
  return res.toNumber();
}

// /**
//  * 计算边距
//  * param里面包含3个点的坐标,及2条边距
//  * A [x1,y1] 当前目标点
//  * B [x2,y2] 在A点下一条邻边上的点
//  * C [last_x,last_y] 在A点上一条邻边上的点
//  * gap_1 AB上的边距; du1 AB与-X轴的夹角,传入角度值
//  * gap_2 AC上的边距; du2 AC与-X轴的夹角,传入角度值
//  */
// function handleGap(param: any) {
//   const { x1, y1, x2, y2, last_x, last_y, gap_1, gap_2, du1, du2 } = param;
//   // console.log('du1', du1);
// }

/**
 * 批量选择
 */
function batchSelection(value: boolean) {
  if (value) {
    startSelected.value = true;
  } else {
    startSelected.value = false;
    state.shapePath.forEach((i) => (i.selected = true));
  }
}

/**
 * 绘制网格线
 */
function drawGrid() {
  let canvas: any = document.getElementById('canvas-grid');
  let pen = canvas.getContext('2d');

  // 绘制网格
  const step = stageSize.gridGap;
  const h = stageSize.height;
  const w = stageSize.width;
  const w_l = w / step;
  const h_l = h / step;

  // 横着的线
  for (let i = 0; i <= h_l; i++) {
    pen.beginPath();
    pen.moveTo(0, i * step);
    pen.lineTo(w, i * step);
    pen.stroke();
  }
  // 竖着的线
  for (let i = 0; i <= w_l; i++) {
    pen.beginPath();
    pen.moveTo(i * step, 0);
    pen.lineTo(i * step, h);
    pen.stroke();
  }
}
</script>
<template>
  <div class="konva-main-page container-height center">
    <div class="column">
      <q-toggle
        v-model="startSelected"
        label="批量选择"
        @update:model-value="batchSelection"
      />
    </div>
    <div class="canvas-box">
      <canvas
        id="canvas-grid"
        :width="stageSize.width"
        :height="stageSize.height"
        style="position: absolute"
      ></canvas>
      <div id="stage-container"></div>
      <!-- ======================== 复选框 ======================== -->
      <template v-for="(item, index) in state.shapePath" :key="index">
        <q-checkbox
          v-model="item.selected"
          dense
          class="my-checkbox"
          v-if="startSelected && item.boundingBox"
          :style="{
            left: item.boundingBox.min_x + 'px',
            top: item.boundingBox.min_y + 'px',
          }"
        />
      </template>
    </div>
  </div>
</template>

<style lang="scss" scoped>
.konva-main-page {
}
.canvas-box {
  box-sizing: border-box;
  width: 1200px;
  height: 800px;
  border: 1px solid #000;
  // background: pink;
  position: relative;
}
.my-checkbox {
  position: absolute;
}
</style>