import StringBuffer from '../../../../java/lang/StringBuffer'
import EdgeIntersectionList from './EdgeIntersectionList'
import MonotoneChainEdge from './index/MonotoneChainEdge'
import Position from './Position'
import Coordinate from '../geom/Coordinate'
import Label from './Label'
import Envelope from '../geom/Envelope'
import Depth from './Depth'
import GraphComponent from './GraphComponent'

export default class Edge extends GraphComponent {
  constructor () {
    super()
    this.pts = null
    this._env = null
    this.eiList = new EdgeIntersectionList(this)
    this._name = null
    this._mce = null
    this._isIsolated = true
    this._depth = new Depth()
    this._depthDelta = 0
    if (arguments.length === 1) {
      const pts = arguments[0]
      Edge.call(this, pts, null)
    } else if (arguments.length === 2) {
      const pts = arguments[0]
      const label = arguments[1]
      this.pts = pts
      this._label = label
    }
  }
  getDepth () {
    return this._depth
  }
  getCollapsedEdge () {
    const newPts = new Array(2).fill(null)
    newPts[0] = this.pts[0]
    newPts[1] = this.pts[1]
    const newe = new Edge(newPts, Label.toLineLabel(this._label))
    return newe
  }
  isIsolated () {
    return this._isIsolated
  }
  getCoordinates () {
    return this.pts
  }
  setIsolated (isIsolated) {
    this._isIsolated = isIsolated
  }
  setName (name) {
    this._name = name
  }
  equals (o) {
    if (!(o instanceof Edge)) return false
    const e = o
    if (this.pts.length !== e.pts.length) return false
    let isEqualForward = true
    let isEqualReverse = true
    let iRev = this.pts.length
    for (let i = 0; i < this.pts.length; i++) {
      if (!this.pts[i].equals2D(e.pts[i])) {
        isEqualForward = false
      }
      if (!this.pts[i].equals2D(e.pts[--iRev])) {
        isEqualReverse = false
      }
      if (!isEqualForward && !isEqualReverse) return false
    }
    return true
  }
  getCoordinate () {
    if (arguments.length === 0) {
      if (this.pts.length > 0) return this.pts[0]
      return null
    } else if (arguments.length === 1) {
      const i = arguments[0]
      return this.pts[i]
    }
  }
  print (out) {
    out.print('edge ' + this._name + ': ')
    out.print('LINESTRING (')
    for (let i = 0; i < this.pts.length; i++) {
      if (i > 0) out.print(',')
      out.print(this.pts[i].x + ' ' + this.pts[i].y)
    }
    out.print(')  ' + this._label + ' ' + this._depthDelta)
  }
  computeIM (im) {
    Edge.updateIM(this._label, im)
  }
  isCollapsed () {
    if (!this._label.isArea()) return false
    if (this.pts.length !== 3) return false
    if (this.pts[0].equals(this.pts[2])) return true
    return false
  }
  isClosed () {
    return this.pts[0].equals(this.pts[this.pts.length - 1])
  }
  getMaximumSegmentIndex () {
    return this.pts.length - 1
  }
  getDepthDelta () {
    return this._depthDelta
  }
  getNumPoints () {
    return this.pts.length
  }
  printReverse (out) {
    out.print('edge ' + this._name + ': ')
    for (let i = this.pts.length - 1; i >= 0; i--) {
      out.print(this.pts[i] + ' ')
    }
    out.println('')
  }
  getMonotoneChainEdge () {
    if (this._mce === null) this._mce = new MonotoneChainEdge(this)
    return this._mce
  }
  getEnvelope () {
    if (this._env === null) {
      this._env = new Envelope()
      for (let i = 0; i < this.pts.length; i++) {
        this._env.expandToInclude(this.pts[i])
      }
    }
    return this._env
  }
  addIntersection (li, segmentIndex, geomIndex, intIndex) {
    const intPt = new Coordinate(li.getIntersection(intIndex))
    let normalizedSegmentIndex = segmentIndex
    let dist = li.getEdgeDistance(geomIndex, intIndex)
    const nextSegIndex = normalizedSegmentIndex + 1
    if (nextSegIndex < this.pts.length) {
      const nextPt = this.pts[nextSegIndex]
      if (intPt.equals2D(nextPt)) {
        normalizedSegmentIndex = nextSegIndex
        dist = 0.0
      }
    }
    this.eiList.add(intPt, normalizedSegmentIndex, dist)
  }
  toString () {
    const buf = new StringBuffer()
    buf.append('edge ' + this._name + ': ')
    buf.append('LINESTRING (')
    for (let i = 0; i < this.pts.length; i++) {
      if (i > 0) buf.append(',')
      buf.append(this.pts[i].x + ' ' + this.pts[i].y)
    }
    buf.append(')  ' + this._label + ' ' + this._depthDelta)
    return buf.toString()
  }
  isPointwiseEqual (e) {
    if (this.pts.length !== e.pts.length) return false
    for (let i = 0; i < this.pts.length; i++) {
      if (!this.pts[i].equals2D(e.pts[i])) {
        return false
      }
    }
    return true
  }
  setDepthDelta (depthDelta) {
    this._depthDelta = depthDelta
  }
  getEdgeIntersectionList () {
    return this.eiList
  }
  addIntersections (li, segmentIndex, geomIndex) {
    for (let i = 0; i < li.getIntersectionNum(); i++) {
      this.addIntersection(li, segmentIndex, geomIndex, i)
    }
  }
  interfaces_ () {
    return []
  }
  getClass () {
    return Edge
  }
  static updateIM () {
    if (arguments.length === 2) {
      const label = arguments[0]
      const im = arguments[1]
      im.setAtLeastIfValid(label.getLocation(0, Position.ON), label.getLocation(1, Position.ON), 1)
      if (label.isArea()) {
        im.setAtLeastIfValid(label.getLocation(0, Position.LEFT), label.getLocation(1, Position.LEFT), 2)
        im.setAtLeastIfValid(label.getLocation(0, Position.RIGHT), label.getLocation(1, Position.RIGHT), 2)
      }
    } else return GraphComponent.prototype.updateIM.apply(this, arguments)
  }
}
