import LineString from '../geom/LineString'
import CoordinateList from '../geom/CoordinateList'
import Geometry from '../geom/Geometry'
import hasInterface from '../../../../hasInterface'
import Collection from '../../../../java/util/Collection'
import Stack from '../../../../java/util/Stack'
import MarkHalfEdge from '../edgegraph/MarkHalfEdge'
import DissolveEdgeGraph from './DissolveEdgeGraph'
import GeometryComponentFilter from '../geom/GeometryComponentFilter'
import ArrayList from '../../../../java/util/ArrayList'

export default class LineDissolver {
  constructor () {
    this._result = null
    this._factory = null
    this._graph = null
    this._lines = new ArrayList()
    this._nodeEdgeStack = new Stack()
    this._ringStartEdge = null
    this._graph = new DissolveEdgeGraph()
  }
  addLine (line) {
    this._lines.add(this._factory.createLineString(line.toCoordinateArray()))
  }
  updateRingStartEdge (e) {
    if (!e.isStart()) {
      e = e.sym()
      if (!e.isStart()) return null
    }
    if (this._ringStartEdge === null) {
      this._ringStartEdge = e
      return null
    }
    if (e.orig().compareTo(this._ringStartEdge.orig()) < 0) {
      this._ringStartEdge = e
    }
  }
  getResult () {
    if (this._result === null) this.computeResult()
    return this._result
  }
  process (e) {
    let eNode = e.prevNode()
    if (eNode === null) eNode = e
    this.stackEdges(eNode)
    this.buildLines()
  }
  buildRing (eStartRing) {
    const line = new CoordinateList()
    let e = eStartRing
    line.add(e.orig().copy(), false)
    while (e.sym().degree() === 2) {
      const eNext = e.next()
      if (eNext === eStartRing) break
      line.add(eNext.orig().copy(), false)
      e = eNext
    }
    line.add(e.dest().copy(), false)
    this.addLine(line)
  }
  buildLine (eStart) {
    const line = new CoordinateList()
    let e = eStart
    this._ringStartEdge = null
    MarkHalfEdge.markBoth(e)
    line.add(e.orig().copy(), false)
    while (e.sym().degree() === 2) {
      this.updateRingStartEdge(e)
      const eNext = e.next()
      if (eNext === eStart) {
        this.buildRing(this._ringStartEdge)
        return null
      }
      line.add(eNext.orig().copy(), false)
      e = eNext
      MarkHalfEdge.markBoth(e)
    }
    line.add(e.dest().copy(), false)
    this.stackEdges(e.sym())
    this.addLine(line)
  }
  stackEdges (node) {
    let e = node
    do {
      if (!MarkHalfEdge.isMarked(e)) this._nodeEdgeStack.add(e)
      e = e.oNext()
    } while (e !== node)
  }
  computeResult () {
    const edges = this._graph.getVertexEdges()
    for (const i = edges.iterator(); i.hasNext();) {
      const e = i.next()
      if (MarkHalfEdge.isMarked(e)) continue
      this.process(e)
    }
    this._result = this._factory.buildGeometry(this._lines)
  }
  buildLines () {
    while (!this._nodeEdgeStack.empty()) {
      const e = this._nodeEdgeStack.pop()
      if (MarkHalfEdge.isMarked(e)) continue
      this.buildLine(e)
    }
  }
  add () {
    if (arguments[0] instanceof Geometry) {
      let geometry = arguments[0]
      geometry.appy({
        interfaces_: function () {
          return [GeometryComponentFilter]
        },
        filter: function (component) {
          if (component instanceof LineString) {
            this.add(component)
          }
        }
      })
    } else if (hasInterface(arguments[0], Collection)) {
      let geometries = arguments[0]
      for (const i = geometries.iterator(); i.hasNext();) {
        const geometry = i.next()
        this.add(geometry)
      }
    } else if (arguments[0] instanceof LineString) {
      let lineString = arguments[0]
      if (this._factory === null) {
        this._factory = lineString.getFactory()
      }
      const seq = lineString.getCoordinateSequence()
      let doneStart = false
      for (let i = 1; i < seq.size(); i++) {
        const e = this._graph.addEdge(seq.getCoordinate(i - 1), seq.getCoordinate(i))
        if (e === null) continue
        if (!doneStart) {
          e.setStart()
          doneStart = true
        }
      }
    }
  }
  interfaces_ () {
    return []
  }
  getClass () {
    return LineDissolver
  }
  static dissolve (g) {
    const d = new LineDissolver()
    d.add(g)
    return d.getResult()
  }
}
