import Location from '../../geom/Location'
import LineString from '../../geom/LineString'
import CGAlgorithms from '../../algorithm/CGAlgorithms'
import Position from '../../geomgraph/Position'
import Point from '../../geom/Point'
import NodedSegmentString from '../../noding/NodedSegmentString'
import Polygon from '../../geom/Polygon'
import MultiPoint from '../../geom/MultiPoint'
import LinearRing from '../../geom/LinearRing'
import MultiPolygon from '../../geom/MultiPolygon'
import Label from '../../geomgraph/Label'
import GeometryCollection from '../../geom/GeometryCollection'
import CoordinateArrays from '../../geom/CoordinateArrays'
import ArrayList from '../../../../../java/util/ArrayList'
import MultiLineString from '../../geom/MultiLineString'
import Triangle from '../../geom/Triangle'

export default class OffsetCurveSetBuilder {
  constructor () {
    this._inputGeom = null
    this._distance = null
    this._curveBuilder = null
    this._curveList = new ArrayList()
    const inputGeom = arguments[0]
    const distance = arguments[1]
    const curveBuilder = arguments[2]
    this._inputGeom = inputGeom
    this._distance = distance
    this._curveBuilder = curveBuilder
  }
  addPoint (p) {
    if (this._distance <= 0.0) return null
    const coord = p.getCoordinates()
    const curve = this._curveBuilder.getLineCurve(coord, this._distance)
    this.addCurve(curve, Location.EXTERIOR, Location.INTERIOR)
  }
  addPolygon (p) {
    let offsetDistance = this._distance
    let offsetSide = Position.LEFT
    if (this._distance < 0.0) {
      offsetDistance = -this._distance
      offsetSide = Position.RIGHT
    }
    const shell = p.getExteriorRing()
    const shellCoord = CoordinateArrays.removeRepeatedPoints(shell.getCoordinates())
    if (this._distance < 0.0 && this.isErodedCompletely(shell, this._distance)) return null
    if (this._distance <= 0.0 && shellCoord.length < 3) return null
    this.addPolygonRing(shellCoord, offsetDistance, offsetSide, Location.EXTERIOR, Location.INTERIOR)
    for (let i = 0; i < p.getNumInteriorRing(); i++) {
      const hole = p.getInteriorRingN(i)
      const holeCoord = CoordinateArrays.removeRepeatedPoints(hole.getCoordinates())
      if (this._distance > 0.0 && this.isErodedCompletely(hole, -this._distance)) continue
      this.addPolygonRing(holeCoord, offsetDistance, Position.opposite(offsetSide), Location.INTERIOR, Location.EXTERIOR)
    }
  }
  isTriangleErodedCompletely (triangleCoord, bufferDistance) {
    const tri = new Triangle(triangleCoord[0], triangleCoord[1], triangleCoord[2])
    const inCentre = tri.inCentre()
    const distToCentre = CGAlgorithms.distancePointLine(inCentre, tri.p0, tri.p1)
    return distToCentre < Math.abs(bufferDistance)
  }
  addLineString (line) {
    if (this._distance <= 0.0 && !this._curveBuilder.getBufferParameters().isSingleSided()) return null
    const coord = CoordinateArrays.removeRepeatedPoints(line.getCoordinates())
    const curve = this._curveBuilder.getLineCurve(coord, this._distance)
    this.addCurve(curve, Location.EXTERIOR, Location.INTERIOR)
  }
  addCurve (coord, leftLoc, rightLoc) {
    if (coord === null || coord.length < 2) return null
    const e = new NodedSegmentString(coord, new Label(0, Location.BOUNDARY, leftLoc, rightLoc))
    this._curveList.add(e)
  }
  getCurves () {
    this.add(this._inputGeom)
    return this._curveList
  }
  addPolygonRing (coord, offsetDistance, side, cwLeftLoc, cwRightLoc) {
    if (offsetDistance === 0.0 && coord.length < LinearRing.MINIMUM_VALID_SIZE) return null
    let leftLoc = cwLeftLoc
    let rightLoc = cwRightLoc
    if (coord.length >= LinearRing.MINIMUM_VALID_SIZE && CGAlgorithms.isCCW(coord)) {
      leftLoc = cwRightLoc
      rightLoc = cwLeftLoc
      side = Position.opposite(side)
    }
    const curve = this._curveBuilder.getRingCurve(coord, side, offsetDistance)
    this.addCurve(curve, leftLoc, rightLoc)
  }
  add (g) {
    if (g.isEmpty()) return null
    if (g instanceof Polygon) this.addPolygon(g)
    else if (g instanceof LineString) this.addLineString(g)
    else if (g instanceof Point) this.addPoint(g)
    else if (g instanceof MultiPoint) this.addCollection(g)
    else if (g instanceof MultiLineString) this.addCollection(g)
    else if (g instanceof MultiPolygon) this.addCollection(g)
    else if (g instanceof GeometryCollection) this.addCollection(g)
    // else throw new UnsupportedOperationException(g.getClass().getName())
  }
  isErodedCompletely (ring, bufferDistance) {
    const ringCoord = ring.getCoordinates()
    // const minDiam = 0.0
    if (ringCoord.length < 4) return bufferDistance < 0
    if (ringCoord.length === 4) return this.isTriangleErodedCompletely(ringCoord, bufferDistance)
    const env = ring.getEnvelopeInternal()
    const envMinDimension = Math.min(env.getHeight(), env.getWidth())
    if (bufferDistance < 0.0 && 2 * Math.abs(bufferDistance) > envMinDimension) return true
    return false
  }
  addCollection (gc) {
    for (let i = 0; i < gc.getNumGeometries(); i++) {
      const g = gc.getGeometryN(i)
      this.add(g)
    }
  }
  interfaces_ () {
    return []
  }
  getClass () {
    return OffsetCurveSetBuilder
  }
}
