import { fabric } from 'fabric'
import { QLine } from '@/components/fabric/QLine'
import { calculateIntersection, distanceBetweenPoints, findClosestPoint, getDirectionByPoint } from '@/util/canvas-util'
import { QPolygon } from '@/components/fabric/QPolygon'

const TWO_PI = Math.PI * 2

export const defineQPloygon = () => {
  fabric.QPolygon.fromObject = function (object, callback) {
    fabric.Object._fromObject('QPolygon', object, callback, 'points')
  }
}

export const drawHelpLineInHexagon = (polygon, chon) => {
  const centerLines = drawCenterLines(polygon)

  let helpLines = []

  const interSectionPoints = []
  const tempInterSectionPoints = []

  const ridgeStartPoints = []
  const ridgeEndPoints = []

  let centerInterSectionPoints = []

  // polygon.lines = polygon.lines.sort((a, b) => a.length - b.length)
  polygon.wall.lines = getOneSideLines(polygon.wall)

  const maxLength = Math.max(...polygon.lines.map((line) => line.length))

  polygon.points.forEach((point, index) => {
    const wallPoint = polygon.wall.points[index]

    const angle = Math.atan2(wallPoint.y - point.y, wallPoint.x - point.x)

    const degree = fabric.util.radiansToDegrees(angle)

    const newX2 = Math.floor(point.x + maxLength * Math.cos(angle))
    const newY2 = Math.floor(point.y + maxLength * Math.sin(angle))

    const helpLine = new QLine([point.x, point.y, newX2, newY2], {
      fontSize: polygon.fontSize,
      stroke: 'green',
      startPoint: point,
      degree: degree,
      idx: index,
    })

    // polygon.canvas?.add(helpLine)

    helpLines.push(helpLine)
  })

  helpLines.forEach((line, index) => {
    for (let i = index + 1; i < helpLines.length; i++) {
      const nextLine = helpLines[i]
      if (!line.connectedPoint) {
        line.connectedPoint = null
        line.connectedPoints = []
      }
      if (!nextLine.connectedPoint) {
        nextLine.connectedPoint = null
        nextLine.connectedPoints = []
      }

      const interSectionPoint = calculateIntersection(line, nextLine)

      if (
        interSectionPoint &&
        polygon.inPolygon(interSectionPoint) &&
        polygon.wall.inPolygon(interSectionPoint) &&
        Math.abs(distanceBetweenPoints(line.startPoint, interSectionPoint) - distanceBetweenPoints(nextLine.startPoint, interSectionPoint)) < 2
      ) {
        const area = calculateTriangleArea(line.startPoint, nextLine.startPoint, interSectionPoint)
        const currentLineConnectedPoint = line.connectedPoint
        const nextLineConnectedPoint = nextLine.connectedPoint

        if (area <= 1) {
          return
        }

        if (currentLineConnectedPoint && currentLineConnectedPoint.area < area) {
          return
        }

        //startPoint는 line의 startPoint와 nextLine의 startPoint를 비교하여 x가 같은경우 y가 더 작은 값, y가 같은경우 x가 더 작은 값을 선택한다.
        const startPoint =
          line.startPoint.x === nextLine.startPoint.x
            ? line.startPoint.y < nextLine.startPoint.y
              ? line.startPoint
              : nextLine.startPoint
            : line.startPoint.x < nextLine.startPoint.x
              ? line.startPoint
              : nextLine.startPoint

        const endPoint =
          line.startPoint.x === nextLine.startPoint.x
            ? line.startPoint.y > nextLine.startPoint.y
              ? line.startPoint
              : nextLine.startPoint
            : line.startPoint.x > nextLine.startPoint.x
              ? line.startPoint
              : nextLine.startPoint

        line.connectedPoint = { interSectionPoint, area, startPoint, endPoint }
        line.connectedPoints.push({ interSectionPoint, area, startPoint, endPoint })
        nextLine.connectedPoint = { interSectionPoint, area, startPoint, endPoint }
        nextLine.connectedPoints.push({ interSectionPoint, area, startPoint, endPoint })
      }
    }
  })

  helpLines.forEach((line) => {
    if (line.connectedPoint) {
      tempInterSectionPoints.push(line.connectedPoint)
    }
  })

  // interSectionPoints에서 interSectionPoint가 중복인 값이 있는 경우만 선택한다.
  tempInterSectionPoints.forEach((point) => {
    // intersectionPoint가 중복인 경우
    const isDuplicated =
      tempInterSectionPoints.filter((p) => p.interSectionPoint.x === point.interSectionPoint.x && p.interSectionPoint.y === point.interSectionPoint.y)
        .length > 1
    if (isDuplicated) {
      interSectionPoints.push(point)
    }
  })

  // interSectionPoints에서 interSectionPoint 기준으로 중복을 제거한다.
  const uniqueInterSectionPoints = Array.from(
    new Set(interSectionPoints.map((point) => `${point.interSectionPoint.x},${point.interSectionPoint.y}`)),
  ).map((key) => {
    const { interSectionPoint, area, startPoint, endPoint } = interSectionPoints.find(
      (point) => `${point.interSectionPoint.x},${point.interSectionPoint.y}` === key,
    )
    return { interSectionPoint, area, startPoint, endPoint }
  })

  uniqueInterSectionPoints.forEach((point) => {
    ridgeStartPoints.push(point.interSectionPoint)

    const line = new QLine([point.startPoint.x, point.startPoint.y, point.interSectionPoint.x, point.interSectionPoint.y], {
      stroke: 'purple',
      fontSize: polygon.fontSize,
      name: 'hip',
    })

    const line2 = new QLine([point.endPoint.x, point.endPoint.y, point.interSectionPoint.x, point.interSectionPoint.y], {
      stroke: 'purple',
      fontSize: polygon.fontSize,
      name: 'hip',
    })

    line.startPoint = point.startPoint
    line.endPoint = point.interSectionPoint

    line2.startPoint = point.endPoint
    line2.endPoint = point.interSectionPoint

    polygon.hips.push(line)
    polygon.hips.push(line2)

    polygon.canvas.add(line)
    polygon.canvas.add(line2)
  })

  const removedIdx = []

  helpLines.forEach((line) => {
    const connectedPoints = line.connectedPoints
    connectedPoints.forEach((connectedPoint) => {
      uniqueInterSectionPoints.forEach((point) => {
        const interSectionPoint = point.interSectionPoint

        if (connectedPoint.interSectionPoint.x === interSectionPoint.x && connectedPoint.interSectionPoint.y === interSectionPoint.y) {
          removedIdx.push(line.idx)
        }
      })
    })
  })

  let notIntersectedLines = helpLines.filter((line) => !removedIdx.includes(line.idx))

  notIntersectedLines = notIntersectedLines.map((line) => {
    return { ...line, centerInterSectionPoints: [] }
  })

  notIntersectedLines.forEach((line) => {
    centerLines.forEach((centerLine) => {
      const interSectionPoint = calculateIntersection(line, centerLine)

      if (interSectionPoint && polygon.inPolygon(interSectionPoint) && polygon.wall.inPolygon(interSectionPoint)) {
        line.centerInterSectionPoints.push(interSectionPoint)
        interSectionPoint.lineIdx = line.idx
        centerInterSectionPoints.push(interSectionPoint)
      }
    })
  })

  // centerInterSectionPoints에서 ridgeStartPoints와 x가 같거나 y가 같은것중 가장 가까운 점들을 찾는다.
  ridgeStartPoints.forEach((point) => {
    const xPoints = centerInterSectionPoints.filter((centerPoint) => Math.abs(centerPoint.x - point.x) < 2)

    const yPoints = centerInterSectionPoints.filter((centerPoint) => Math.abs(centerPoint.y - point.y) < 2)
    let closestPoint
    if (xPoints.length === 0) {
      closestPoint = findClosestPoint(point, yPoints)
    } else if (yPoints.length === 0) {
      closestPoint = findClosestPoint(point, xPoints)
    }

    if (closestPoint) {
      const line = new QLine([point.x, point.y, closestPoint.x, closestPoint.y], {
        stroke: 'purple',
        fontSize: polygon.fontSize,
        name: 'ridge',
        direction: getDirectionByPoint(point, closestPoint),
      })

      line.startPoint = point
      line.endPoint = closestPoint

      polygon.ridges.push(line)
      polygon.canvas.add(line)
      ridgeEndPoints.push(closestPoint)

      notIntersectedLines = notIntersectedLines.filter((line) => line.idx !== closestPoint.lineIdx)
    }
  })

  centerInterSectionPoints = []
  notIntersectedLines.forEach((line) => {
    centerInterSectionPoints.push(...line.centerInterSectionPoints)
  })

  // ridgeEndPoints끼리 이어준다.
  const remainingPoints = [...ridgeEndPoints]

  // ridgeEndPoint에서 centerInterSectionPoints와 45도인 점을 찾아 이어준다.

  ridgeEndPoints.forEach((ridgePoint) => {
    const filteredCenterInterSectionPoints = centerInterSectionPoints.filter((centerPoint) => {
      const degree = calculateAngle(ridgePoint, centerPoint)
      return Math.abs(degree) === 45 || Math.abs(degree) === 135
    })[0]

    if (filteredCenterInterSectionPoints) {
      const line = new QLine([ridgePoint.x, ridgePoint.y, filteredCenterInterSectionPoints.x, filteredCenterInterSectionPoints.y], {
        stroke: 'purple',
        fontSize: polygon.fontSize,
        name: 'hip',
      })

      line.startPoint = ridgePoint
      line.endPoint = filteredCenterInterSectionPoints

      polygon.hips.push(line)
      polygon.canvas.add(line)

      ridgeStartPoints.push(filteredCenterInterSectionPoints)

      polygon.points.forEach((point) => {
        const degree = calculateAngle(ridgePoint, point)

        if (Math.abs(degree) % 45 < 1) {
          const line = new QLine([ridgePoint.x, ridgePoint.y, point.x, point.y], {
            stroke: 'purple',
            fontSize: polygon.fontSize,
            name: 'hip',
          })

          polygon.hips.push(line)
          polygon.canvas.add(line)
        }
      })
    }
  })

  // ridgeEndPoint끼리 연결한다.
  while (remainingPoints.length > 1) {
    const startPoint = remainingPoints.shift()
    const endPoint = remainingPoints.shift()

    const line = new QLine([startPoint.x, startPoint.y, endPoint.x, endPoint.y], {
      stroke: 'purple',
      fontSize: polygon.fontSize,
      name: 'connectRidge',
    })

    line.startPoint = startPoint
    line.endPoint = endPoint

    polygon.connectRidges.push(line)

    polygon.points.forEach((point) => {
      const degree = calculateAngle(startPoint, point)

      if (Math.abs(degree) === 45 || Math.abs(degree) === 135) {
        const line = new QLine([startPoint.x, startPoint.y, point.x, point.y], {
          stroke: 'purple',
          fontSize: polygon.fontSize,
          name: 'hip',
        })

        line.startPoint = startPoint
        line.endPoint = point

        polygon.hips.push(line)
        polygon.canvas.add(line)
      }
    })

    polygon.points.forEach((point) => {
      const degree = calculateAngle(endPoint, point)

      if (Math.abs(degree) === 45 || Math.abs(degree) === 135) {
        const line = new QLine([endPoint.x, endPoint.y, point.x, point.y], {
          stroke: 'purple',
          fontSize: polygon.fontSize,
          name: 'hip',
        })

        line.startPoint = endPoint
        line.endPoint = point

        polygon.hips.push(line)
        polygon.canvas.add(line)
      }
    })

    polygon.canvas.add(line)
  }
}

