import { intersect } from 'mathjs'

/**
 * Collection of function to use on canvas
 */
// define a function that can locate the controls
export function polygonPositionHandler(dim, finalMatrix, fabricObject) {
  // @ts-ignore
  let x = fabricObject.points[this.pointIndex].x - fabricObject.pathOffset.x
  // @ts-ignore
  let y = fabricObject.points[this.pointIndex].y - fabricObject.pathOffset.y
  return fabric.util.transformPoint(
    { x, y },
    fabric.util.multiplyTransformMatrices(fabricObject.canvas.viewportTransform, fabricObject.calcTransformMatrix()),
  )
}

function getObjectSizeWithStroke(object) {
  let stroke = new fabric.Point(object.strokeUniform ? 1 / object.scaleX : 1, object.strokeUniform ? 1 / object.scaleY : 1).multiply(
    object.strokeWidth,
  )
  return new fabric.Point(object.width + stroke.x, object.height + stroke.y)
}

// define a function that will define what the control does
export function actionHandler(eventData, transform, x, y) {
  let polygon = transform.target,
    currentControl = polygon.controls[polygon.__corner],
    mouseLocalPosition = polygon.toLocalPoint(new fabric.Point(x, y), 'center', 'center'),
    polygonBaseSize = getObjectSizeWithStroke(polygon),
    size = polygon._getTransformedDimensions(0, 0)
  polygon.points[currentControl.pointIndex] = {
    x: (mouseLocalPosition.x * polygonBaseSize.x) / size.x + polygon.pathOffset.x,
    y: (mouseLocalPosition.y * polygonBaseSize.y) / size.y + polygon.pathOffset.y,
  }
  return true
}

// define a function that can keep the polygon in the same position when we change its width/height/top/left
export function anchorWrapper(anchorIndex, fn) {
  return function (eventData, transform, x, y) {
    let fabricObject = transform.target
    let originX = fabricObject?.points[anchorIndex].x - fabricObject.pathOffset.x
    let originY = fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y
    let absolutePoint = fabric.util.transformPoint(
      {
        x: originX,
        y: originY,
      },
      fabricObject.calcTransformMatrix(),
    )
    let actionPerformed = fn(eventData, transform, x, y)
    let newDim = fabricObject._setPositionDimensions({})
    let polygonBaseSize = getObjectSizeWithStroke(fabricObject)
    let newX = (fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x) / polygonBaseSize.x
    let newY = (fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y) / polygonBaseSize.y
    fabricObject.setPositionByOrigin(absolutePoint, newX + 0.5, newY + 0.5)
    return actionPerformed
  }
}

/**
 * 두 좌표의 중간점 좌표를 계산해서 반환하는 함수
 * @param {number} point1
 * @param {number} point2 방향에 상관없이 항상 큰 값이 뒤에 위치해야 함
 * @returns
 */
export const getCenterPoint = (point1, point2) => {
  return point1 + (point2 - point1) / 2
}

/**
 * 두 점 사이의 거리를 계산하는 함수
 * @param {*} x1 첫번째 점 x좌표
 * @param {*} y1 첫번째 점 y좌표
 * @param {*} x2 두번째 점 x좌표
 * @param {*} y2 두번째 점 y좌표
 * @returns
 */
export const getDistance = (x1, y1, x2, y2) => {
  return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))
}

// polygon의 각 변에 해당 점과 점 사이의 거리를 나타내는 IText를 추가하는 함수
export function addDistanceTextToPolygon(polygon) {
  const points = polygon.get('points')
  const texts = []

  for (let i = 0; i < points.length; i++) {
    const start = points[i]
    const end = points[(i + 1) % points.length] // 다음 점 (마지막 점의 경우 첫번째 점으로)
    const distance = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2)) // 두 점 사이의 거리 계산

    const text = new fabric.Textbox(distance.toFixed(2), {
      // 소수 둘째자리까지 표시
      left: (start.x + end.x) / 2, // 텍스트의 위치는 두 점의 중간
      top: (start.y + end.y) / 2,
      fontSize: 20,
    })

    texts.push(text)
  }

  return new fabric.Group([polygon, ...texts], {
    // polygon과 텍스트들을 그룹화
    selectable: true,
  })
}

/**
 *
 * @param {number} value
 * @param {boolean} useDefault
 * @param {string} delimeter
 * @returns
 * ex) 1,100 mm
 */
