// import {CompassVector} from '../../math/geometry/compassVector'
import {Direction} from '../../math/geometry/direction'
import {LineSegment} from '../../math/geometry/lineSegment'
import {Point} from '../../math/geometry/point'
import {Rectangle} from '../../math/geometry/rectangle'
// import {Assert} from '../../utils/assert'
import {SegmentBase} from '../visibility/SegmentBase'
import {VisibilityEdge} from '../visibility/VisibilityEdge'
import {VisibilityVertex} from '../visibility/VisibilityVertex'
import {PointComparer} from './PointComparer'
import {ScanDirection} from './ScanDirection'

export class StaticGraphUtility {
  //  Determine the direction of an edge.
  static EdgeDirectionVE(edge: VisibilityEdge): Direction {
    return StaticGraphUtility.EdgeDirectionVV(edge.Source, edge.Target)
  }

  static EdgeDirectionVV(source: VisibilityVertex, target: VisibilityVertex): Direction {
    return PointComparer.GetDirections(source.point, target.point)
  }

  static GetEdgeEnd(edge: VisibilityEdge, dir: Direction): VisibilityVertex {
    const edgeDir: Direction = StaticGraphUtility.EdgeDirectionVE(edge)
    /*Assert.assert(
      0 != (dir & (edgeDir | CompassVector.OppositeDir(edgeDir))),
      'dir is orthogonal to edge',
    )*/
    return dir == edgeDir ? edge.Target : edge.Source
  }

  static FindAdjacentVertex(vertex: VisibilityVertex, dir: Direction): VisibilityVertex {
    //  This function finds the next vertex in the desired direction relative to the
    //  current vertex, not necessarily the edge orientation, hence it does not use
    //  EdgeDirection().  This is so the caller can operate on a desired movement
    //  direction without having to track whether we're going forward or backward
    //  through the In/OutEdge chain.
    for (const edge of vertex.InEdges) {
      if (PointComparer.GetDirections(vertex.point, edge.SourcePoint) == dir) {
        return edge.Source
      }
    }

    //  Avoid GetEnumerator overhead.
    for (const edge of vertex.OutEdges) {
      if (PointComparer.GetDirections(vertex.point, edge.TargetPoint) == dir) {
        return edge.Target
      }
    }

    return null
  }

  static FindAdjacentEdge(a: VisibilityVertex, dir: Direction): VisibilityEdge {
    for (const edge of a.InEdges) {
      if (PointComparer.GetDirections(edge.SourcePoint, a.point) == dir) {
        return edge
      }
    }

    for (const edge of a.OutEdges) {
      if (PointComparer.GetDirections(a.point, edge.TargetPoint) == dir) {
        return edge
      }
    }
    return null
  }

  static FindBendPointBetween(sourcePoint: Point, targetPoint: Point, finalEdgeDir: Direction): Point {
    return !StaticGraphUtility.IsVerticalD(finalEdgeDir) ? new Point(sourcePoint.x, targetPoint.y) : new Point(targetPoint.x, sourcePoint.y)
  }

  static SegmentIntersectionPPP(first: Point, second: Point, from: Point): Point {
    const dir = PointComparer.GetDirections(first, second)
    return StaticGraphUtility.IsVerticalD(dir) ? new Point(first.x, from.y) : new Point(from.x, first.y)
  }

  static SegmentIntersectionSP(seg: SegmentBase, from: Point): Point {
    return StaticGraphUtility.SegmentIntersectionPPP(seg.Start, seg.End, from)
  }

  static SegmentsIntersection(first: SegmentBase, second: SegmentBase): Point {
    return StaticGraphUtility.IntervalsIntersect(first.Start, first.End, second.Start, second.End)
  }

  static SegmentsIntersectLL(first: LineSegment, second: LineSegment): Point {
    return StaticGraphUtility.IntervalsIntersect(first.start, first.end, second.start, second.end)
  }

  static IntervalsOverlapSS(first: SegmentBase, second: SegmentBase): boolean {
    return StaticGraphUtility.IntervalsOverlapPPPP(first.Start, first.End, second.Start, second.End)
  }

  static IntervalsOverlapPPPP(start1: Point, end1: Point, start2: Point, end2: Point): boolean {
    return (
      StaticGraphUtility.IntervalsAreCollinear(start1, end1, start2, end2) &&
      PointComparer.ComparePP(start1, end2) != PointComparer.ComparePP(end1, start2)
    )
  }

  static IntervalsAreCollinear(start1: Point, end1: Point, start2: Point, end2: Point): boolean {
    /*Assert.assert(
      StaticGraphUtility.IsVerticalPP(start1, end1) ==
        StaticGraphUtility.IsVerticalPP(start2, end2),
      'segments are not in the same orientation',
    )*/
    const vertical: boolean = StaticGraphUtility.IsVerticalPP(start1, end1)
    if (StaticGraphUtility.IsVerticalPP(start2, end2) == vertical) {
      //  This handles touching endpoints as well.
      return vertical ? PointComparer.Equal(start1.x, start2.x) : PointComparer.Equal(start1.y, start2.y)
    }

    return false
  }