export const drawCenterLines = (polygon) => {
  const centerLines = []

  const oneSideLines = polygon.lines.map((line) => getOneSideLine(line))

  const horizontalLines = oneSideLines.filter((line) => line.direction === 'right')
  const verticalLines = oneSideLines.filter((line) => line.direction === 'bottom')
  // horizontalLines 를 y1 좌표 기준으로 정렬한다.
  horizontalLines.sort((a, b) => a.y1 - b.y1)
  // verticalLines 를 x1 좌표 기준으로 정렬한다.
  verticalLines.sort((a, b) => a.x1 - b.x1)

  let maxHorizontalLineLength = 0
  let maxVerticalLineLength = 0
  // 모든 가로선의 중심선을 긋는다.
  horizontalLines.forEach((line, index) => {
    const nextLine = horizontalLines[(index + 1) % horizontalLines.length]

    polygon.canvas.renderAll()

    const startCenterX = Math.min(line.x1, nextLine.x1)
    const startCenterY = (line.y1 + nextLine.y1) / 2

    const endCenterX = line.x2 > nextLine.x2 ? line.x2 : nextLine.x2
    const endCenterY = startCenterY

    const centerLine = new QLine([startCenterX, startCenterY, endCenterX, endCenterY], {
      fontSize: polygon.fontSize,
      stroke: 'red',
      strokeWidth: 1,
      direction: 'horizontal',
    })

    centerLines.push(centerLine)
  })

  // 모든 세로선의 중심선을 긋는다.
  verticalLines.forEach((line, index) => {
    const nextLine = verticalLines[(index + 1) % verticalLines.length]

    const startCenterX = (line.x1 + nextLine.x1) / 2
    const startCenterY = Math.min(line.y1, nextLine.y1)

    const endCenterX = startCenterX
    let endCenterY = line.y2 > nextLine.y2 ? line.y2 : nextLine.y2

    const centerLine = new QLine([startCenterX, startCenterY, endCenterX, endCenterY], {
      fontSize: polygon.fontSize,
      stroke: 'blue',
      strokeWidth: 1,
      direction: 'vertical',
    })

    centerLines.push(centerLine)
  })

  return centerLines
}