export const formattedWithComma = (value, unit = 'mm') => {
  let formatterdData = value.toLocaleString('ko-KR')
  if (unit === 'cm') {
    formatterdData = value.toLocaleString('ko-KR') / 10
  } else if (unit === 'm') {
    formatterdData = value.toLocaleString('ko-KR') / 1000
  }

  return `${formatterdData} ${unit}`
}

export const distanceBetweenPoints = (point1, point2) => {
  const dx = point2.x - point1.x
  const dy = point2.y - point1.y
  return Math.sqrt(dx * dx + dy * dy)
}

/**
 * line의 시작점을 찾는 함수
 * @param lines
 * @returns {number}
 */
export const getStartIndex = (lines) => {
  let smallestIndex = 0
  let smallestX1 = lines[0].x1
  let smallestY1 = lines[0].y1

  for (let i = 1; i < lines.length; i++) {
    if (lines[i].x1 < smallestX1 || (lines[i].x1 === smallestX1 && lines[i].y1 < smallestY1)) {
      smallestIndex = i
      smallestX1 = lines[i].x1
      smallestY1 = lines[i].y1
    }
  }

  return smallestIndex
}

/**
 * points 배열에서 시작점을 찾는 함수
 * @param points
 * @returns {number}
 */
export const getStartIndexPoint = (points) => {
  let smallestIndex = 0
  let smallestX1 = points[0].x
  let smallestY1 = points[0].y

  for (let i = 1; i < points.length; i++) {
    if (points[i].x < smallestX1 || (points[i].x === smallestX1 && points[i].y < smallestY1)) {
      smallestIndex = i
      smallestX1 = points[i].x
      smallestY1 = points[i].y
    }
  }

  return smallestIndex
}

/**
 * 이 함수는 두 개의 매개변수를 받습니다: array와 index.
 * array는 재배열할 대상 배열입니다.
 * index는 재배열의 기준이 될 배열 내의 위치입니다.
 * 함수는 먼저 index 위치부터 배열의 마지막 요소까지를 추출합니다(fromIndexToEnd).
 * 그 다음, 배열의 처음부터 index 위치까지의 요소를 추출합니다(fromStartToIndex).
 * 마지막으로, fromIndexToEnd와 fromStartToIndex 두 부분을 concat 메소드를 이용해 합칩니다.
 * 따라서, 이 함수는 주어진 index를 기준으로 배열을 두 부분으로 나누고, index부터 시작하는 부분을 앞에 두고, 그 뒤에 index 이전의 부분을 이어붙여 새로운 배열을 생성합니다. 이는 배열의 회전(rotating) 연산을 수행하는 것과 유사합니다.
 * @param array 재배열할 대상 배열
 * @param index 재배열 기준이 될 배열 내의 인덱스
 * @returns {*} 새로 재배열된 배열
 */
export const rearrangeArray = (array, index) => {
  // 배열의 특정 인덱스부터 마지막 요소까지를 가져옵니다.
  const fromIndexToEnd = array.slice(index)

  // 배열의 처음부터 특정 인덱스까지의 요소를 가져옵니다.
  const fromStartToIndex = array.slice(0, index)

  // 두 부분을 concat 메소드를 이용해 합칩니다.
  return fromIndexToEnd.concat(fromStartToIndex)
}

export const findTopTwoIndexesByDistance = (objArr) => {
  if (objArr.length < 2) {
    return [] // 배열의 길이가 2보다 작으면 빈 배열 반환
  }

  let firstIndex = -1
  let secondIndex = -1
  let firstDistance = -Infinity
  let secondDistance = -Infinity

  for (let i = 0; i < objArr.length; i++) {
    const distance = objArr[i].length

    if (distance > firstDistance) {
      secondDistance = firstDistance
      secondIndex = firstIndex
      firstDistance = distance
      firstIndex = i
    } else if (distance > secondDistance) {
      secondDistance = distance
      secondIndex = i
    }
  }

  return [firstIndex, secondIndex]
}

/**
 * 지붕의 누워있는 높이
 * @param base 밑변
 * @param degree 각도 ex(4촌의 경우 21.8)
 */
export const getRoofHeight = (base, degree) => {
  return base / Math.cos((degree * Math.PI) / 180)
}

/**
 * 지붕 빗변의 길이
 * @param base 밑변
 */
export const getRoofHypotenuse = (base) => {
  return Math.sqrt(base * base * 2)
}

/**
 * 촌을 입력받아 각도를 반환
 * @param chon
 * @returns {number}
 */
export const getDegreeByChon = (chon) => {
  // tan(theta) = height / base
  const radians = Math.atan(chon / 10)
  // 라디안을 도 단위로 변환
  return Number((radians * (180 / Math.PI)).toFixed(2))
}

