import { SVG } from 'https://cdn.skypack.dev/@svgdotjs/svg.js'
import { Vec2 } from 'https://cdn.skypack.dev/wtc-math';
console.clear();

const config = {
  drawingType: 1,
  dimensions: (new Vec2(window.innerWidth, window.innerHeight)).scale(2),
  breakRun: 2000,
  maxDepth: 12,
  maxPerRun: 100,
  insertType: 1,
  randomType: 0
};
const vars = {
  drawing: null,
  i:0,
  running: true,
  triangles: []
}
const setup = () => {
  vars.running = false;
  setTimeout(() => {
    vars.running = true;
    vars.triangles = [];
    config.insertType = Math.floor(Math.random() * 3);
    config.randomType = Math.floor(Math.random() * 3);
    vars.i=0;
    
    document.querySelector('#container').innerHTML = '';
    vars.drawing = new Drawing(config.drawingType).addTo('#container').size(config.dimensions);
    document.body.querySelector('#container>:first-child').addEventListener('click', () => {
      setup();
    });
    
    draw();
  }, 100);
};

window.addEventListener('resize', () => {
  config.dimensions = ( new Vec2(window.innerWidth, window.innerHeight)).scale(2)
  setup();
});

let depth = 0;
const ts = [
  ['a', 'b'],
  ['b', 'c'],
  ['c', 'a']
];
const drawStep = () => {
  if(!vars.running) return;
  if(vars.i > 2000) return;
  let newTriangles = [];
  vars.triangles.forEach((triangle,i) => {
    if(i>config.breakRun) {
      // newTriangles.splice(0,0,triangle);
      return;
    }
    vars.drawing.polygon(triangle.points);
    let c = triangle.randomCentroid;
    if(config.randomType == 0) {
      c = triangle.centroid;
    } 
    
    // vars.drawing.circle(c, 5);
    if(triangle.depth < config.maxDepth) {
      ts.forEach(t => {
        const tr = new Triangle(triangle[t[0]], triangle[t[1]], c);
        tr.depth=triangle.depth+1;
        newTriangles.push(tr); 
      });
    }
  });
  if(config.insertType === 0) {
    if(vars.triangles.length>config.breakRun) newTriangles = vars.triangles.splice(config.breakRun).concat(newTriangles);
  } else if(config.insertType === 1) {
    if(vars.triangles.length>config.breakRun) newTriangles = vars.triangles.splice(config.breakRun).reverse().concat(newTriangles);
  } else {
    newTriangles = newTriangles.concat(vars.triangles.splice(config.breakRun));
  }
  
  vars.triangles = newTriangles;
  vars.i++;
  requestAnimationFrame(drawStep);
}
let interval;
const draw = () => {
  vars.drawing.linewidth = 1;
  vars.drawing.fill = "#333";
  vars.drawing.rect(new Vec2(0,0), config.dimensions);
  vars.drawing.linewidth = 1;
  vars.drawing.fill = null;
  vars.drawing.stroke = 'rgba(255,255,255,.02)'
  
  const r = Math.min(config.dimensions.x, config.dimensions.y) * .6;
  const offset = r * 0.2333;
  const points = [];
  for(let i = 0.; i < Math.PI * 2; i += Math.PI * 2. / 3.) {
    points.push( new Vec2(Math.cos(i+Math.PI*.5) * r + config.dimensions.x / 2, Math.sin(i+Math.PI*.5) * r + config.dimensions.y / 2 - offset) );
  }
  
  const t = new Triangle(...points);
 t.depth=0;
  vars.triangles.push(t);
  
  drawStep();
}

class Triangle {
  
  #points
  
  constructor(a, b, c) {
    this.points = [a, b, c];
  }
  
  set points(p) {
    if(p.reduce((a, c) => c instanceof Vec2 ? a+1 : 0, 0) === 3) this.#points = p;
  }
  get points() {
    return this.#points || [];
  }
  
  get a() {
    return this.#points[0];
  }
  get b() {
    return this.#points[1];
  }
  get c() {
    return this.#points[2];
  }
  