  static IntervalsAreSame(start1: Point, end1: Point, start2: Point, end2: Point): boolean {
    return PointComparer.EqualPP(start1, start2) && PointComparer.EqualPP(end1, end2)
  }

  static IntervalsIntersect(firstStart: Point, firstEnd: Point, secondStart: Point, secondEnd: Point): Point {
    /*Assert.assert(
      StaticGraphUtility.IsVerticalPP(firstStart, firstEnd) !=
        StaticGraphUtility.IsVerticalPP(secondStart, secondEnd),
      'cannot intersect two parallel segments',
    )*/
    const intersect = StaticGraphUtility.SegmentIntersectionPPP(firstStart, firstEnd, secondStart)
    return StaticGraphUtility.PointIsOnSegmentPPP(firstStart, firstEnd, intersect) &&
      StaticGraphUtility.PointIsOnSegmentPPP(secondStart, secondEnd, intersect)
      ? intersect
      : undefined
  }

  static SegmentIntersectionEP(edge: VisibilityEdge, from: Point): Point {
    return StaticGraphUtility.SegmentIntersectionPPP(edge.SourcePoint, edge.TargetPoint, from)
  }

  static PointIsOnSegmentPPP(first: Point, second: Point, test: Point): boolean {
    return (
      PointComparer.EqualPP(first, test) ||
      PointComparer.EqualPP(second, test) ||
      PointComparer.GetDirections(first, test) == PointComparer.GetDirections(test, second)
    )
  }

  static PointIsOnSegmentSP(seg: SegmentBase, test: Point): boolean {
    return StaticGraphUtility.PointIsOnSegmentPPP(seg.Start, seg.End, test)
  }

  static IsVerticalD(dir: Direction): boolean {
    return 0 != (dir & (Direction.North | Direction.South))
  }

  static IsVerticalE(edge: VisibilityEdge): boolean {
    return StaticGraphUtility.IsVerticalD(PointComparer.GetDirections(edge.SourcePoint, edge.TargetPoint))
  }

  static IsVerticalPP(first: Point, second: Point): boolean {
    return StaticGraphUtility.IsVerticalD(PointComparer.GetDirections(first, second))
  }

  static IsVertical(seg: LineSegment): boolean {
    return StaticGraphUtility.IsVerticalD(PointComparer.GetDirections(seg.start, seg.end))
  }

  static IsAscending(dir: Direction): boolean {
    return (dir & (Direction.North | Direction.East)) != 0
  }

  static Slope(start: Point, end: Point, scanDir: ScanDirection): number {
    //  Find the slope relative to scanline - how much scan coord changes per sweep change.
    const lineDir = end.sub(start)
    return lineDir.dot(scanDir.PerpDirectionAsPoint) / lineDir.dot(scanDir.DirectionAsPoint)
  }

  static SortAscending(a: Point, b: Point): [Point, Point] {
    const dir: Direction = PointComparer.GetDirections(a, b)
    /*Assert.assert(
      Direction.None == dir || PointComparer.IsPureDirectionD(dir),
      'SortAscending with impure direction',
    )*/
    return Direction.None == dir || StaticGraphUtility.IsAscending(dir) ? [a, b] : [b, a]
  }

  static RectangleBorderIntersect(boundingBox: Rectangle, point: Point, dir: Direction): Point {
    switch (dir) {
      case Direction.North:
      case Direction.South:
        return new Point(point.x, StaticGraphUtility.GetRectangleBound(boundingBox, dir))
        break
      case Direction.East:
      case Direction.West:
        return new Point(StaticGraphUtility.GetRectangleBound(boundingBox, dir), point.y)
        break
      default:
        throw new Error()
        break
    }
  }

  static GetRectangleBound(rect: Rectangle, dir: Direction): number {
    switch (dir) {
      case Direction.North:
        return rect.top
        break
      case Direction.South:
        return rect.bottom
        break
      case Direction.East:
        return rect.right
        break
      case Direction.West:
        return rect.left
        break
      default:
        throw new Error()
        break
    }
  }

  static RectangleInteriorsIntersect(a: Rectangle, b: Rectangle): boolean {
    return (
      PointComparer.Compare(a.bottom, b.top) < 0 &&
      PointComparer.Compare(b.bottom, a.top) < 0 &&
      PointComparer.Compare(a.left, b.right) < 0 &&
      PointComparer.Compare(b.left, a.right) < 0
    )
  }

  static PointIsInRectangleInterior(point: Point, rect: Rectangle): boolean {
    return (
      PointComparer.Compare(point.y, rect.top) < 0 &&
      PointComparer.Compare(rect.bottom, point.y) < 0 &&
      PointComparer.Compare(point.x, rect.right) < 0 &&
      PointComparer.Compare(rect.left, point.x) < 0
    )
  }
}