/**
 * 두 점 사이의 방향을 반환합니다.
 * @param a {fabric.Object}
 * @param b {fabric.Object}
 * @returns {string}
 */
export const getDirection = (a, b) => {
  const vector = {
    x: b.left - a.left,
    y: b.top - a.top,
  }

  if (Math.abs(vector.x) > Math.abs(vector.y)) {
    // x축 방향으로 더 많이 이동
    return vector.x > 0 ? 'right' : 'left'
  } else {
    // y축 방향으로 더 많이 이동
    return vector.y > 0 ? 'bottom' : 'top'
  }
}

/**
 * 두 점 사이의 방향을 반환합니다.
 */
export const getDirectionByPoint = (a, b) => {
  const vector = {
    x: b.x - a.x,
    y: b.y - a.y,
  }

  if (Math.abs(vector.x) > Math.abs(vector.y)) {
    // x축 방향으로 더 많이 이동
    return vector.x > 0 ? 'right' : 'left'
  } else {
    // y축 방향으로 더 많이 이동
    return vector.y > 0 ? 'bottom' : 'top'
  }
}

export const findIntersection1 = (line1, line2) => {
  const { x1, y1, x2, y2 } = line1 // 첫 번째 선의 두 점

  const x3 = line2.x1
  const y3 = line2.y1
  const x4 = line2.x2
  const y4 = line2.y2

  // 선의 방정식의 계수 계산
  const A1 = y2 - y1
  const B1 = x1 - x2
  const C1 = A1 * x1 + B1 * y1

  const A2 = y4 - y3
  const B2 = x3 - x4
  const C2 = A2 * x3 + B2 * y3

  const determinant = A1 * B2 - A2 * B1

  if (determinant === 0) {
    // 두 선이 평행하거나 일직선일 경우
    return null
  }

  const x = (B1 * C2 - B2 * C1) / determinant
  const y = (A1 * C2 - A2 * C1) / determinant

  return { x, y }
}

export const calculateIntersection = (line1, line2) => {
  const result = intersect([line1.x1, line1.y1], [line1.x2, line1.y2], [line2.x1, line2.y1], [line2.x2, line2.y2])

  if (!result) {
    return null
  }

  // Determine the min and max for line1 and line2 for both x and y
  const line1MinX = Math.min(line1.x1, line1.x2)
  const line1MaxX = Math.max(line1.x1, line1.x2)
  const line2MinX = Math.min(line2.x1, line2.x2)
  const line2MaxX = Math.max(line2.x1, line2.x2)

  const line1MinY = Math.min(line1.y1, line1.y2)
  const line1MaxY = Math.max(line1.y1, line1.y2)
  const line2MinY = Math.min(line2.y1, line2.y2)
  const line2MaxY = Math.max(line2.y1, line2.y2)

  // Check if the intersection X and Y are within the range of both lines
  if (
    result[0] >= line1MinX &&
    result[0] <= line1MaxX &&
    result[0] >= line2MinX &&
    result[0] <= line2MaxX &&
    result[1] >= line1MinY &&
    result[1] <= line1MaxY &&
    result[1] >= line2MinY &&
    result[1] <= line2MaxY
  ) {
    return { x: Math.round(result[0]), y: Math.round(result[1]) }
  } else {
    return null // Intersection is out of range
  }
}

export function findOrthogonalPoint(line1, line2) {
  // Calculate the intersection point of two lines
  const intersectionX =
    ((line1.x1 * line2.y1 - line1.y1 * line2.x1) * (line2.x2 - line2.x1) - (line1.x1 - line1.x2) * (line2.x2 * line2.y2 - line2.y1 * line2.x1)) /
    ((line1.x1 - line1.x2) * (line2.y1 - line2.y2) - (line1.y1 - line1.y2) * (line2.x2 - line2.x1))
  const intersectionY =
    ((line1.x1 * line2.y1 - line1.y1 * line2.x1) * (line2.y2 - line2.y1) - (line1.y1 - line1.y2) * (line2.x2 * line2.y2 - line2.y1 * line2.x1)) /
    ((line1.x1 - line1.x2) * (line2.y1 - line2.y2) - (line1.y1 - line1.y2) * (line2.x2 - line2.x1))

  return { x: intersectionX, y: intersectionY }
}

/**
 * points배열을 입력받아 반시계방향으로 정렬된 points를 반환합니다.
 * @param points
 */
