import { showError } from "./debug";
import "./style.css";

import vsSource from "./shaders/basic.vert?raw";
import fsSource from "./shaders/basic.frag?raw";
import { loadBuffer, loadProgram, loadShader } from "./utils/shader";

import Vector2D from "./geometry/Vector2D";
import { createBasicVao } from "./utils/vao";
import Shape2D from "./geometry/Shape2D";

import outline from "./data/letter_outline.json";
import solid from "./data/letter_triangle.json";

import { Timeline, Track } from "./utils/sequence";

function render() {
  const canvas = document.querySelector<HTMLCanvasElement>("#canvas");
  if (!canvas) {
    throw new Error("Canvas does not exist");
  }

  const gl = canvas.getContext("webgl2");
  if (!gl) {
    throw new Error("This browser does not support WebGL 2");
  }

  const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
  const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
  const program = loadProgram(gl, vertexShader, fragmentShader);
  gl.useProgram(program);

  const vertexPositionAttribLocation = gl.getAttribLocation(program, "vertexPosition");
  if (vertexPositionAttribLocation < 0) {
    throw new Error("Failed to get attrib location for vertexPosition");
  }

  const kVerts = new Float32Array(outline.k.vertices);
  const kBuffer = loadBuffer(gl, kVerts);
  const kVao = createBasicVao(gl, kBuffer, vertexPositionAttribLocation);

  const eVerts = new Float32Array(outline.e.vertices);
  const eBuffer = loadBuffer(gl, eVerts);
  const eVao = createBasicVao(gl, eBuffer, vertexPositionAttribLocation);

  const aVerts = new Float32Array(outline.a.vertices);
  const aBuffer = loadBuffer(gl, aVerts);
  const aVao = createBasicVao(gl, aBuffer, vertexPositionAttribLocation);

  const KVerts = new Float32Array(solid.k.vertices);
  const KBuffer = loadBuffer(gl, KVerts);
  const KVao = createBasicVao(gl, KBuffer, vertexPositionAttribLocation);

  const EVerts = new Float32Array(solid.e.vertices);
  const EBuffer = loadBuffer(gl, EVerts);
  const EVao = createBasicVao(gl, EBuffer, vertexPositionAttribLocation);

  const AVerts = new Float32Array(solid.a.vertices);
  const ABuffer = loadBuffer(gl, AVerts);
  const AVao = createBasicVao(gl, ABuffer, vertexPositionAttribLocation);

  const shapes: Shape2D[] = [
    new Shape2D(new Vector2D(0, 0), 200, [0.22745, 0.35294, 0.25098], kVerts.length / 2, kVao, gl.LINES),
    new Shape2D(new Vector2D(0, 0), 200, [0.22745, 0.35294, 0.25098], eVerts.length / 2, eVao, gl.LINES),
    new Shape2D(new Vector2D(0, 0), 200, [0.22745, 0.35294, 0.25098], aVerts.length / 2, aVao, gl.LINES),
    new Shape2D(new Vector2D(0, 0), 800, [0.98823, 0.30980, 0.21960], KVerts.length / 2, KVao, gl.TRIANGLES),
    new Shape2D(new Vector2D(0, 0), 800, [0.98823, 0.30980, 0.21960], EVerts.length / 2, EVao, gl.TRIANGLES),
    new Shape2D(new Vector2D(0, 0), 800, [0.98823, 0.30980, 0.21960], AVerts.length / 2, AVao, gl.TRIANGLES),
  ]

  const pTrackK = new Track([
    { time: 0.0, value: [300, -200] },
    { time: 0.5, value: [300, 300] }, 
  ]);

  const pTrackE = new Track([
    { time: 0.5, value: [625, 900] },
    { time: 1.0, value: [625, 300] },
  ]);

  const pTrackA = new Track([
    { time: 1.0, value: [900, -200] },
    { time: 1.5, value: [900, 300] },
  ]);

  const qTrackK = new Track([
    { time: 5.0, value: [100, 610] },
    { time: 5.5, value: [100, 75] }, 
  ]);

  const qTrackE = new Track([
    { time: 5.5, value: [450, -500] },
    { time: 6.0, value: [450, 75] },
  ]);

  const qTrackA = new Track([
    { time: 6.0, value: [800, 610] },
    { time: 6.5, value: [800, 75] },
  ]);

  const bgTrack = new Track([
    { time: 0, value: [0.0, 0.0, 0.0] },
    { time: 4.5, value: [0.0, 0.0, 0.0] },
    { time: 5.0, value: [0.22745, 0.35294, 0.25098] },
  ]);

  const timeline = new Timeline({
    "position0": pTrackK,
    "position1": pTrackE,
    "position2": pTrackA,
    "position3": qTrackK,
    "position4": qTrackE,
    "position5": qTrackA,
    "backgroundColor": bgTrack
  }, 9.5, true);

  const fragmentColorUniform = gl.getUniformLocation(program, "uColor");
  const canvasSizeUniform = gl.getUniformLocation(program, "uCanvasSize");
  const shapeLocationUniform = gl.getUniformLocation(program, "uLocation");
  const shapeScaleUniform = gl.getUniformLocation(program, "uScale");
  if (!(fragmentColorUniform && canvasSizeUniform && shapeLocationUniform && shapeScaleUniform)) {
    throw new Error(`Some uniform not found:
      ${fragmentColorUniform ? "" : "uColor"};
      ${canvasSizeUniform ? "" : "uCanvasSize"};
      ${shapeLocationUniform ? "" : "uLocation"};
      ${shapeScaleUniform ? "" : "uScale"};
    `);
  }

  let startTime = performance.now();
  
  const frame = () => {
    const currentTime = (performance.now() - startTime) / 1000;
    const interpolatedValues = timeline.update(currentTime);

    const [r, g, b, a] = interpolatedValues.backgroundColor as number[];

    canvas.width = canvas.clientWidth;
    canvas.height = canvas.clientHeight;
    
    gl.clearColor(r, g, b, a);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    gl.viewport(0, 0, canvas.width, canvas.height);
    gl.uniform2f(canvasSizeUniform, canvas.width, canvas.height);

    shapes.forEach((shape, index) => {
      const positionKey = `position${index}`;
      const [x, y] = interpolatedValues[positionKey] as number[];

      gl.uniform3f(fragmentColorUniform, shape.color[0], shape.color[1], shape.color[2]);
      gl.uniform2f(shapeLocationUniform, x, y);
      gl.uniform1f(shapeScaleUniform, shape.scale);

      gl.bindVertexArray(shape.vao);
      gl.drawArrays(shape.renderType, 0, shape.verticesCount);
    });

    requestAnimationFrame(frame)
  }

  requestAnimationFrame(frame);
}


try {
  render();
} catch (err) {
  showError(err);
}