(function () {
  function fillCanvas (element, draw) {
    var canvas = document.createElement("canvas");
    var ctx = canvas.getContext("2d");

    var windowToCanvas;
    function resize () {
      var vw = element.clientWidth;
      var vh = element.clientHeight;
      canvas.width = vw;
      canvas.height = vh;

      var vpMin = Math.min(vw, vh);

      // cartesion coordinates [0, 100] on both x and y axes
      ctx.setTransform(
        vpMin / 100, 0,
        0, -vpMin / 100,
        (vw - vpMin) / 2, (vh - vpMin) / 2 + vh
      );
      windowToCanvas = ctx.getTransform().inverse();
    }
    new ResizeObserver(resize).observe(element);
    resize();

    function render () {
      draw(ctx, canvas.width, canvas.height, windowToCanvas);
      requestAnimationFrame(render);
    }
    render();

    element.appendChild(canvas);
  }

  const Vec2 = {
    add: (v1, v2) => [v1[0] + v2[0], v1[1] + v2[1]],
    sub: (v1, v2) => [v1[0] - v2[0], v1[1] - v2[1]],
    scale: (s, v) => [s * v[0], s * v[1]],
    magnitude: (v) => Math.sqrt(v[0] * v[0] + v[1] * v[1]),
    distance: (v1, v2) => Vec2.magnitude(Vec2.sub(v1, v2)),
    normalize: (v) => Vec2.scale(1 / Vec2.magnitude(v), v),
    transform: (m, v) => {
      return [
        m.a * v[0] + m.c * v[1] + m.e,
        m.b * v[1] + m.d * v[1] + m.f
      ];
    },
    dot: (v1, v2) => [v1[0] * v2[0] + v1[1] * v2[1]],
    triangleArea: (p1, p2, p3) => {
      // Cramers rule for signed area (necessary to get negative lambdas)
      var [x1, y1] = p1;
      var [x2, y2] = p2;
      var [x3, y3] = p3;

      return (x1 * y2 - x1 * y3 - x2 * y1 + x3 * y1 + x2 * y3 - x3 * y2) / 2;
    }
  };

  var ptr = null;
  window.addEventListener("mousemove", function (e) {
    ptr = [e.clientX, e.clientY];
  });

  function drawPath (ctx, path) {
    for (var i = 1; i < path.length; i++) {
      ctx.beginPath();
      ctx.moveTo(path[i - 1][0], path[i - 1][1]);
      ctx.lineTo(path[i][0], path[i][1]);
      ctx.stroke();
    }

    for (var i = 0; i < path.length; i++) {
      ctx.beginPath();
      ctx.arc(path[i][0], path[i][1], 1, 0, Math.PI * 2);
      ctx.fill();
    }
  }

  function drawCurve (ctx, curve, u, depth) {
    var pointsOnCurve = drawPolygons(curve, u, depth);
    pointsOnCurve.splice(0, 0, curve[0]);
    pointsOnCurve.push(curve[curve.length - 1]);

    ctx.fillStyle = "blue";
    ctx.strokeStyle = "blue";
    ctx.lineWidth = 0.5;
    drawPath(ctx, pointsOnCurve, true);

    function drawPolygons (curve, u, depth) {
      if (depth <= 0) {
        return [];
      }

      ctx.fillStyle = "#555";
      ctx.strokeStyle = "#555";
      ctx.lineWidth = 0.25;
      drawPath(ctx, curve, true);

      var b = [curve];
      for (var i = 1; i < curve.length; i++) {
        var midpoints = [];
        for (var k = 0; k < curve.length - i; k++) {
          midpoints.push(Vec2.add(
            Vec2.scale(u, b[i - 1][k]),
            Vec2.scale(1 - u, b[i - 1][k + 1])
          ));
        }
        drawPath(ctx, midpoints);
        b.push(midpoints);
      }

      var pointsOnCurve = [];
      var c0 = b.map(function (bi) {
        return bi[0];
      });
      pointsOnCurve = pointsOnCurve.concat(drawPolygons(c0, u, depth - 1));

      pointsOnCurve.push(b[b.length - 1][0]);

      var c1 = b.map(function (bi) {
        return bi[bi.length - 1];
      });
      pointsOnCurve = pointsOnCurve.concat(drawPolygons(c1, 1 -  u, depth - 1).toReversed());

      return pointsOnCurve;
    }
  }

  var selected = 0;
  var curve = [
    [10, 10],
    [10, 90],
    [90, 10],
    [90, 90]
  ];
  var depth = 5;

  window.addEventListener("keydown", function (e) {
    switch (e.key) {
      case "j":
        selected = (selected + 1) % curve.length;
        break;
      case "k":
        selected = (curve.length + selected - 1) % curve.length;
        break;
      case "ArrowLeft":
        curve[selected][0]--;
        break;
      case "ArrowRight":
        curve[selected][0]++;
        break;
      case "ArrowUp":
        curve[selected][1]++;
        break;
      case "ArrowDown":
        curve[selected][1]--;
        break;
      case "h":
        depth = Math.max(0, depth - 1);
        break;
      case "l":
        depth++;
        break;
      case "?":
        document.querySelector(".legend").classList.toggle("--hidden");
        break;
    }
  });

  fillCanvas(document.querySelector(".canvas"), function (ctx, vw, vh, w2c) {
    var origin = Vec2.transform(w2c, [0, 0]);
    var omega = Vec2.transform(w2c, [vw, vh]);
    ctx.fillStyle = "black";
    ctx.fillRect(origin[0], origin[1], omega[0] - origin[0], omega[1] - origin[1])

    drawCurve(ctx, curve, 0.5 + 0.45 * Math.cos(Math.PI * 2 * performance.now() / 5000), depth);

    /*
    ctx.beginPath();
    ctx.moveTo(curve[0][0], curve[0][1]);
    ctx.bezierCurveTo(
      curve[1][0], curve[1][1],
      curve[2][0], curve[2][1],
      curve[3][0], curve[3][1]
    );
    ctx.strokeStyle = "red";
    ctx.lineWidth = 0.25;
    ctx.stroke();
    */

    ctx.beginPath();
    ctx.arc(curve[selected][0], curve[selected][1], 2, 0, Math.PI * 2);
    ctx.strokeStyle = "green";
    ctx.lineWidth = 0.5;
    ctx.stroke();
  });
})();