export const sortedPoints = (points) => {
  const copyPoints = [...points]
  copyPoints.forEach((point) => {
    point.x1 = point.x
    point.y1 = point.y
    const nextPoint = copyPoints[(copyPoints.indexOf(point) + 1) % copyPoints.length]
    point.x2 = nextPoint.x
    point.y2 = nextPoint.y
  })

  // copyPoint에서 x1, y1 값을 기준으로 시작 인덱스
  const startIndex = getStartIndex(copyPoints)
  const startDirection = getDirectionByPoint(
    { x: copyPoints[startIndex].x1, y: copyPoints[startIndex].y1 },
    { x: copyPoints[startIndex].x2, y: copyPoints[startIndex].y2 },
  )

  const resultPoints = [copyPoints[startIndex]]

  let currentPoint = copyPoints[startIndex]

  switch (startDirection) {
    case 'right': {
      copyPoints.forEach((point, index) => {
        if (index === startIndex) return

        const nextPoint = copyPoints.find((p) => p.x2 === currentPoint.x && p.y2 === currentPoint.y)
        resultPoints.push(nextPoint)
        currentPoint = nextPoint
      })
      break
    }
    case 'bottom': {
      copyPoints.forEach((point, index) => {
        if (index === startIndex) return

        const nextPoint = copyPoints.find((p) => p.x1 === currentPoint.x2 && p.y1 === currentPoint.y2)
        resultPoints.push(nextPoint)
        currentPoint = nextPoint
      })
      break
    }
  }

  return resultPoints.map((point) => {
    return { x: point.x, y: point.y }
  })
}

export const sortedPointLessEightPoint = (points) => {
  const copyPoints = [...points]
  //points를 x,y좌표를 기준으로 정렬합니다.
  copyPoints.sort((a, b) => {
    if (a.x === b.x) {
      return a.y - b.y
    }
    return a.x - b.x
  })

  const resultPoints = [copyPoints[0]]
  let index = 1
  let currentPoint = { ...copyPoints[0] }
  copyPoints.splice(0, 1)
  while (index < points.length) {
    if (index === points.length - 1) {
      resultPoints.push(copyPoints[0])
      index++
      break
    } else if (index % 2 === 0) {
      // 짝수번째는 y값이 같은 점을 찾는다.
      for (let i = 0; i < copyPoints.length; i++) {
        // y값이 같은 point가 많은 경우 그 중 x값이 가장 큰걸 찾는다.
        let temp = copyPoints.filter((point) => point.y === currentPoint.y)
        if (temp.length === 0) {
          // temp가 비어있을 경우 copyPoints에서 가장 가까운 점을 찾는다.
          temp = Array.of(findClosestPointByY(currentPoint, copyPoints))
        }
        // temp중 x값이 가장 가까운 값

        const min = temp.reduce((prev, current) => (Math.abs(current.x - currentPoint.x) <= Math.abs(prev.x - currentPoint.x) ? current : prev))

        resultPoints.push(min)
        currentPoint = min
        copyPoints.splice(copyPoints.indexOf(min), 1)
        index++
        break
      }
    } else {
      // 홀수번째는 x값이 같은 점을 찾는다.
      for (let i = 0; i < copyPoints.length; i++) {
        // x값이 같은 point가 많은 경우 그 중 y값이 가장 큰걸 찾는다.
        let temp = copyPoints.filter((point) => point.x === currentPoint.x)
        if (temp.length === 0) {
          // temp가 비어있을 경우 copyPoints에서 가장 가까운 점을 찾는다.

          temp = Array.of(findClosestPointByX(currentPoint, copyPoints))
        }
        // temp중 y값이 가장 가까운 값
        const min = temp.reduce((prev, current) => (Math.abs(current.y - currentPoint.y) <= Math.abs(prev.y - currentPoint.y) ? current : prev))

        resultPoints.push(min)
        currentPoint = min
        copyPoints.splice(copyPoints.indexOf(min), 1)
        index++
        break
      }
    }
  }
  return resultPoints
}

/**
 * point가 선 위에 있는지 확인
 * @param line
 * @param point
 * @returns {boolean}
 */
// 직선의 방정식.
// 방정식은 ax + by + c = 0이며, 점의 좌표를 대입하여 계산된 값은 직선과 점 사이의 관계를 나타낸다.
export function isPointOnLine(line, point) {
  const a = line.y2 - line.y1
  const b = line.x1 - line.x2
  const c = line.x2 * line.y1 - line.x1 * line.y2
  return a * point.x + b * point.y + c === 0
}