  get circumcenter() {
    const d = this.b.subtractNew(this.a);
    const e = this.c.subtractNew(this.a);

    const bl = d.x * d.x + d.y * d.y;
    const cl = e.x * e.x + e.y * e.y;
    const ds = 0.5 / (d.x * e.y - d.y * e.x);
    
    return new Vec2(
      this.a.x + (e.y * bl - d.y * cl) * ds,
      this.a.y + (d.x * cl - e.x * bl) * ds
    );
  }
  get randomCentroid() {
    let a = floatRandomBetween(.2,1.8);
    let b = floatRandomBetween(.2,1.8);
    let c = floatRandomBetween(.2,1.8);
    let abc = a+b+c;
    const f = (3-abc)/3;
    a += f;
    b += f;
    c += f;
    return this.a.scaleNew(a).add(this.b.scaleNew(b)).add(this.c.scaleNew(c)).divideScalar(3);
  }
  get centroid() {
    const a = floatRandomBetween(.5,1.5);
    const b = floatRandomBetween(.5,1.5);
    const c = floatRandomBetween(.5,1.5);
    const abc = a+b+c;
    return this.a.addNew(this.b).add(this.c).divideScalar(3);
  }
  
  get isComplete() {
    return this.points.reduce((a, c) => c instanceof Vec2 ? a+1 : 0, 0) === 3;
  }
  
  get segments() {
    return [
      {
        a: this.a,
        b: this.b,
        c: this.c,
        segment: this.a.subtractNew(this.b)
      },
      {
        a: this.b,
        b: this.c,
        c: this.a,
        segment: this.b.subtractNew(this.c)
      },
      {
        a: this.c,
        b: this.a,
        c: this.b,
        segment: this.c.subtractNew(this.a)
      }
    ];
  }
  
  get angles() {
    let ab = this.b.subtractNew(this.a).normalise();
    let ac = this.c.subtractNew(this.a).normalise();
    
    let ba = this.a.subtractNew(this.b).normalise();
    let bc = this.c.subtractNew(this.b).normalise();
    
    const ta = (Math.acos(ab.dot(ac)));
    const tb = (Math.acos(ba.dot(bc)));
    const tc = (Math.acos(ac.dot(bc)));
    
    return [ta,tb,tc];
  }
  
  get isEqualateral() {
    return this.numEqualSides === 3;
  }
  get isIsosceles() {
    return this.numEqualSides === 2;
  }
  get isScalene() {
    return this.numEqualSides === 1;
  }
  get isRightAngle() {
    return this.angles.reduce((n, s) => n || precisionRound(Math.abs(s * 180 / Math.PI), 3) === 90, false);
  }
  get isObtuse() {
    return this.angles.reduce((n, s) => n || precisionRound(Math.abs(s * 180 / Math.PI), 3) > 90, false);
  }
  get isAcute() {
    return !this.isObtuse;
  }
  
  get numEqualSides() {
    const ls = this.segments.map(seg => precisionRound(seg.segment.length, 3));
    const n = ls.map(l => ls.reduce((n, l1) => n + (l1 === l), 0));
    return n.reduce((a, b) => b > a ? b : a, 0);
  }
  
  get hypot() {
    const segs = this.segments;
    let longest = 0;
    let hypotenuse = {};
    segs.forEach(seg => {
      if(seg.segment.length > longest) {
        longest = seg.segment.length;
        hypotenuse = seg;
      }
    });
    return hypotenuse;
  }
}

setTimeout(() => {
  setup();
}, 500);

