import NotRepresentableException from '../algorithm/NotRepresentableException'
import CGAlgorithms from '../algorithm/CGAlgorithms'
import Coordinate from './Coordinate'
import Double from '../../../../java/lang/Double'
import Comparable from '../../../../java/lang/Comparable'
import RobustLineIntersector from '../algorithm/RobustLineIntersector'
import HCoordinate from '../algorithm/HCoordinate'
import Serializable from '../../../../java/io/Serializable'

export default class LineSegment {
  constructor () {
    this.p0 = null
    this.p1 = null
    if (arguments.length === 0) {
      this.p0 = new Coordinate()
      this.p1 = new Coordinate()
    } else if (arguments.length === 1) {
      const ls = arguments[0]
      this.p0 = new Coordinate(ls.p0)
      this.p1 = new Coordinate(ls.p1)
    } else if (arguments.length === 2) {
      this.p0 = arguments[0]
      this.p1 = arguments[1]
    } else if (arguments.length === 4) {
      const x0 = arguments[0]
      const y0 = arguments[1]
      const x1 = arguments[2]
      const y1 = arguments[3]
      this.p0 = new Coordinate(x0, y0)
      this.p1 = new Coordinate(x1, y1)
    }
  }
  minX () {
    return Math.min(this.p0.x, this.p1.x)
  }
  orientationIndex () {
    if (arguments[0] instanceof LineSegment) {
      let seg = arguments[0]
      var orient0 = CGAlgorithms.orientationIndex(this.p0, this.p1, seg.p0)
      var orient1 = CGAlgorithms.orientationIndex(this.p0, this.p1, seg.p1)
      if (orient0 >= 0 && orient1 >= 0) return Math.max(orient0, orient1)
      if (orient0 <= 0 && orient1 <= 0) return Math.max(orient0, orient1)
      return 0
    } else if (arguments[0] instanceof Coordinate) {
      let p = arguments[0]
      return CGAlgorithms.orientationIndex(this.p0, this.p1, p)
    }
  }
  toGeometry (geomFactory) {
    return geomFactory.createLineString([this.p0, this.p1])
  }
  isVertical () {
    return this.p0.x === this.p1.x
  }
  equals (o) {
    if (!(o instanceof LineSegment)) {
      return false
    }
    var other = o
    return this.p0.equals(other.p0) && this.p1.equals(other.p1)
  }
  intersection (line) {
    var li = new RobustLineIntersector()
    li.computeIntersection(this.p0, this.p1, line.p0, line.p1)
    if (li.hasIntersection()) return li.getIntersection(0)
    return null
  }
  project () {
    if (arguments[0] instanceof Coordinate) {
      let p = arguments[0]
      if (p.equals(this.p0) || p.equals(this.p1)) return new Coordinate(p)
      var r = this.projectionFactor(p)
      var coord = new Coordinate()
      coord.x = this.p0.x + r * (this.p1.x - this.p0.x)
      coord.y = this.p0.y + r * (this.p1.y - this.p0.y)
      return coord
    } else if (arguments[0] instanceof LineSegment) {
      let seg = arguments[0]
      var pf0 = this.projectionFactor(seg.p0)
      var pf1 = this.projectionFactor(seg.p1)
      if (pf0 >= 1.0 && pf1 >= 1.0) return null
      if (pf0 <= 0.0 && pf1 <= 0.0) return null
      var newp0 = this.project(seg.p0)
      if (pf0 < 0.0) newp0 = this.p0
      if (pf0 > 1.0) newp0 = this.p1
      var newp1 = this.project(seg.p1)
      if (pf1 < 0.0) newp1 = this.p0
      if (pf1 > 1.0) newp1 = this.p1
      return new LineSegment(newp0, newp1)
    }
  }
  normalize () {
    if (this.p1.compareTo(this.p0) < 0) this.reverse()
  }
  angle () {
    return Math.atan2(this.p1.y - this.p0.y, this.p1.x - this.p0.x)
  }
  getCoordinate (i) {
    if (i === 0) return this.p0
    return this.p1
  }
  distancePerpendicular (p) {
    return CGAlgorithms.distancePointLinePerpendicular(p, this.p0, this.p1)
  }
  minY () {
    return Math.min(this.p0.y, this.p1.y)
  }
  midPoint () {
    return LineSegment.midPoint(this.p0, this.p1)
  }
  projectionFactor (p) {
    if (p.equals(this.p0)) return 0.0
    if (p.equals(this.p1)) return 1.0
    var dx = this.p1.x - this.p0.x
    var dy = this.p1.y - this.p0.y
    var len = dx * dx + dy * dy
    if (len <= 0.0) return Double.NaN
    var r = ((p.x - this.p0.x) * dx + (p.y - this.p0.y) * dy) / len
    return r
  }
  closestPoints (line) {
    var intPt = this.intersection(line)
    if (intPt !== null) {
      return [intPt, intPt]
    }
    var closestPt = new Array(2).fill(null)
    var minDistance = Double.MAX_VALUE
    var dist = null
    var close00 = this.closestPoint(line.p0)
    minDistance = close00.distance(line.p0)
    closestPt[0] = close00
    closestPt[1] = line.p0
    var close01 = this.closestPoint(line.p1)
    dist = close01.distance(line.p1)
    if (dist < minDistance) {
      minDistance = dist
      closestPt[0] = close01
      closestPt[1] = line.p1
    }
    var close10 = line.closestPoint(this.p0)
    dist = close10.distance(this.p0)
    if (dist < minDistance) {
      minDistance = dist
      closestPt[0] = this.p0
      closestPt[1] = close10
    }
    var close11 = line.closestPoint(this.p1)
    dist = close11.distance(this.p1)
    if (dist < minDistance) {
      minDistance = dist
      closestPt[0] = this.p1
      closestPt[1] = close11
    }
    return closestPt
  }
  closestPoint (p) {
    var factor = this.projectionFactor(p)
    if (factor > 0 && factor < 1) {
      return this.project(p)
    }
    var dist0 = this.p0.distance(p)
    var dist1 = this.p1.distance(p)
    if (dist0 < dist1) return this.p0
    return this.p1
  }
  maxX () {
    return Math.max(this.p0.x, this.p1.x)
  }
  getLength () {
    return this.p0.distance(this.p1)
  }
  compareTo (o) {
    var other = o
    var comp0 = this.p0.compareTo(other.p0)
    if (comp0 !== 0) return comp0
    return this.p1.compareTo(other.p1)
  }
  reverse () {
    var temp = this.p0
    this.p0 = this.p1
    this.p1 = temp
  }
  equalsTopo (other) {
    return this.p0.equals(other.p0) &&
          (this.p1.equals(other.p1) || this.p0.equals(other.p1)) &&
           this.p1.equals(other.p0)
  }
  lineIntersection (line) {
    try {
      var intPt = HCoordinate.intersection(this.p0, this.p1, line.p0, line.p1)
      return intPt
    } catch (ex) {
      if (ex instanceof NotRepresentableException) {} else throw ex
    } finally {}
    return null
  }
  maxY () {
    return Math.max(this.p0.y, this.p1.y)
  }
  pointAlongOffset (segmentLengthFraction, offsetDistance) {
    var segx = this.p0.x + segmentLengthFraction * (this.p1.x - this.p0.x)
    var segy = this.p0.y + segmentLengthFraction * (this.p1.y - this.p0.y)
    var dx = this.p1.x - this.p0.x
    var dy = this.p1.y - this.p0.y
    var len = Math.sqrt(dx * dx + dy * dy)
    var ux = 0.0
    var uy = 0.0
    if (offsetDistance !== 0.0) {
      if (len <= 0.0) throw new Error('Cannot compute offset from zero-length line segment')
      ux = offsetDistance * dx / len
      uy = offsetDistance * dy / len
    }
    var offsetx = segx - uy
    var offsety = segy + ux
    var coord = new Coordinate(offsetx, offsety)
    return coord
  }
  setCoordinates () {
    if (arguments.length === 1) {
      const ls = arguments[0]
      this.setCoordinates(ls.p0, ls.p1)
    } else if (arguments.length === 2) {
      const p0 = arguments[0]
      const p1 = arguments[1]
      this.p0.x = p0.x
      this.p0.y = p0.y
      this.p1.x = p1.x
      this.p1.y = p1.y
    }
  }
  segmentFraction (inputPt) {
    var segFrac = this.projectionFactor(inputPt)
    if (segFrac < 0.0) segFrac = 0.0; else if (segFrac > 1.0 || Double.isNaN(segFrac)) segFrac = 1.0
    return segFrac
  }
  toString () {
    return 'LINESTRING( ' + this.p0.x + ' ' + this.p0.y + ', ' + this.p1.x + ' ' + this.p1.y + ')'
  }
  isHorizontal () {
    return this.p0.y === this.p1.y
  }
  distance () {
    if (arguments[0] instanceof LineSegment) {
      const ls = arguments[0]
      return CGAlgorithms.distanceLineLine(this.p0, this.p1, ls.p0, ls.p1)
    } else if (arguments[0] instanceof Coordinate) {
      const p = arguments[0]
      return CGAlgorithms.distancePointLine(p, this.p0, this.p1)
    }
  }
  pointAlong (segmentLengthFraction) {
    const coord = new Coordinate()
    coord.x = this.p0.x + segmentLengthFraction * (this.p1.x - this.p0.x)
    coord.y = this.p0.y + segmentLengthFraction * (this.p1.y - this.p0.y)
    return coord
  }
  hashCode () {
    let bits0 = Double.doubleToLongBits(this.p0.x)
    bits0 ^= Double.doubleToLongBits(this.p0.y) * 31
    let hash0 = Math.trunc(bits0) ^ Math.trunc(bits0 >> 32)
    let bits1 = Double.doubleToLongBits(this.p1.x)
    bits1 ^= Double.doubleToLongBits(this.p1.y) * 31
    let hash1 = Math.trunc(bits1) ^ Math.trunc(bits1 >> 32)
    return hash0 ^ hash1
  }
  interfaces_ () {
    return [Comparable, Serializable]
  }
  getClass () {
    return LineSegment
  }
  static midPoint (p0, p1) {
    return new Coordinate((p0.x + p1.x) / 2, (p0.y + p1.y) / 2)
  }
  static get serialVersionUID () { return 3252005833466256227 }
}
