import {SketchObject} from './sketch-object'
import {Segment} from './segment'

import * as draw_utils from '../shapes/draw-utils'
import {isPointInsidePolygon, polygonOffset, ConvexHull2D} from "geom/euclidean";
import Vector from "math/vector";


export class BezierCurve extends SketchObject {

  constructor(ax, ay, cp1x, cp1y, cp2x, cp2y, bx, by, id) {
    super(id);
    const s1 = new Segment(ax, ay, cp1x, cp1y, this.id + ':1');
    const s2 = new Segment(bx, by, cp2x, cp2y, this.id + ':2');
    this.addChild(s1);
    this.addChild(s2);

    this.a = s1.a;
    this.b = s2.b;
    this.cp1 = s1.b;
    this.cp2 = s2.a;
    
    for (const c of this.children) {
      c.role = 'objectConstruction';
    }
  }

  get p0() {
    return this.a;
  }
  get p1() {
    return this.cp1;
  }
  get p2() {
    return this.cp2;
  }
  get p3() {
    return this.b;
  }

  stabilize(viewer) {
    this.children.forEach(c => c.stabilize(viewer));
  }

  visitParams(callback) {
    this.a.visitParams(callback);
    this.b.visitParams(callback);
    this.cp1.visitParams(callback);
    this.cp2.visitParams(callback);
  }

  normalDistance(aim, scale) {
    this.hull = ConvexHull2D([this.a, this.b, this.cp1, this.cp2]);
    this.hull = polygonOffset(this.hull, 1 + (0.3 / scale));
    if (isPointInsidePolygon(aim, this.hull)) {
      this.lut = LUT(this.a, this.b, this.cp1, this.cp2, scale);
      return this.closestNormalDistance(aim, this.lut)
    }
    return -1;
  }
  
  closestNormalDistance(aim, segments) {
    let hero = -1;
    for (let p = segments.length - 1, q = 0; q < segments.length; p = q ++) {
      const dist = Math.min(Segment.calcNormalDistance(aim, segments[p], segments[q]));
      if (dist !== -1) {
        hero = hero === -1 ? dist : Math.min(dist, hero);
      }
    }
    return hero;
  }
  
  drawImpl(ctx, scale, viewer) {
    ctx.beginPath();
    ctx.moveTo(this.a.x, this.a.y);
    ctx.bezierCurveTo(this.cp1.x, this.cp1.y, this.cp2.x, this.cp2.y, this.b.x, this.b.y);
    ctx.stroke();

    //debug lut and hull
    //this.drawLUTAndHull(ctx, scale);
  }

  drawLUTAndHull(ctx, scale) {
    if (this.lut) {
      for (const p of this.lut) {
        draw_utils.DrawPoint(ctx, p.x, p.y, 3, scale);
      }

      ctx.moveTo(this.hull[0].x, this.hull[0].y);
      for (const p of this.hull) {
        ctx.lineTo(p.x, p.y);
      }
      ctx.stroke();
    }
  }

  get labelCenter() {
    return compute(0.5, this.a, this.b, this.cp1, this.cp2);
  }

  write() {
    return {
      cp1: this.a.write(),
      cp2: this.cp1.write(),
      cp3: this.cp2.write(),
      cp4: this.b.write()
    };
  }

  static read(id, data) {
    return new BezierCurve(
      data.cp1.x,
      data.cp1.y,
      data.cp2.x,
      data.cp2.y,
      data.cp3.x,
      data.cp3.y,
      data.cp4.x,
      data.cp4.y,
      id
    )
  }
}

BezierCurve.prototype.TYPE = 'BezierCurve';

BezierCurve.prototype._class = 'TCAD.TWO.BezierCurve';

const RECOVER_LENGTH = 100;

export function LUT(a, b, cp1, cp2, scale) {
  scale = 1 / scale;
  const lut = [];
  for (let t = 0; t < 1; t += 0.1 * scale) {
    const p = compute(t, a, b, cp1, cp2);
    lut.push(p);
  }
  lut[0] = a;
  lut[lut.length - 1] = b;
  return lut;
}

export function compute(t, from, to, controlPoint1, controlPoint2) {
  const mt = 1 - t;
  const mt2 = mt * mt;
  const t2 = t * t;

  const a = mt2 * mt;
  const b = mt2 * t * 3;
  const c = mt * t2 * 3;
  const d = t * t2;
  const p0 = from;
  const p3 = to;
  const p1 = controlPoint1;
  const p2 = controlPoint2;
  return new Vector(
    a * p0.x + b * p1.x + c * p2.x + d * p3.x,
    a * p0.y + b * p1.y + c * p2.y + d * p3.y,
    a * p0.z + b * p1.z + c * p2.z + d * p3.z
  );
}