class Drawing {
  static #defaults = {
    stroke: '#333',
    pxratio: 1
  }
  static DT_CANVAS = 1;
  static DT_SVG = 2;
  #drawing;
  #ctx;
  #mode;
  #instructions = [];
  
  constructor(mode = Drawing.DT_CANVAS, settings) {
    settings = Object.assign({}, Drawing.#defaults, settings);
    
    this.mode = mode;
    if(this.mode & Drawing.DT_CANVAS) {
      this.#drawing = document.createElement('canvas');
    } else if(this.mode & Drawing.DT_SVG) {
      this.#drawing = SVG();
    }
    
    this.stroke = settings.stroke;
    this.pxratio = settings.pxratio;
  }
  clear() {
    if(this.mode & Drawing.DT_CANVAS) {
      this.c.clearRect(0,0,...this.dimensions.array);
    } else if(this.mode & Drawing.DT_SVG) {
      this.drawing.clear();
    }
  }
  rect(position, dimensions) {
    if(this.saving) {
      this.#instructions.push({
        f: 'rect',
        args: [position, dimensions]
      });
    }
    position = position.scaleNew(this.pxratio);
    dimensions = dimensions.scaleNew(this.pxratio);
    if(this.mode & Drawing.DT_CANVAS) {
      this.c.beginPath();
      this.c.rect(...position.array, ...dimensions.array);
      if(this.stroke) this.c.stroke();
      if(this.fill) this.c.fill();
    } else if(this.mode & Drawing.DT_SVG) {
      return this.drawing.rect(dimensions.width, dimensions.height).move(...position.array).fill("none").stroke(this.strokeParams);
    }
  }
  circle(position, radius) {
    if(this.saving) {
      this.#instructions.push({
        f: 'circle',
        args: [position, radius]
      });
    }
    position = position.scaleNew(this.pxratio);
    radius *= this.pxratio;
    if(this.mode & Drawing.DT_CANVAS) {
      if(!window.trace) {
        console.log(this.fill)
        window.trace = true;
      }
      if(this.fill) this.c.fillStyle = this.fill;
      this.c.beginPath();
      this.c.arc(position.x, position.y, radius, 0, 2 * Math.PI);
      if(this.stroke) this.c.stroke();
      if(this.fill) this.c.fill();
    } else if(this.mode & Drawing.DT_SVG) {
      return this.drawing.circle(radius*2).fill("none").stroke(this.strokeParams).move(...position.subtractScalarNew(radius).array);
    }
  }
  line(a, b) {
    if(this.saving) {
      this.#instructions.push({
        f: 'line',
        args: [a, b]
      });
    }
    a = a.scaleNew(this.pxratio);
    b = b.scaleNew(this.pxratio);
    if(this.mode & Drawing.DT_CANVAS) {
      this.c.beginPath();
      this.c.moveTo(a.x, a.y);
      this.c.lineTo(b.x, b.y);
      if(this.stroke) this.c.stroke();
    } else if(this.mode & Drawing.DT_SVG) {
      return this.drawing.line(...a.array, ...b.array).stroke(this.strokeParams);
    }
  }
  polyline(points) {
    if(this.saving) {
      this.#instructions.push({
        f: 'polyline',
        args: points
      });
    }
    if(this.mode & Drawing.DT_CANVAS) {
      this.c.beginPath();
      points.forEach((p, i) => {
        p = p.scaleNew(this.pxratio);
        if(i === 0) this.c.moveTo(p.x, p.y);
        else this.c.lineTo(p.x, p.y);
      })
      if(this.stroke) this.c.stroke();
    } else if(this.mode & Drawing.DT_SVG) {
      return this.drawing.polyline(points.map(p => p.array)).fill('none').stroke(this.strokeParams);
    }
  }
  path(path) {
    if(this.mode & Drawing.DT_CANVAS) {
      this.c.beginPath();
      const p1 = new Path2D(path);
      const m = document.createElementNS("http://www.w3.org/2000/svg", "svg").createSVGMatrix()
      const p = new Path2D();
      const t = m.scale(this.pxratio);
      p.addPath(p1, t);
      if(this.stroke) this.c.stroke(p);
    } else if(this.mode & Drawing.DT_SVG) {
      const _path = this.drawing.path().fill('none').stroke(this.strokeParams);
      _path.plot(path);
      return _path;
    }
  }
  polygon(points) {
    if(this.saving) {
      this.#instructions.push({
        f: 'polygon',
        args: [points]
      });
    }
    if(this.mode & Drawing.DT_CANVAS) {
      this.c.beginPath();
      points.forEach((p, i) => {
        p = p.scaleNew(this.pxratio);
        if(i === 0) this.c.moveTo(p.x, p.y);
        else this.c.lineTo(p.x, p.y);
      })
      if(this.stroke) this.c.stroke();
      if(this.fill) this.c.fill();
    } else if(this.mode & Drawing.DT_SVG) {
      return this.drawing.polygon(points.map(p => p.array)).fill('none').stroke(this.strokeParams);
    }
  }
  
  download() {
    let d;
    if(this.mode & Drawing.DT_CANVAS) {
      d = new Drawing(Drawing.DT_SVG).size(this.dimensions);
      this.#instructions.forEach((i) => {
        d[i.f](...i.args);
      });
    } else if(this.mode & Drawing.DT_SVG) {
      d = this;
    }
    
    const fileName = "untitled.svg"
    const url = "data:image/svg+xml;utf8," + encodeURIComponent(d.drawing.svg());
    const link = document.createElement("a");
    link.download = fileName;
    link.href = url;
    link.click();
  }
  
  addTo(element) {
    if(typeof(element) === 'string') {
      if(this.mode & Drawing.DT_CANVAS) {
        document.body.querySelector(element).appendChild(this.drawing);
      } else if(this.mode & Drawing.DT_SVG) {
        this.drawing.addTo('#container');
      }
    }
    return this;
  }
  
  size(d) {
    if(this.mode & Drawing.DT_CANVAS) {
      this.drawing.width = d.width * this.#pxratio;
      this.drawing.height = d.height * this.#pxratio;
    } else if(this.mode & Drawing.DT_SVG) {
      this.drawing.viewbox(0, 0, d.x, d.y)
      this.drawing.size(...d.scaleNew(this.#pxratio).array);
    }
    this.#dimensions = d;
    return this;
  }
  
  #dimensions
  set dimensions(v) {
    if(v instanceof Vec2) {
      this.#dimensions = v;
      this.size(v);
    }
  }
  get dimensions() {
    return this.#dimensions;
  }
  
  #pxratio = 1;
  set pxratio(v) {
    if(v > 0) {
      this.#pxratio = v;
    }
  }
  get pxratio() {
    return this.#pxratio || 1;
  }
  
  get drawing() {
    return this.#drawing;
  }
  get c() {
    if(this.mode & Drawing.DT_CANVAS) {
      if(this.#ctx) return this.#ctx;
      this.#ctx = this.drawing.getContext('2d');
      return this.#ctx;
    }
  }
  #stroke;
  set stroke(v) {
    this.#stroke = v;
    if(this.mode & Drawing.DT_CANVAS) {
      this.c.strokeStyle = v;
    }
  }
  get stroke() {
    return this.#stroke;
  }
  #fill = null;
  set fill(v) {
    this.#fill = v;
    if(this.mode & Drawing.DT_CANVAS) {
      this.c.fillStyle = v;
    }
  }
  get fill() {
    return this.#fill;
  }
  #linecap = null;
  set linecap(v) {
    if(['square', 'butt', 'round'].indexOf(v) > -1) {
      this.#linecap = v;
      if(this.mode & Drawing.DT_CANVAS) {
        this.c.lineCap = v;
      }
    }
  }
  get linecap() {
    return this.#linecap;
  }
  #lineWidth
  set linewidth(v) {
    if(v > 0) {
      this.#lineWidth = v;
      if(this.mode & Drawing.DT_CANVAS) {
        this.c.lineWidth = v*this.pxratio;
      }
    }
  }
  get linewidth() {
    return this.#lineWidth;
  }
  get strokeParams() {
    const params = {};
    if(this.#lineWidth) params.width = this.#lineWidth;
    if(this.#stroke) params.color = this.#stroke;
    if(this.#linecap) params.linecap = this.#linecap;
    return params;
  }
  set mode(v) {
    if(v & (Drawing.DT_CANVAS | Drawing.DT_SVG)) {
      this.#mode = v;
    }
  }
  get mode() {
    return this.#mode || Drawing.DT_CANVAS;
  }
  #saving = false
  set saving(v) {
    this.#saving = v === true;
  }
  get saving() {
    return this.#saving;
  }
}

 /// Create the download button/
/*
const dl = document.createElement('button');
dl.innerText = 'download';
dl.addEventListener('click', () => { 
  vars.drawing.download();
});
document.body.querySelector('#container').appendChild(dl);
*/



const floatRandomBetween = (min, max) => {
  return Math.random() * (max - min) + min;
};
const clamp = function(a, b, v) {
  return Math.min(b, Math.max(a, v));
}
const lerp = function(a, b, progress) {
  return a + progress * (b - a);
}
const hash21 = (p) => {
  const o = p.dot(new Vec2(27.609, 57.583));
  return fract(Math.sin(o)*config.seed);
}
const precisionRound = (n, p) => {
  const ip = Math.pow(10, p);
  return Math.round(n*ip)/ip;
}
const fract = (n, p = 6) => {
  const ip = Math.pow(10, p);
  const _n = Math.abs(Math.floor(n*ip)/ip);
  if(_n < 1) return _n;
  return Math.floor(_n % Math.floor(_n)*ip)/ip;
}
const pal = ( t, a, b, c, d ) => {
  const mp = c.scaleNew(t).add(d).scale(6.28318);
  mp.x = Math.cos(mp.x);
  mp.y = Math.cos(mp.y);
  mp.z = Math.cos(mp.z);
  return a.addNew(b.multiplyNew(mp));
}
const getColour = (d) => {
  const col = pal( 
    d/70+.65, 
    new Vec3(0.5,0.5,0.5),
    new Vec3(0.5,0.5,0.5),
    new Vec3(1.0,1.0,1.0),
    new Vec3(0.0,0.1,0.2) );

  const colour = Math.floor(col.x * 255).toString(16) + Math.floor(col.y * 255).toString(16) + Math.floor(col.z * 255).toString(16);
  const a = Math.floor((1.-d/30)*255).toString(16);
  return colour+a;
}