const getOneSideLines = (polygon) => {
  return [...polygon.lines].map((line) => {
    let newX1, newY1, newX2, newY2
    if (line.direction === 'top') {
      newX1 = line.x2
      newY1 = line.y2
      newX2 = line.x1
      newY2 = line.y1

      line.x1 = newX1
      line.y1 = newY1
      line.x2 = newX2
      line.y2 = newY2
      line.direction = 'bottom'
      line.startPoint = { x: newX1, y: newY1 }
      line.endPoint = { x: newX2, y: newY2 }
    } else if (line.direction === 'left') {
      newX1 = line.x2
      newY1 = line.y2
      newX2 = line.x1
      newY2 = line.y1

      line.x1 = newX1
      line.y1 = newY1
      line.x2 = newX2
      line.y2 = newY2
      line.direction = 'right'
      line.startPoint = { x: newX1, y: newY1 }
      line.endPoint = { x: newX2, y: newY2 }
    }
    return line
  })
}
export const calculateAngle = (point1, point2) => {
  const deltaX = point2.x - point1.x
  const deltaY = point2.y - point1.y
  const angleInRadians = Math.atan2(deltaY, deltaX)
  return angleInRadians * (180 / Math.PI)
}

/**
 * 3개의 점을 이용해 직각 이등변 삼각형인지 확인
 * @param point1
 * @param point2
 * @param point3
 * @returns {boolean}
 */
