import {ICurve} from './icurve'
import {Point} from './point'
import {LineSegment} from './lineSegment'
import {Parallelogram} from './parallelogram'
import {GeomConstants} from './geomConstants'
// import {Assert} from './../../utils/assert'
// import {LinearSystem2} from './linearSystem'
// import {allVerticesOfParall} from './parallelogram'
// import {Polyline} from './polyline'
// import {DebugCurve} from './debugCurve'
// import {CurveFactory} from './curveFactory'
// Serves to hold a Parallelogram and a ICurve,
// and is used in curve intersections routines.
// The node can be a top of the hierarchy if its sons are non-nulls.
// The sons are either both nulls or both non-nulls

export type PN = {
  parallelogram: Parallelogram
  seg: ICurve
  leafBoxesOffset: number
  node: PNInternal | PNLeaf
}

export function createPNLeaf(start: number, end: number, box: Parallelogram, seg: ICurve, eps: number): PN {
  return {
    parallelogram: box,
    seg: seg,
    leafBoxesOffset: eps,
    node: {
      low: start,
      high: end,
      chord: null, // create a cord only the segment and the chord are within intersectionEpsilon
    },
  }
}

export type PNLeaf = {
  low: number
  high: number
  chord: LineSegment | null
}

export type PNInternal = {
  children: PN[]
}

export class ParallelogramNode {
  static distToSegm(p: Point, s: Point, e: Point): number {
    const l = e.sub(s)
    if (l.length < GeomConstants.intersectionEpsilon) return p.sub(s.add(e).div(2)).length
    let perp = new Point(-l.y, l.x)
    perp = perp.mul(1 / perp.length)
    return Math.abs(p.sub(s).dot(perp))
  }

  static createParallelogramOnSubSeg(start: number, end: number, seg: ICurve): Parallelogram | undefined {
    let tan1 = seg.derivative(start)
    const tan2 = seg.derivative(end)
    const tan2Perp = new Point(-tan2.y, tan2.x)
    const corner = seg.value(start)
    const e = seg.value(end)
    const p = e.sub(corner)

    const numerator = p.dot(tan2Perp)
    const denumerator = tan1.dot(tan2Perp)
    //x  = (p * tan2Perp) / (tan1 * tan2Perp);
    // x*tan1 will be a side of the parallelogram

    const numeratorTiny = Math.abs(numerator) < GeomConstants.distanceEpsilon
    if (!numeratorTiny && Math.abs(denumerator) < GeomConstants.distanceEpsilon) {
      //it is degenerated; the adjacent sides would parallel, but
      //since p * tan2Perp is big the parallelogram would not contain e
      return
    }

    const x = numeratorTiny ? 0 : numerator / denumerator

    tan1 = tan1.mul(x)

    return Parallelogram.parallelogramByCornerSideSide(corner, tan1, e.sub(corner).sub(tan1))
  }

  static createParallelogramNodeForCurveSeg(start: number, end: number, seg: ICurve, eps: number): PN {
    const closedSeg = start == seg.parStart && end == seg.parEnd && Point.close(seg.start, seg.end, GeomConstants.distanceEpsilon)
    if (closedSeg) return ParallelogramNode.createNodeWithSegmentSplit(start, end, seg, eps)

    const s = seg.value(start)
    const e = seg.value(end)
    const w = e.sub(s)
    const middle = seg.value((start + end) / 2)

    if (
      ParallelogramNode.distToSegm(middle, s, e) <= GeomConstants.intersectionEpsilon &&
      w.dot(w) < GeomConstants.lineSegmentThreshold * GeomConstants.lineSegmentThreshold &&
      end - start < GeomConstants.lineSegmentThreshold
    ) {
      const ls = LineSegment.mkPP(s, e)
      const pn: PN = ls.pNodeOverICurve()
      pn.seg = seg
      const leaf = pn.node as PNLeaf
      leaf.low = start
      leaf.high = end
      leaf.chord = ls
      return pn
    }

    if (ParallelogramNode.WithinEpsilon(seg, start, end, eps)) {
      const box = ParallelogramNode.createParallelogramOnSubSeg(start, end, seg)
      if (box != undefined) return createPNLeaf(start, end, box, seg, eps)
    }
    return ParallelogramNode.createNodeWithSegmentSplit(start, end, seg, eps)
  }

  static WithinEpsilon(seg: ICurve, start: number, end: number, eps: number) {
    const n = 3 //hack !!!! but maybe can be proven for Bezier curves and other regular curves
    const d = (end - start) / n
    const s = seg.value(start)
    const e = seg.value(end)

    const d0 = ParallelogramNode.distToSegm(seg.value(start + d), s, e)
    if (d0 > eps) return false

    const d1 = ParallelogramNode.distToSegm(seg.value(start + d * (n - 1)), s, e)

    return d1 <= eps
  }

  static createParallelogramNodeForCurveSegDefaultOffset(seg: ICurve) {
    return ParallelogramNode.createParallelogramNodeForCurveSeg(seg.parStart, seg.parEnd, seg, GeomConstants.defaultLeafBoxesOffset)
  }

  static createNodeWithSegmentSplit(start: number, end: number, ell: ICurve, eps: number) {
    const pBNode: PN = {
      parallelogram: null,
      seg: ell,
      leafBoxesOffset: 1,
      node: {children: []},
    }

    const intNode: PNInternal = pBNode.node as PNInternal

    intNode.children.push(ParallelogramNode.createParallelogramNodeForCurveSeg(start, 0.5 * (start + end), ell, eps))
    intNode.children.push(ParallelogramNode.createParallelogramNodeForCurveSeg(0.5 * (start + end), end, ell, eps))

    pBNode.parallelogram = Parallelogram.parallelogramOfTwo(intNode.children[0].parallelogram, intNode.children[1].parallelogram)
    return pBNode
  }
}