/**
 * 점과 가까운 line 찾기
 * @param point
 * @param lines {Array}
 * @returns {null}
 */
export function findClosestLineToPoint(point, lines) {
  let closestLine = null
  let closestDistance = Infinity

  for (const line of lines) {
    const distance = calculateDistance(point, line)

    if (distance < closestDistance) {
      closestDistance = distance
      closestLine = line
    }
  }

  return closestLine
}

/**
 * x값이 가장 가까운 점
 * @param targetPoint
 * @param points
 * @returns {*|null}
 */
function findClosestPointByX(targetPoint, points) {
  if (points.length === 0) {
    return null // Return null if the points array is empty
  }

  let closestPoint = points[0]
  let smallestDistance = Math.abs(targetPoint.x - points[0].x)

  for (let i = 1; i < points.length; i++) {
    const currentDistance = Math.abs(targetPoint.x - points[i].x)
    if (currentDistance < smallestDistance) {
      smallestDistance = currentDistance
      closestPoint = points[i]
    }
  }

  return closestPoint
}

/**
 * y값이 가장 가까운 점
 * @param targetPoint
 * @param points
 * @returns {*|null}
 */
function findClosestPointByY(targetPoint, points) {
  if (points.length === 0) {
    return null // Return null if the points array is empty
  }

  let closestPoint = points[0]
  let smallestDistance = Math.abs(targetPoint.y - points[0].y)

  for (let i = 1; i < points.length; i++) {
    const currentDistance = Math.abs(targetPoint.y - points[i].y)
    if (currentDistance < smallestDistance) {
      smallestDistance = currentDistance
      closestPoint = points[i]
    }
  }

  return closestPoint
}

/**
 * 주어진 점에서 points 배열 중 가장 가까운 점을 찾는 함수입니다.
 * @param {Object} targetPoint - { x: number, y: number } 형태의 대상 점 객체
 * @param {Array} points - { x: number, y: number } 형태의 점 객체들의 배열
 * @returns {Object} targetPoint에 가장 가까운 점 객체
 */
export function findClosestPoint(targetPoint, points) {
  if (points.length === 0) {
    return null // points 배열이 비어있으면 null 반환
  }

  let closestPoint = points[0]
  let smallestDistance = calculateDistancePoint(targetPoint, closestPoint)

  for (let i = 1; i < points.length; i++) {
    const currentDistance = calculateDistancePoint(targetPoint, points[i])
    if (currentDistance < smallestDistance) {
      smallestDistance = currentDistance
      closestPoint = points[i]
    }
  }

  return closestPoint
}

/**
 * 점과 직선사이의 최단 거리
 * @param point
 * @param line
 * @returns {number}
 */
export function calculateDistance(point, line) {
  const x1 = line.x1
  const y1 = line.y1
  const x2 = line.x2
  const y2 = line.y2
  const x0 = point.x
  const y0 = point.y

  const numerator = Math.abs((y2 - y1) * x0 - (x2 - x1) * y0 + x2 * y1 - y2 * x1)
  const denominator = Math.sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2))
  return numerator / denominator
}

/**
 * 두 점 사이의 거리를 계산하는 함수입니다.
 * @param {Object} point1 - 첫 번째 점 { x: number, y: number }
 * @param {Object} point2 - 두 번째 점 { x: number, y: number }
 * @returns {number} 두 점 사이의 거리
 */
function calculateDistancePoint(point1, point2) {
  return Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2))
}

/**
 * {x, y} 형태의 배열을 받아 중복된 점을 제거하는 함수
 * @param points
 * @returns {*[]}
 */
export function removeDuplicatePoints(points) {
  const uniquePoints = []
  const seen = new Set()

  points.forEach((point) => {
    const identifier = `${point.x}:${point.y}`
    if (!seen.has(identifier)) {
      seen.add(identifier)
      uniquePoints.push(point)
    }
  })

  return uniquePoints
}

/**
 * x,y가 다르면서 가장 가까운 점
 * @param targetPoint
 * @param points
 * @returns {null}
 */
export function findClosestPointWithDifferentXY(targetPoint, points) {
  let closestPoint = null
  let smallestDistance = Infinity

  points.forEach((point) => {
    if (point.x !== targetPoint.x && point.y !== targetPoint.y) {
      const distance = Math.sqrt(Math.pow(point.x - targetPoint.x, 2) + Math.pow(point.y - targetPoint.y, 2))
      if (distance < smallestDistance) {
        smallestDistance = distance
        closestPoint = point
      }
    }
  })

  return closestPoint
}
