import Location from '../geom/Location'
import CGAlgorithms from '../algorithm/CGAlgorithms'
import Position from './Position'
import TopologyException from '../geom/TopologyException'
import Label from './Label'
import ArrayList from '../../../../java/util/ArrayList'
import Assert from '../util/Assert'

export default class EdgeRing {
  constructor () {
    this._startDe = null
    this._maxNodeDegree = -1
    this._edges = new ArrayList()
    this._pts = new ArrayList()
    this._label = new Label(Location.NONE)
    this._ring = null
    this._isHole = null
    this._shell = null
    this._holes = new ArrayList()
    this._geometryFactory = null
    const start = arguments[0]
    const geometryFactory = arguments[1]
    this._geometryFactory = geometryFactory
    this.computePoints(start)
    this.computeRing()
  }
  computeRing () {
    if (this._ring !== null) return null
    const coord = new Array(this._pts.size()).fill(null)
    for (let i = 0; i < this._pts.size(); i++) {
      coord[i] = this._pts.get(i)
    }
    this._ring = this._geometryFactory.createLinearRing(coord)
    this._isHole = CGAlgorithms.isCCW(this._ring.getCoordinates())
  }
  isIsolated () {
    return this._label.getGeometryCount() === 1
  }
  computePoints (start) {
    this._startDe = start
    let de = start
    let isFirstEdge = true
    do {
      if (de === null) throw new TopologyException('Found null DirectedEdge')
      if (de.getEdgeRing() === this) throw new TopologyException('Directed Edge visited twice during ring-building at ' + de.getCoordinate())
      this._edges.add(de)
      const label = de.getLabel()
      Assert.isTrue(label.isArea())
      this.mergeLabel(label)
      this.addPoints(de.getEdge(), de.isForward(), isFirstEdge)
      isFirstEdge = false
      this.setEdgeRing(de, this)
      de = this.getNext(de)
    } while (de !== this._startDe)
  }
  getLinearRing () {
    return this._ring
  }
  getCoordinate (i) {
    return this._pts.get(i)
  }
  computeMaxNodeDegree () {
    this._maxNodeDegree = 0
    let de = this._startDe
    do {
      const node = de.getNode()
      const degree = node.getEdges().getOutgoingDegree(this)
      if (degree > this._maxNodeDegree) this._maxNodeDegree = degree
      de = this.getNext(de)
    } while (de !== this._startDe)
    this._maxNodeDegree *= 2
  }
  addPoints (edge, isForward, isFirstEdge) {
    const edgePts = edge.getCoordinates()
    if (isForward) {
      let startIndex = 1
      if (isFirstEdge) startIndex = 0
      for (let i = startIndex; i < edgePts.length; i++) {
        this._pts.add(edgePts[i])
      }
    } else {
      let startIndex = edgePts.length - 2
      if (isFirstEdge) startIndex = edgePts.length - 1
      for (let i = startIndex; i >= 0; i--) {
        this._pts.add(edgePts[i])
      }
    }
  }
  isHole () {
    return this._isHole
  }
  setInResult () {
    let de = this._startDe
    do {
      de.getEdge().setInResult(true)
      de = de.getNext()
    } while (de !== this._startDe)
  }
  containsPoint (p) {
    const shell = this.getLinearRing()
    const env = shell.getEnvelopeInternal()
    if (!env.contains(p)) return false
    if (!CGAlgorithms.isPointInRing(p, shell.getCoordinates())) return false
    for (let i = this._holes.iterator(); i.hasNext();) {
      const hole = i.next()
      if (hole.containsPoint(p)) return false
    }
    return true
  }
  addHole (ring) {
    this._holes.add(ring)
  }
  isShell () {
    return this._shell === null
  }
  getLabel () {
    return this._label
  }
  getEdges () {
    return this._edges
  }
  getMaxNodeDegree () {
    if (this._maxNodeDegree < 0) this.computeMaxNodeDegree()
    return this._maxNodeDegree
  }
  getShell () {
    return this._shell
  }
  mergeLabel () {
    if (arguments.length === 1) {
      const deLabel = arguments[0]
      this.mergeLabel(deLabel, 0)
      this.mergeLabel(deLabel, 1)
    } else if (arguments.length === 2) {
      const deLabel = arguments[0]
      const geomIndex = arguments[1]
      const loc = deLabel.getLocation(geomIndex, Position.RIGHT)
      if (loc === Location.NONE) return null
      if (this._label.getLocation(geomIndex) === Location.NONE) {
        this._label.setLocation(geomIndex, loc)
        return null
      }
    }
  }
  setShell (shell) {
    this._shell = shell
    if (shell !== null) shell.addHole(this)
  }
  toPolygon (geometryFactory) {
    const holeLR = new Array(this._holes.size()).fill(null)
    for (let i = 0; i < this._holes.size(); i++) {
      holeLR[i] = this._holes.get(i).getLinearRing()
    }
    const poly = geometryFactory.createPolygon(this.getLinearRing(), holeLR)
    return poly
  }
  interfaces_ () {
    return []
  }
  getClass () {
    return EdgeRing
  }
}
