import envFragSource from "./shader-env.frag?raw";
import envCubeFragSource from "./shader-env-cube.frag?raw";
import envVertSource from "./shader-env.vert?raw";
import { loadImage } from "@/util";
import { createFPSCamera, createProgram, generateVao, useTex } from "@/util/gl";
import { mat4, glMatrix } from "gl-matrix";
import { setUniforms } from "./setUniform";

const generatePreset = (gl: WebGL2RenderingContext) => {
  const skyCubeTex = gl.createTexture();
  const skyCubeTex1 = gl.createTexture();
  const updateSky1 = (images: HTMLImageElement[]) => {
    gl.bindTexture(gl.TEXTURE_CUBE_MAP, skyCubeTex1);
    const mapper = [2, 4, 0, 5, 1, 3];
    for (let i = 0; i < 6; i++) {
      gl.texImage2D(
        gl.TEXTURE_CUBE_MAP_POSITIVE_X + i,
        0,
        gl.RGB,
        gl.RGB,
        gl.UNSIGNED_BYTE,
        images[mapper[i]]
      );
    }
    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
    gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
  };

  const updateSky = (image: HTMLImageElement) => {
    gl.bindTexture(gl.TEXTURE_2D, skyCubeTex);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.bindTexture(gl.TEXTURE_2D, null);
  };

  return {
    skyCubeTex,
    skyCubeTex1,
    // async preset(urls: string[]) {
    //   const images = await Promise.all(urls.map(loadImage));
    //   updateSky(images);
    // },
    async preset(url: string | string[]) {
      if (Array.isArray(url)) {
        const images = await Promise.all(url.map(loadImage));
        updateSky1(images);
      } else {
        const image = await loadImage(url);
        updateSky(image);
      }
    },
  };
};

const getDrawVaring = (gl: WebGL2RenderingContext) => {
  const positions = new Float32Array([
    -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, -1,
  ]);
  const vao = generateVao(gl, { positions }, [
    {
      loc: 0,
      key: "positions",
      size: 2,
      type: gl.FLOAT,
      stride: 0,
      offset: 0,
    },
  ]);
  return {
    ...generatePreset(gl),
    vao,
    numArrays: positions.length / 2,
  };
};

export const init = (canvas: HTMLCanvasElement) => {
  let activeTex: "skyCubeTex1" | "skyCubeTex" = "skyCubeTex1";
  const gl = canvas.getContext("webgl2", { preserveDrawingBuffer: true })!;
  const program = createProgram(gl, envVertSource, envFragSource);
  const program1 = createProgram(gl, envVertSource, envCubeFragSource);

  const setSize = (size: number[]) => {
    mat4.perspective(
      projectionMat,
      glMatrix.toRadian(45),
      size[0] / size[1],
      0.1,
      100
    );
    gl.viewport(0, 0, size[0], size[1]);
    updateInv();
    redraw();
  };

  const projectionMat = mat4.create();
  const invProjectionViewMat = mat4.create();
  const viewMat = mat4.create();
  const varing = getDrawVaring(gl);
  const updateInv = () => {
    mat4.multiply(invProjectionViewMat, projectionMat, viewMat);
    mat4.invert(invProjectionViewMat, invProjectionViewMat);
  };

  const redraw = () => {
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.enable(gl.DEPTH_TEST);
    gl.depthFunc(gl.LEQUAL);
    const g = activeTex === "skyCubeTex" ? program : program1;
    gl.useProgram(g);
    gl.bindVertexArray(varing.vao);
    setUniforms(gl, g, {
      invProjectionViewMat,
      envTex:
        activeTex === "skyCubeTex1"
          ? useTex(gl, varing.skyCubeTex1!, gl.TEXTURE_CUBE_MAP, 1)
          : useTex(gl, varing.skyCubeTex!, gl.TEXTURE_2D),
    });
    gl.drawArrays(gl.TRIANGLES, 0, varing.numArrays);
  };

  setSize([canvas.width, canvas.height]);
  const fps = createFPSCamera(
    canvas.parentElement!,
    (nViewMat) => {
      mat4.copy(viewMat, nViewMat);
      updateInv();
      redraw();
    },
    [0, 1, 0],
    [0, 0, 0],
    { yaw: glMatrix.toRadian(-180) },
    80
  );
  return {
    setSize,
    redraw,
    changeUrls(urls: string | string[]) {
      fps.recovery();
      return varing.preset(urls).then(() => {
        activeTex = Array.isArray(urls) ? "skyCubeTex1" : "skyCubeTex";
        redraw();
      });
    },
    destory: fps.destory,
  };
};
