import CGAlgorithms from '../../algorithm/CGAlgorithms'
import TopologyException from '../../geom/TopologyException'
import MaximalEdgeRing from './MaximalEdgeRing'
import ArrayList from '../../../../../java/util/ArrayList'
import Assert from '../../util/Assert'
import PlanarGraph from '../../geomgraph/PlanarGraph'

export default class PolygonBuilder {
  constructor () {
    this._geometryFactory = null
    this._shellList = new ArrayList()
    let geometryFactory = arguments[0]
    this._geometryFactory = geometryFactory
  }
  sortShellsAndHoles (edgeRings, shellList, freeHoleList) {
    for (const it = edgeRings.iterator(); it.hasNext();) {
      const er = it.next()
      if (er.isHole()) {
        freeHoleList.add(er)
      } else {
        shellList.add(er)
      }
    }
  }
  computePolygons (shellList) {
    const resultPolyList = new ArrayList()
    for (const it = shellList.iterator(); it.hasNext();) {
      const er = it.next()
      const poly = er.toPolygon(this._geometryFactory)
      resultPolyList.add(poly)
    }
    return resultPolyList
  }
  placeFreeHoles (shellList, freeHoleList) {
    for (const it = freeHoleList.iterator(); it.hasNext();) {
      const hole = it.next()
      if (hole.getShell() === null) {
        const shell = this.findEdgeRingContaining(hole, shellList)
        if (shell === null) throw new TopologyException('unable to assign hole to a shell', hole.getCoordinate(0))
        hole.setShell(shell)
      }
    }
  }
  buildMinimalEdgeRings (maxEdgeRings, shellList, freeHoleList) {
    const edgeRings = new ArrayList()
    for (const it = maxEdgeRings.iterator(); it.hasNext();) {
      const er = it.next()
      if (er.getMaxNodeDegree() > 2) {
        er.linkDirectedEdgesForMinimalEdgeRings()
        const minEdgeRings = er.buildMinimalRings()
        const shell = this.findShell(minEdgeRings)
        if (shell !== null) {
          this.placePolygonHoles(shell, minEdgeRings)
          shellList.add(shell)
        } else {
          freeHoleList.addAll(minEdgeRings)
        }
      } else {
        edgeRings.add(er)
      }
    }
    return edgeRings
  }
  containsPoint (p) {
    for (const it = this._shellList.iterator(); it.hasNext();) {
      const er = it.next()
      if (er.containsPoint(p)) return true
    }
    return false
  }
  buildMaximalEdgeRings (dirEdges) {
    const maxEdgeRings = new ArrayList()
    for (const it = dirEdges.iterator(); it.hasNext();) {
      const de = it.next()
      if (de.isInResult() && de.getLabel().isArea()) {
        if (de.getEdgeRing() === null) {
          const er = new MaximalEdgeRing(de, this._geometryFactory)
          maxEdgeRings.add(er)
          er.setInResult()
        }
      }
    }
    return maxEdgeRings
  }
  placePolygonHoles (shell, minEdgeRings) {
    for (const it = minEdgeRings.iterator(); it.hasNext();) {
      const er = it.next()
      if (er.isHole()) {
        er.setShell(shell)
      }
    }
  }
  getPolygons () {
    const resultPolyList = this.computePolygons(this._shellList)
    return resultPolyList
  }
  findEdgeRingContaining (testEr, shellList) {
    const testRing = testEr.getLinearRing()
    const testEnv = testRing.getEnvelopeInternal()
    const testPt = testRing.getCoordinateN(0)
    let minShell = null
    let minEnv = null
    for (const it = shellList.iterator(); it.hasNext();) {
      const tryShell = it.next()
      const tryRing = tryShell.getLinearRing()
      const tryEnv = tryRing.getEnvelopeInternal()
      if (minShell !== null) minEnv = minShell.getLinearRing().getEnvelopeInternal()
      let isContained = false
      if (tryEnv.contains(testEnv) && CGAlgorithms.isPointInRing(testPt, tryRing.getCoordinates())) isContained = true
      if (isContained) {
        if (minShell === null || minEnv.contains(tryEnv)) {
          minShell = tryShell
        }
      }
    }
    return minShell
  }
  findShell (minEdgeRings) {
    let shellCount = 0
    let shell = null
    for (const it = minEdgeRings.iterator(); it.hasNext();) {
      const er = it.next()
      if (!er.isHole()) {
        shell = er
        shellCount++
      }
    }
    Assert.isTrue(shellCount <= 1, 'found two shells in MinimalEdgeRing list')
    return shell
  }
  add () {
    if (arguments.length === 1) {
      const graph = arguments[0]
      this.add(graph.getEdgeEnds(), graph.getNodes())
    } else if (arguments.length === 2) {
      const dirEdges = arguments[0]
      const nodes = arguments[1]
      PlanarGraph.linkResultDirectedEdges(nodes)
      const maxEdgeRings = this.buildMaximalEdgeRings(dirEdges)
      const freeHoleList = new ArrayList()
      const edgeRings = this.buildMinimalEdgeRings(maxEdgeRings, this._shellList, freeHoleList)
      this.sortShellsAndHoles(edgeRings, this._shellList, freeHoleList)
      this.placeFreeHoles(this._shellList, freeHoleList)
    }
  }
  interfaces_ () {
    return []
  }
  getClass () {
    return PolygonBuilder
  }
}
