///  This is a point on a path that is not associated with an obstacle, such as
///  a port for the end of a dragged path, or a waypoint.

import {Point, Rectangle, CompassVector, Direction, LineSegment} from '../../math/geometry'
// import {Assert} from '../../utils/assert'
import {VisibilityEdge} from '../visibility/VisibilityEdge'
import {VisibilityVertex} from '../visibility/VisibilityVertex'
import {PointAndCrossingsList} from './PointAndCrossingsList'
import {PointComparer} from './PointComparer'
import {ScanSegment} from './ScanSegment'
import {StaticGraphUtility} from './StaticGraphUtility'
import {TransientGraphUtility} from './TransientGraphUtility'
type SegmentAndCrossings = [LineSegment, PointAndCrossingsList]

export class FreePoint {
  //  The VisibilityVertex for this path point; created if it does not already exist.
  Vertex: VisibilityVertex

  get Point(): Point {
    return this.Vertex.point
  }

  IsOverlapped: boolean

  get InitialWeight(): number {
    return this.IsOverlapped ? ScanSegment.OverlappedWeight : ScanSegment.NormalWeight
  }

  OutOfBoundsDirectionFromGraph: Direction

  get IsOutOfBounds(): boolean {
    return Direction.None != this.OutOfBoundsDirectionFromGraph
  }

  private maxVisibilitySegmentsAndCrossings: SegmentAndCrossings[] = new Array(4)

  //  Called if we must create the vertex.
  constructor(transUtil: TransientGraphUtility, point: Point) {
    this.OutOfBoundsDirectionFromGraph = Direction.None
    this.GetVertex(transUtil, point)
  }

  GetVertex(transUtil: TransientGraphUtility, point: Point) {
    this.Vertex = transUtil.FindOrAddVertex(point)
  }

  //  Adds an edge from this.Vertex to a (possibly new) vertex at an intersection with an
  //  existing Edge that adjoins the point.  We take 'dir' as an input parameter for edge
  //  extension because we may be on the edge so can't calculate the direction.
  AddEdgeToAdjacentEdge(
    transUtil: TransientGraphUtility,
    targetEdge: VisibilityEdge,
    dirToExtend: Direction,
    limitRect: Rectangle,
  ): VisibilityVertex {
    const targetIntersect: Point = StaticGraphUtility.SegmentIntersectionEP(targetEdge, this.Point)
    let targetVertex: VisibilityVertex = transUtil.VisGraph.FindVertex(targetIntersect)
    if (null != targetVertex) {
      this.AddToAdjacentVertex(transUtil, targetVertex, dirToExtend, limitRect)
    } else {
      targetVertex = transUtil.AddEdgeToTargetEdge(this.Vertex, targetEdge, targetIntersect)
    }

    this.ExtendEdgeChain(transUtil, targetVertex, dirToExtend, limitRect)
    return targetVertex
  }

  AddToAdjacentVertex(transUtil: TransientGraphUtility, targetVertex: VisibilityVertex, dirToExtend: Direction, limitRect: Rectangle) {
    if (!PointComparer.EqualPP(this.Point, targetVertex.point)) {
      transUtil.FindOrAddEdge(this.Vertex, targetVertex, this.InitialWeight)
    }

    this.ExtendEdgeChain(transUtil, targetVertex, dirToExtend, limitRect)
  }

  ExtendEdgeChain(transUtil: TransientGraphUtility, targetVertex: VisibilityVertex, dirToExtend: Direction, limitRect: Rectangle) {
    //  Extend the edge chain to the opposite side of the limit rectangle.
    // StaticGraphUtility.Assert((PointComparer.Equal(this.Point, targetVertex.point)
    //                 || (PointComparer.GetPureDirectionVV(this.Point, targetVertex.point) == dirToExtend)), "input dir does not match with to-targetVertex direction", transUtil.ObstacleTree, transUtil.VisGraph);
    let extendOverlapped = this.IsOverlapped
    if (extendOverlapped) {
      //  The initial vertex we connected to may be on the border of the enclosing obstacle,
      //  or of another also-overlapped obstacle.  If the former, we turn off overlap now.
      extendOverlapped = transUtil.ObstacleTree.PointIsInsideAnObstaclePD(targetVertex.point, dirToExtend)
    }

    //  If we're inside an obstacle's boundaries we'll never extend past the end of the obstacle
    //  due to encountering the boundary from the inside.  So start the extension at targetVertex.
    const segmentAndCrossings: SegmentAndCrossings = this.GetSegmentAndCrossings(
      this.IsOverlapped ? targetVertex : this.Vertex,
      dirToExtend,
      transUtil,
    )

    transUtil.ExtendEdgeChainVRLPB(targetVertex, limitRect, segmentAndCrossings[0], segmentAndCrossings[1], extendOverlapped)
  }

  private GetSegmentAndCrossings(
    startVertex: VisibilityVertex,
    dirToExtend: Direction,
    transUtil: TransientGraphUtility,
  ): SegmentAndCrossings {
    const dirIndex = CompassVector.ToIndex(dirToExtend)
    let segmentAndCrossings = this.maxVisibilitySegmentsAndCrossings[dirIndex]
    if (segmentAndCrossings == null) {
      const t: {pacList: PointAndCrossingsList} = {pacList: null}
      const maxVisibilitySegment = transUtil.ObstacleTree.CreateMaxVisibilitySegment(startVertex.point, dirToExtend, t)
      segmentAndCrossings = [maxVisibilitySegment, t.pacList]
      this.maxVisibilitySegmentsAndCrossings[dirIndex] = segmentAndCrossings
    } else {
      //  For a waypoint this will be a target and then a source, so there may be a different lateral edge to
      //  connect to. In that case make sure we are consistent in directions - back up the start point if needed.
      if (PointComparer.GetDirections(startVertex.point, segmentAndCrossings[0].start) == dirToExtend) {
        segmentAndCrossings[0].start = startVertex.point
      }
    }

    return segmentAndCrossings
  }

  MaxVisibilityInDirectionForNonOverlappedFreePoint(dirToExtend: Direction, transUtil: TransientGraphUtility): Point {
    /*Assert.assert(
      !this.IsOverlapped,
      'Do not precalculate overlapped obstacle visibility as we should extend from the outer target vertex instead',
    )*/
    const segmentAndCrossings: SegmentAndCrossings = this.GetSegmentAndCrossings(this.Vertex, dirToExtend, transUtil)
    return segmentAndCrossings[0].end
  }

  AddOobEdgesFromGraphCorner(transUtil: TransientGraphUtility, cornerPoint: Point) {
    const dirs: Direction = PointComparer.GetDirections(cornerPoint, this.Vertex.point)
    const cornerVertex: VisibilityVertex = transUtil.VisGraph.FindVertex(cornerPoint)
    //  For waypoints we want to be able to enter in both directions.
    transUtil.ConnectVertexToTargetVertex(cornerVertex, this.Vertex, dirs & (Direction.North | Direction.South), ScanSegment.NormalWeight)
    transUtil.ConnectVertexToTargetVertex(cornerVertex, this.Vertex, dirs & (Direction.East | Direction.West), ScanSegment.NormalWeight)
  }

  RemoveFromGraph() {
    //  Currently all transient removals and edge restorations are done by TransientGraphUtility itself.
    this.Vertex = null
  }

  public toString(): string {
    return this.Vertex.toString()
  }
}
