"use strict";

class ShaderManager {
    _shaders = {};
    _gl;

    constructor (webGLRenderingContext) {
        this._gl = webGLRenderingContext;
    }

    load(name, vertexFilename, fragmentFilename) {
        var me = this;
        return new Promise((resolve) => {
            this._loadShader(vertexFilename, fragmentFilename).then((shaderProgram) => {
                this._shaders[name] = shaderProgram;
                resolve();
            });
        });
    }

    get(name) {
        return this._shaders[name];
    }

    _loadShader(vertexFilename, fragmentFilename) {
        return new Promise((resolve) => {
            const gl = this._gl;
            Promise.all([
                this._loadShaderFile(vertexFilename, gl.VERTEX_SHADER),
                this._loadShaderFile(fragmentFilename, gl.FRAGMENT_SHADER)
            ]).then(([vertexShader, fragmentShader]) => {
                const shaderProgram = gl.createProgram();
                gl.attachShader(shaderProgram, vertexShader);
                gl.attachShader(shaderProgram, fragmentShader);
                gl.linkProgram(shaderProgram);
                resolve(shaderProgram);
            });
        });
    }

    _loadShaderFile(filename, type) {
        return new Promise((resolve) => {
            window.fetch(filename, { cache: "no-store" }).then((response) => {
                response.text().then((shaderCode) => {
                    const gl = this._gl;
                    // TODO, PJ: We should probably only call this for shadertoy-style shaders :D
                    if (type === gl.FRAGMENT_SHADER)
                        shaderCode = this._convertShaderToyShader(shaderCode);
                    const shader = gl.createShader(type);
                    gl.shaderSource(shader, shaderCode);
                    gl.compileShader(shader);
                    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
                        console.error(`Error in shader ${filename}: ${gl.getShaderInfoLog(shader)}\n${shaderCode}`);
                    }
                    resolve(shader);
                });
            });
        });
    }

    _convertShaderToyShader(shaderCode) {

        shaderCode = shaderCode.replace(/mainImage\(([^)]+)\)/g, "main()");
        shaderCode = shaderCode.replace(/fragCoord/g, "gl_FragCoord");
        shaderCode = shaderCode.replace(/fragColor/g, "gl_FragColor");
        shaderCode = shaderCode.replace(/texture\(/g, "texture2D(");

        const prefix =
            "precision highp float;" +
            "uniform float iTime;" +
            "uniform vec2 iResolution;" + 
            "uniform vec2 iMouse;" +
            "uniform sampler2D iChannel0;";

        return prefix + shaderCode;
    }

}