import StringBuffer from '../../../../java/lang/StringBuffer'
import Location from '../geom/Location'
import Position from './Position'
import TopologyException from '../geom/TopologyException'
import System from '../../../../java/lang/System'
import SimplePointInAreaLocator from '../algorithm/locate/SimplePointInAreaLocator'
import ArrayList from '../../../../java/util/ArrayList'
import Assert from '../util/Assert'
import TreeMap from '../../../../java/util/TreeMap'

export default class EdgeEndStar {
  constructor () {
    this._edgeMap = new TreeMap()
    this._edgeList = null
    this._ptInAreaLocation = [Location.NONE, Location.NONE]
  }
  getNextCW (ee) {
    this.getEdges()
    const i = this._edgeList.indexOf(ee)
    let iNextCW = i - 1
    if (i === 0) iNextCW = this._edgeList.size() - 1
    return this._edgeList.get(iNextCW)
  }
  propagateSideLabels (geomIndex) {
    let startLoc = Location.NONE
    for (const it = this.iterator(); it.hasNext();) {
      const e = it.next()
      const label = e.getLabel()
      if (label.isArea(geomIndex) && label.getLocation(geomIndex, Position.LEFT) !== Location.NONE) startLoc = label.getLocation(geomIndex, Position.LEFT)
    }
    if (startLoc === Location.NONE) return null
    let currLoc = startLoc
    for (const it = this.iterator(); it.hasNext();) {
      const e = it.next()
      const label = e.getLabel()
      if (label.getLocation(geomIndex, Position.ON) === Location.NONE) label.setLocation(geomIndex, Position.ON, currLoc)
      if (label.isArea(geomIndex)) {
        const leftLoc = label.getLocation(geomIndex, Position.LEFT)
        const rightLoc = label.getLocation(geomIndex, Position.RIGHT)
        if (rightLoc !== Location.NONE) {
          if (rightLoc !== currLoc) throw new TopologyException('side location conflict', e.getCoordinate())
          if (leftLoc === Location.NONE) {
            Assert.shouldNeverReachHere('found single null side (at ' + e.getCoordinate() + ')')
          }
          currLoc = leftLoc
        } else {
          Assert.isTrue(label.getLocation(geomIndex, Position.LEFT) === Location.NONE, 'found single null side')
          label.setLocation(geomIndex, Position.RIGHT, currLoc)
          label.setLocation(geomIndex, Position.LEFT, currLoc)
        }
      }
    }
  }
  getCoordinate () {
    const it = this.iterator()
    if (!it.hasNext()) return null
    const e = it.next()
    return e.getCoordinate()
  }
  print (out) {
    System.out.println('EdgeEndStar:   ' + this.getCoordinate())
    for (const it = this.iterator(); it.hasNext();) {
      const e = it.next()
      e.print(out)
    }
  }
  isAreaLabelsConsistent (geomGraph) {
    this.computeEdgeEndLabels(geomGraph.getBoundaryNodeRule())
    return this.checkAreaLabelsConsistent(0)
  }
  checkAreaLabelsConsistent (geomIndex) {
    const edges = this.getEdges()
    if (edges.size() <= 0) return true
    const lastEdgeIndex = edges.size() - 1
    const startLabel = edges.get(lastEdgeIndex).getLabel()
    const startLoc = startLabel.getLocation(geomIndex, Position.LEFT)
    Assert.isTrue(startLoc !== Location.NONE, 'Found unlabelled area edge')
    let currLoc = startLoc
    for (const it = this.iterator(); it.hasNext();) {
      const e = it.next()
      const label = e.getLabel()
      Assert.isTrue(label.isArea(geomIndex), 'Found non-area edge')
      const leftLoc = label.getLocation(geomIndex, Position.LEFT)
      const rightLoc = label.getLocation(geomIndex, Position.RIGHT)
      if (leftLoc === rightLoc) {
        return false
      }
      if (rightLoc !== currLoc) {
        return false
      }
      currLoc = leftLoc
    }
    return true
  }
  findIndex (eSearch) {
    this.iterator()
    for (let i = 0; i < this._edgeList.size(); i++) {
      const e = this._edgeList.get(i)
      if (e === eSearch) return i
    }
    return -1
  }
  iterator () {
    return this.getEdges().iterator()
  }
  getEdges () {
    if (this._edgeList === null) {
      this._edgeList = new ArrayList(this._edgeMap.values())
    }
    return this._edgeList
  }
  getLocation (geomIndex, p, geom) {
    if (this._ptInAreaLocation[geomIndex] === Location.NONE) {
      this._ptInAreaLocation[geomIndex] = SimplePointInAreaLocator.locate(p, geom[geomIndex].getGeometry())
    }
    return this._ptInAreaLocation[geomIndex]
  }
  toString () {
    const buf = new StringBuffer()
    buf.append('EdgeEndStar:   ' + this.getCoordinate())
    buf.append('\n')
    for (const it = this.iterator(); it.hasNext();) {
      const e = it.next()
      buf.append(e)
      buf.append('\n')
    }
    return buf.toString()
  }
  computeEdgeEndLabels (boundaryNodeRule) {
    for (const it = this.iterator(); it.hasNext();) {
      const ee = it.next()
      ee.computeLabel(boundaryNodeRule)
    }
  }
  computeLabelling (geomGraph) {
    this.computeEdgeEndLabels(geomGraph[0].getBoundaryNodeRule())
    this.propagateSideLabels(0)
    this.propagateSideLabels(1)
    const hasDimensionalCollapseEdge = [false, false]
    for (const it = this.iterator(); it.hasNext();) {
      const e = it.next()
      const label = e.getLabel()
      for (let geomi = 0; geomi < 2; geomi++) {
        if (label.isLine(geomi) && label.getLocation(geomi) === Location.BOUNDARY) hasDimensionalCollapseEdge[geomi] = true
      }
    }
    for (const it = this.iterator(); it.hasNext();) {
      const e = it.next()
      const label = e.getLabel()
      for (let geomi = 0; geomi < 2; geomi++) {
        if (label.isAnyNull(geomi)) {
          let loc = Location.NONE
          if (hasDimensionalCollapseEdge[geomi]) {
            loc = Location.EXTERIOR
          } else {
            const p = e.getCoordinate()
            loc = this.getLocation(geomi, p, geomGraph)
          }
          label.setAllLocationsIfNull(geomi, loc)
        }
      }
    }
  }
  getDegree () {
    return this._edgeMap.size()
  }
  insertEdgeEnd (e, obj) {
    this._edgeMap.put(e, obj)
    this._edgeList = null
  }
  interfaces_ () {
    return []
  }
  getClass () {
    return EdgeEndStar
  }
}
