import GeometryFactory from '../geom/GeometryFactory'
import NonEncroachingSplitPointFinder from './NonEncroachingSplitPointFinder'
import ConstraintVertex from './ConstraintVertex'
import Coordinate from '../geom/Coordinate'
import IncrementalDelaunayTriangulator from './IncrementalDelaunayTriangulator'
import QuadEdgeSubdivision from './quadedge/QuadEdgeSubdivision'
import Double from '../../../../java/lang/Double'
import LastFoundQuadEdgeLocator from './quadedge/LastFoundQuadEdgeLocator'
import Segment from './Segment'
import ConvexHull from '../algorithm/ConvexHull'
import KdTree from '../index/kdtree/KdTree'
import ArrayList from '../../../../java/util/ArrayList'
import Envelope from '../geom/Envelope'

export default class ConformingDelaunayTriangulator {
  constructor () {
    this._initialVertices = null
    this._segVertices = null
    this._segments = new ArrayList()
    this._subdiv = null
    this._incDel = null
    this._convexHull = null
    this._splitFinder = new NonEncroachingSplitPointFinder()
    this._kdt = null
    this._vertexFactory = null
    this._computeAreaEnv = null
    this._splitPt = null
    this._tolerance = null
    const initialVertices = arguments[0]
    const tolerance = arguments[1]
    this._initialVertices = new ArrayList(initialVertices)
    this._tolerance = tolerance
    this._kdt = new KdTree(tolerance)
  }
  getInitialVertices () {
    return this._initialVertices
  }
  getKDT () {
    return this._kdt
  }
  enforceConstraints () {
    this.addConstraintVertices()
    let count = 0
    let splits = 0
    do {
      splits = this.enforceGabriel(this._segments)
      count++
    } while (splits > 0 && count < ConformingDelaunayTriangulator.MAX_SPLIT_ITER)
  }
  insertSites (vertices) {
    for (const i = vertices.iterator(); i.hasNext();) {
      const v = i.next()
      this.insertSite(v)
    }
  }
  getVertexFactory () {
    return this._vertexFactory
  }
  getPointArray () {
    const pts = new Array(this._initialVertices.size() + this._segVertices.size()).fill(null)
    let index = 0
    for (const i = this._initialVertices.iterator(); i.hasNext();) {
      const v = i.next()
      pts[index++] = v.getCoordinate()
    }
    for (const i2 = this._segVertices.iterator(); i2.hasNext();) {
      const v = i2.next()
      pts[index++] = v.getCoordinate()
    }
    return pts
  }
  setConstraints (segments, segVertices) {
    this._segments = segments
    this._segVertices = segVertices
  }
  computeConvexHull () {
    const fact = new GeometryFactory()
    const coords = this.getPointArray()
    const hull = new ConvexHull(coords, fact)
    this._convexHull = hull.getConvexHull()
  }
  addConstraintVertices () {
    this.computeConvexHull()
    this.insertSites(this._segVertices)
  }
  findNonGabrielPoint (seg) {
    const p = seg.getStart()
    const q = seg.getEnd()
    const midPt = new Coordinate((p.x + q.x) / 2.0, (p.y + q.y) / 2.0)
    const segRadius = p.distance(midPt)
    const env = new Envelope(midPt)
    env.expandBy(segRadius)
    const result = this._kdt.query(env)
    let closestNonGabriel = null
    let minDist = Double.MAX_VALUE
    for (const i = result.iterator(); i.hasNext();) {
      const nextNode = i.next()
      const testPt = nextNode.getCoordinate()
      if (testPt.equals2D(p) || testPt.equals2D(q)) continue
      const testRadius = midPt.distance(testPt)
      if (testRadius < segRadius) {
        const testDist = testRadius
        if (closestNonGabriel === null || testDist < minDist) {
          closestNonGabriel = testPt
          minDist = testDist
        }
      }
    }
    return closestNonGabriel
  }
  getConstraintSegments () {
    return this._segments
  }
  setSplitPointFinder (splitFinder) {
    this._splitFinder = splitFinder
  }
  getConvexHull () {
    return this._convexHull
  }
  getTolerance () {
    return this._tolerance
  }
  enforceGabriel (segsToInsert) {
    const newSegments = new ArrayList()
    let splits = 0
    const segsToRemove = new ArrayList()
    for (const i = segsToInsert.iterator(); i.hasNext();) {
      const seg = i.next()
      const encroachPt = this.findNonGabrielPoint(seg)
      if (encroachPt === null) continue
      this._splitPt = this._splitFinder.findSplitPoint(seg, encroachPt)
      const splitVertex = this.createVertex(this._splitPt, seg)
      // const insertedVertex = this.insertSite(splitVertex)
      const s1 = new Segment(seg.getStartX(), seg.getStartY(), seg.getStartZ(), splitVertex.getX(), splitVertex.getY(), splitVertex.getZ(), seg.getData())
      const s2 = new Segment(splitVertex.getX(), splitVertex.getY(), splitVertex.getZ(), seg.getEndX(), seg.getEndY(), seg.getEndZ(), seg.getData())
      newSegments.add(s1)
      newSegments.add(s2)
      segsToRemove.add(seg)
      splits = splits + 1
    }
    segsToInsert.removeAll(segsToRemove)
    segsToInsert.addAll(newSegments)
    return splits
  }
  createVertex () {
    if (arguments.length === 1) {
      const p = arguments[0]
      let v = null
      if (this._vertexFactory !== null) v = this._vertexFactory.createVertex(p, null); else v = new ConstraintVertex(p)
      return v
    } else if (arguments.length === 2) {
      const p = arguments[0]
      const seg = arguments[1]
      let v = null
      if (this._vertexFactory !== null) v = this._vertexFactory.createVertex(p, seg); else v = new ConstraintVertex(p)
      v.setOnConstraint(true)
      return v
    }
  }
  getSubdivision () {
    return this._subdiv
  }
  computeBoundingBox () {
    const vertexEnv = ConformingDelaunayTriangulator.computeVertexEnvelope(this._initialVertices)
    const segEnv = ConformingDelaunayTriangulator.computeVertexEnvelope(this._segVertices)
    const allPointsEnv = new Envelope(vertexEnv)
    allPointsEnv.expandToInclude(segEnv)
    const deltaX = allPointsEnv.getWidth() * 0.2
    const deltaY = allPointsEnv.getHeight() * 0.2
    const delta = Math.max(deltaX, deltaY)
    this._computeAreaEnv = new Envelope(allPointsEnv)
    this._computeAreaEnv.expandBy(delta)
  }
  setVertexFactory (vertexFactory) {
    this._vertexFactory = vertexFactory
  }
  formInitialDelaunay () {
    this.computeBoundingBox()
    this._subdiv = new QuadEdgeSubdivision(this._computeAreaEnv, this._tolerance)
    this._subdiv.setLocator(new LastFoundQuadEdgeLocator(this._subdiv))
    this._incDel = new IncrementalDelaunayTriangulator(this._subdiv)
    this.insertSites(this._initialVertices)
  }
  insertSite () {
    if (arguments[0] instanceof ConstraintVertex) {
      const v = arguments[0]
      const kdnode = this._kdt.insert(v.getCoordinate(), v)
      if (!kdnode.isRepeated()) {
        this._incDel.insertSite(v)
      } else {
        const snappedV = kdnode.getData()
        snappedV.merge(v)
        return snappedV
      }
      return v
    } else if (arguments[0] instanceof Coordinate) {
      const p = arguments[0]
      this.insertSite(this.createVertex(p))
    }
  }
  interfaces_ () {
    return []
  }
  getClass () {
    return ConformingDelaunayTriangulator
  }
  static computeVertexEnvelope (vertices) {
    const env = new Envelope()
    for (const i = vertices.iterator(); i.hasNext();) {
      const v = i.next()
      env.expandToInclude(v.getCoordinate())
    }
    return env
  }
  static get MAX_SPLIT_ITER () { return 99 }
}