const isRightIsoscelesTriangle = (point1, point2, point3) => {
  const distance = (p1, p2) => Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2))

  const d1 = distance(point1, point2)
  const d2 = distance(point2, point3)
  const d3 = distance(point3, point1)

  const distances = [d1, d2, d3].sort((a, b) => a - b)

  // Check if the two smaller distances are equal and the largest distance is the hypotenuse
  return distances[0] === distances[1] && Math.abs(Math.pow(distances[0], 2) * 2 - Math.pow(distances[2], 2)) < 1
}

/**
 * 세개의 점으로 삼각형의 넓이를 구한다.
 * @param point1
 * @param point2
 * @param point3
 * @returns {number}
 */
const calculateTriangleArea = (point1, point2, point3) => {
  const { x: x1, y: y1 } = point1
  const { x: x2, y: y2 } = point2
  const { x: x3, y: y3 } = point3

  return Math.abs(x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2
}

// polygon을 나눈다.
export const dividePolygon = (polygon) => {
  let hips = polygon.hips
  const ridges = polygon.ridges.map((ridge) => getOneSideLine(ridge))
  const connectRidges = polygon.connectRidges
  const polygonLines = polygon.lines

  hips.forEach((hip) => {
    // hips의 startPoint와 endPoint를 polygon의 points와 비교하여 같은 점이 endPoint일 경우 startPoint로 변경한다.
    const startPoint = polygon.points.find((point) => point.x === hip.endPoint.x && point.y === hip.endPoint.y)
    if (startPoint) {
      const temp = hip.startPoint
      hip.startPoint = hip.endPoint
      hip.endPoint = temp
    }
  })
  hips = [...hips, ...connectRidges]
  polygon.setViewLengthText(false)

  polygonLines.forEach((line) => {
    let ridge

    const startPoint = line.startPoint
    const endPoint = line.endPoint
    let polygonPoints = []

    polygonPoints.push(startPoint)

    polygonPoints.push(endPoint)

    const startHip = hips.find((hip) => hip.startPoint.x === startPoint.x && hip.startPoint.y === startPoint.y)
    const endHip = hips.find((hip) => hip.startPoint.x === endPoint.x && hip.startPoint.y === endPoint.y)

    if (!startHip || !endHip) {
      return
    }

    if (startHip && endHip && startHip.endPoint.x === endHip.endPoint.x && startHip.endPoint.y === endHip.endPoint.y) {
      polygonPoints.push(startHip.endPoint)

      const newPolygon = new QPolygon(polygonPoints, {
        fontSize: polygon.fontSize,
        id: polygon.id,
        name: 'roof',
        selectable: false,
        stroke: 'black',
        fill: 'transparent',
        strokeWidth: 3,
      })

      polygon.canvas.add(newPolygon)
      return
    }

    let connectedRidge = ridges.find(
      (ridge) =>
        (ridge.startPoint.x === startHip.endPoint.x && ridge.startPoint.y === startHip.endPoint.y) ||
        (ridge.endPoint.x === startHip.endPoint.x && ridge.endPoint.y === startHip.endPoint.y),
    )

    const hipStartPoint = startHip.endPoint
    const hipEndPoint = endHip.endPoint
    const restRidgeConnection = connectRidges[0]

    if (connectedRidge.startPoint.x === hipStartPoint.x && connectedRidge.startPoint.y === hipStartPoint.y) {
      if (connectedRidge.endPoint.x === hipEndPoint.x && connectedRidge.endPoint.y === hipEndPoint.y) {
        polygonPoints.push(connectedRidge.endPoint)
        polygonPoints.push(connectedRidge.startPoint)

        const newPolygon = new QPolygon(polygonPoints, {
          fontSize: polygon.fontSize,
          id: polygon.id,
          name: 'roof',
          selectable: false,
          stroke: 'black',
          fill: 'transparent',
          strokeWidth: 3,
        })

        polygon.canvas.add(newPolygon)
        return
      }
    } else if (connectedRidge.endPoint.x === hipStartPoint.x && connectedRidge.endPoint.y === hipStartPoint.y) {
      if (connectedRidge.startPoint.x === hipEndPoint.x && connectedRidge.startPoint.y === hipEndPoint.y) {
        polygonPoints.push(connectedRidge.startPoint)
        polygonPoints.push(connectedRidge.endPoint)

        const newPolygon = new QPolygon(polygonPoints, {
          fontSize: polygon.fontSize,
          id: polygon.id,
          name: 'roof',
          selectable: false,
          stroke: 'black',
          fill: 'transparent',
          strokeWidth: 3,
          sort: true,
        })

        polygon.canvas.add(newPolygon)
        return
      }
    }

    // 지붕이 꺾여있는 경우

    if (
      (restRidgeConnection.startPoint.x === startHip.endPoint.x && restRidgeConnection.startPoint.y === startHip.endPoint.y) ||
      (restRidgeConnection.endPoint.x === startHip.endPoint.x && restRidgeConnection.endPoint.y === startHip.endPoint.y)
    ) {
      polygonPoints = [startPoint, startHip.endPoint]
      let lastPoint

      if (restRidgeConnection.startPoint.x === startHip.endPoint.x && restRidgeConnection.startPoint.y === startHip.endPoint.y) {
        lastPoint = restRidgeConnection.endPoint
        polygonPoints.push(restRidgeConnection.endPoint)
      } else {
        lastPoint = restRidgeConnection.startPoint
        polygonPoints.push(restRidgeConnection.startPoint)
      }

      connectedRidge = ridges.find(
        (ridge) =>
          (ridge.startPoint.x === lastPoint.x && ridge.startPoint.y === lastPoint.y) ||
          (ridge.endPoint.x === lastPoint.x && ridge.endPoint.y === lastPoint.y),
      )

      if (connectedRidge.startPoint.x === lastPoint.x && connectedRidge.startPoint.y === lastPoint.y) {
        polygonPoints.push(connectedRidge.endPoint)
      } else {
        polygonPoints.push(connectedRidge.startPoint)
      }

      polygonPoints.push(endPoint)
    } else {
      polygonPoints = [endPoint, endHip.endPoint]
      let lastPoint

      if (restRidgeConnection.startPoint.x === endHip.endPoint.x && restRidgeConnection.startPoint.y === endHip.endPoint.y) {
        lastPoint = restRidgeConnection.endPoint
        polygonPoints.push(restRidgeConnection.endPoint)
      } else {
        lastPoint = restRidgeConnection.startPoint
        polygonPoints.push(restRidgeConnection.startPoint)
      }

      connectedRidge = ridges.find(
        (ridge) =>
          (ridge.startPoint.x === lastPoint.x && ridge.startPoint.y === lastPoint.y) ||
          (ridge.endPoint.x === lastPoint.x && ridge.endPoint.y === lastPoint.y),
      )

      if (connectedRidge.startPoint.x === startHip.endPoint.x && connectedRidge.startPoint.y === startHip.endPoint.y) {
        lastPoint = connectedRidge.startPoint
        polygonPoints.push(connectedRidge.startPoint)
      } else {
        lastPoint = connectedRidge.endPoint
        polygonPoints.push(connectedRidge.endPoint)
      }

      polygonPoints.push(startPoint)
    }

    const newPolygon = new QPolygon(polygonPoints, {
      fontSize: polygon.fontSize,
      id: polygon.id,
      name: 'roof',
      selectable: false,
      stroke: 'black',
      fill: 'transparent',
      strokeWidth: 3,
    })

    polygon.canvas.add(newPolygon)
  })
}

const getOneSideLine = (line) => {
  // left, top 방향의 line은 right, bottom 방향의 line으로 변경한다.
  const newLine = { ...line }
  let newX1, newY1, newX2, newY2
  if (newLine.direction === 'top') {
    newX1 = newLine.x2
    newY1 = newLine.y2
    newX2 = newLine.x1
    newY2 = newLine.y1

    newLine.x1 = newX1
    newLine.y1 = newY1
    newLine.x2 = newX2
    newLine.y2 = newY2
    newLine.direction = 'bottom'
    newLine.startPoint = { x: newX1, y: newY1 }
    newLine.endPoint = { x: newX2, y: newY2 }
  } else if (line.direction === 'left') {
    newX1 = newLine.x2
    newY1 = newLine.y2
    newX2 = newLine.x1
    newY2 = newLine.y1

    newLine.x1 = newX1
    newLine.y1 = newY1
    newLine.x2 = newX2
    newLine.y2 = newY2
    newLine.direction = 'right'
    newLine.startPoint = { x: newX1, y: newY1 }
    newLine.endPoint = { x: newX2, y: newY2 }
  }

  return newLine
}

function inwardEdgeNormal(vertex1, vertex2) {
  // Assuming that polygon vertices are in clockwise order
  const dx = vertex2.x - vertex1.x
  const dy = vertex2.y - vertex1.y
  const edgeLength = Math.sqrt(dx * dx + dy * dy)

  return {
    x: -dy / edgeLength,
    y: dx / edgeLength,
  }
}

function outwardEdgeNormal(vertex1, vertex2) {
  var n = inwardEdgeNormal(vertex1, vertex2)

  return {
    x: -n.x,
    y: -n.y,
  }
}

function createPolygon(vertices) {
  const edges = []
  let minX = vertices.length > 0 ? vertices[0].x : undefined
  let minY = vertices.length > 0 ? vertices[0].y : undefined
  let maxX = minX
  let maxY = minY

  for (let i = 0; i < vertices.length; i++) {
    const vertex1 = vertices[i]
    const vertex2 = vertices[(i + 1) % vertices.length]

    const outwardNormal = outwardEdgeNormal(vertex1, vertex2)

    const inwardNormal = inwardEdgeNormal(vertex1, vertex2)

    const edge = {
      vertex1,
      vertex2,
      index: i,
      outwardNormal,
      inwardNormal,
    }

    edges.push(edge)

    const x = vertices[i].x
    const y = vertices[i].y
    minX = Math.min(x, minX)
    minY = Math.min(y, minY)
    maxX = Math.max(x, maxX)
    maxY = Math.max(y, maxY)
  }

  return {
    vertices,
    edges,
    minX,
    minY,
    maxX,
    maxY,
  }
}

// based on http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/, edgeA => "line a", edgeB => "line b"

function edgesIntersection(edgeA, edgeB) {
  const den =
    (edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex2.x - edgeA.vertex1.x) -
    (edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex2.y - edgeA.vertex1.y)

  if (den == 0) {
    return null // lines are parallel or coincident
  }

  const ua =
    ((edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) -
      (edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) /
    den

  const ub =
    ((edgeA.vertex2.x - edgeA.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) -
      (edgeA.vertex2.y - edgeA.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) /
    den

  // Edges are not intersecting but the lines defined by them are
  const isIntersectionOutside = ua < 0 || ub < 0 || ua > 1 || ub > 1

  return {
    x: edgeA.vertex1.x + ua * (edgeA.vertex2.x - edgeA.vertex1.x),
    y: edgeA.vertex1.y + ua * (edgeA.vertex2.y - edgeA.vertex1.y),
    isIntersectionOutside,
  }
}

function appendArc(arcSegments, vertices, center, radius, startVertex, endVertex, isPaddingBoundary) {
  var startAngle = Math.atan2(startVertex.y - center.y, startVertex.x - center.x)
  var endAngle = Math.atan2(endVertex.y - center.y, endVertex.x - center.x)

  if (startAngle < 0) {
    startAngle += TWO_PI
  }

  if (endAngle < 0) {
    endAngle += TWO_PI
  }

  const angle = startAngle > endAngle ? startAngle - endAngle : startAngle + TWO_PI - endAngle
  const angleStep = (isPaddingBoundary ? -angle : TWO_PI - angle) / arcSegments

  vertices.push(startVertex)

  for (let i = 1; i < arcSegments; ++i) {
    const angle = startAngle + angleStep * i

    const vertex = {
      x: center.x + Math.cos(angle) * radius,
      y: center.y + Math.sin(angle) * radius,
    }

    vertices.push(vertex)
  }

  vertices.push(endVertex)
}

function createOffsetEdge(edge, dx, dy) {
  return {
    vertex1: {
      x: edge.vertex1.x + dx,
      y: edge.vertex1.y + dy,
    },
    vertex2: {
      x: edge.vertex2.x + dx,
      y: edge.vertex2.y + dy,
    },
  }
}

function createMarginPolygon(polygon, offset, arcSegments = 0) {
  const offsetEdges = []

  for (let i = 0; i < polygon.edges.length; i++) {
    const edge = polygon.edges[i]
    const dx = edge.outwardNormal.x * offset
    const dy = edge.outwardNormal.y * offset
    offsetEdges.push(createOffsetEdge(edge, dx, dy))
  }

  const vertices = []

  for (let i = 0; i < offsetEdges.length; i++) {
    const thisEdge = offsetEdges[i]
    const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]
    const vertex = edgesIntersection(prevEdge, thisEdge)

    if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) {
      vertices.push({
        x: vertex.x,
        y: vertex.y,
      })
    } else {
      const arcCenter = polygon.edges[i].vertex1

      appendArc(arcSegments, vertices, arcCenter, offset, prevEdge.vertex2, thisEdge.vertex1, false)
    }
  }

  const marginPolygon = createPolygon(vertices)

  marginPolygon.offsetEdges = offsetEdges

  return marginPolygon
}

function createPaddingPolygon(polygon, offset, arcSegments = 0) {
  const offsetEdges = []

  for (let i = 0; i < polygon.edges.length; i++) {
    const edge = polygon.edges[i]
    const dx = edge.inwardNormal.x * offset
    const dy = edge.inwardNormal.y * offset
    offsetEdges.push(createOffsetEdge(edge, dx, dy))
  }

  const vertices = []

  for (let i = 0; i < offsetEdges.length; i++) {
    const thisEdge = offsetEdges[i]
    const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]
    const vertex = edgesIntersection(prevEdge, thisEdge)
    if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) {
      vertices.push({
        x: vertex.x,
        y: vertex.y,
      })
    } else {
      const arcCenter = polygon.edges[i].vertex1

      appendArc(arcSegments, vertices, arcCenter, offset, prevEdge.vertex2, thisEdge.vertex1, true)
    }
  }

  const paddingPolygon = createPolygon(vertices)

  paddingPolygon.offsetEdges = offsetEdges

  return paddingPolygon
}

export default function offsetPolygon(vertices, offset, arcSegments = 0) {
  const polygon = createPolygon(vertices)

  const originPolygon = new QPolygon(vertices, { fontSize: 0 })

  if (offset > 0) {
    let result = createMarginPolygon(polygon, offset, arcSegments).vertices
    const allPointsOutside = result.every((point) => !originPolygon.inPolygon(point))

    if (allPointsOutside) {
      return createMarginPolygon(polygon, offset, arcSegments).vertices
    } else {
      return createPaddingPolygon(polygon, offset, arcSegments).vertices
    }
  } else {
    let result = createPaddingPolygon(polygon, offset, arcSegments).vertices
    const allPointsInside = result.every((point) => originPolygon.inPolygon(point))

    if (allPointsInside) {
      return createPaddingPolygon(polygon, offset, arcSegments).vertices
    } else {
      return createMarginPolygon(polygon, offset, arcSegments).vertices
    }
  }
}
