/**
 * 基于WebGL的视频裁切处理器
 * @param {Array} configs 配置数组，每个元素为一个配置对象，包含以下属性：
 *   - type: 裁切类型，默认值为"none"
 *   - x: 裁切区域的起始横坐标百分比（左上角），范围介于0到1之间，默认值为0
 *   - y: 裁切区域的起始纵坐标百分比（左上角），范围介于0到1之间，默认值为0
 *   - w: 裁切区域的宽度百分比，范围介于0到1之间，默认值为1
 *   - h: 裁切区域的高度百分比，范围介于0到1之间，默认值为1
 *
 * 使用方式：
 * 1. 创建一个VideoProcessor实例，传入适当的配置数组。
 * 2. 调用initializeById方法，传入源canvas的ID和目标canvas ID数组。
 *    这会初始化源和目标canvas，并将源canvas内容处理后绘制到目标canvas。
 * 3. 在需要更新帧时（例如，源视频播放新的一帧时），调用drawFrame方法。
 *    这会将当前源canvas中的帧复制并按照给定配置处理，然后绘制到目标canvas。
 *
 * 示例代码：
 * ```js
 * const configs = [
 *   { type: 'none', x: 0, y: 0, w: 1, h: 1 },
 *   { type: "scaleOnly", x: 2/3, y: 0.0, w: 1/3, h: 4/9 },
 *   { type: "threePart", x: 0.0, y: 22/27, w: 1, h: 4/27 },
 * ];
 * const videoProcessor = new VideoProcessor(configs);
 * videoProcessor.initializeById("video1", ["camera1", "camera2", "camera3"]);
 *
 * // 每当源视频播放新的一帧时执行以下操作
 * videoProcessor.drawFrame();
 * ```
 */
export default class VideoProcessor {
    constructor(configs = []) {
        this.configs = configs;
        this.vertexShaderSource = this._createVertexShaderSource();
    }

    initialize(sourceCanvas, targetCanvasArray) {
        this.canvasSource = sourceCanvas;
        this.canvasTargets = targetCanvasArray;

        this.glSource = this.canvasSource.getContext("webgl");

        // 初始化帧缓冲区和纹理数组
        this.frameBuffers = [];
        this.textures = [];
        this.programs = [];

        this.canvasTargets.forEach((targetCanvas, index) => {
            targetCanvas.width = this.canvasSource.width;
            targetCanvas.height = this.canvasSource.height;

            const config = this.configs[index] || {
                type: "none",
                x: 0,
                y: 0,
                w: 1,
                h: 1,
            };
            this.fragmentShaderSources = ["" * this.configs.length];
            this.fragmentShaderSources[index] =
                this._createFragmentShaderSource(config);

            const glTarget = this.canvasTargets[index].getContext("webgl");
            // 创建并初始化帧缓冲区和纹理
            this._initWebGLCanvas(glTarget, index);
        });
    }

    initializeById(sourceCanvasId, targetCanvasIds) {
        const sourceCanvas = document.getElementById(sourceCanvasId);
        const targetCanvasArray = targetCanvasIds.map((id) =>
            document.getElementById(id)
        );
        this.initialize(sourceCanvas, targetCanvasArray);
    }

    drawFrame() {
        this.canvasTargets.forEach((_, index) => {
            const glTarget = this.canvasTargets[index].getContext("webgl");
            this._copyOnVideoFrame(glTarget, index);
        });
    }

    _initWebGLCanvas(glTarget, index) {
        console.log(`开始初始化 webgl-${index}`);

        // 创建纹理
        const texture = glTarget.createTexture();
        this.textures.push(texture);
        glTarget.bindTexture(glTarget.TEXTURE_2D, this.textures[index]);
        glTarget.texParameteri(
            glTarget.TEXTURE_2D,
            glTarget.TEXTURE_MIN_FILTER,
            glTarget.LINEAR
        );
        glTarget.texParameteri(
            glTarget.TEXTURE_2D,
            glTarget.TEXTURE_WRAP_S,
            glTarget.CLAMP_TO_EDGE
        );
        glTarget.texParameteri(
            glTarget.TEXTURE_2D,
            glTarget.TEXTURE_WRAP_T,
            glTarget.CLAMP_TO_EDGE
        );
        glTarget.texImage2D(
            glTarget.TEXTURE_2D,
            0,
            glTarget.RGBA,
            this.canvasSource.width,
            this.canvasSource.height,
            0,
            glTarget.RGBA,
            glTarget.UNSIGNED_BYTE,
            null
        );

        // 创建一个帧缓冲区
        const frameBuffer = glTarget.createFramebuffer();
        this.frameBuffers.push(frameBuffer);
        glTarget.bindFramebuffer(glTarget.FRAMEBUFFER, this.frameBuffers[index]);
        glTarget.framebufferTexture2D(
            glTarget.FRAMEBUFFER,
            glTarget.COLOR_ATTACHMENT0,
            glTarget.TEXTURE_2D,
            this.textures[index],
            0
        );
        // 检查状态
        var status = glTarget.checkFramebufferStatus(glTarget.FRAMEBUFFER);
        if (status == glTarget.FRAMEBUFFER_COMPLETE) {
            console.log("Framebuffer is complete.");
        } else {
            console.error("Framebuffer is incomplete:", status);
        }
        // 创建着色器程序
        var vertexShader = this._createShader(
            glTarget,
            glTarget.VERTEX_SHADER,
            this.vertexShaderSource
        );
        // 使用对应的片段着色器源创建并链接不同的着色器程序
        var fragmentShader = this._createShader(
            glTarget,
            glTarget.FRAGMENT_SHADER,
            this.fragmentShaderSources[index]
        );
        const program = glTarget.createProgram();
        this.programs.push(program);
        glTarget.attachShader(this.programs[index], vertexShader);
        glTarget.attachShader(this.programs[index], fragmentShader);
        glTarget.linkProgram(this.programs[index]);
        glTarget.useProgram(this.programs[index]);
        var success = glTarget.getProgramParameter(
            this.programs[index],
            glTarget.LINK_STATUS
        );
        if (success) {
            console.log("Program linked successfully.");
        } else {
            console.error(glTarget.getProgramInfoLog(this.programs[index]));
            glTarget.deleteProgram(this.programs[index]);
        }

        // 创建顶点缓冲区，并设置 WebGL 的属性与通道（Channels）
        var positionLocation = glTarget.getAttribLocation(
            this.programs[index],
            "a_position"
        );
        var texCoordLocation = glTarget.getAttribLocation(
            this.programs[index],
            "a_texCoord"
        );

        var positionBuffer = glTarget.createBuffer();
        glTarget.bindBuffer(glTarget.ARRAY_BUFFER, positionBuffer);

        // 顶点坐标信息
        var positions = [-1, -1, 1, -1, -1, 1, 1, 1];

        glTarget.bufferData(
            glTarget.ARRAY_BUFFER,
            new Float32Array(positions),
            glTarget.STATIC_DRAW
        );
        glTarget.enableVertexAttribArray(positionLocation);
        glTarget.vertexAttribPointer(
            positionLocation,
            2,
            glTarget.FLOAT,
            false,
            0,
            0
        );

        var texCoordBuffer = glTarget.createBuffer();
        glTarget.bindBuffer(glTarget.ARRAY_BUFFER, texCoordBuffer);

        // 纹理坐标信息
        var texCoords = [0, 0, 1, 0, 0, 1, 1, 1];

        glTarget.bufferData(
            glTarget.ARRAY_BUFFER,
            new Float32Array(texCoords),
            glTarget.STATIC_DRAW
        );
        glTarget.enableVertexAttribArray(texCoordLocation);
        glTarget.vertexAttribPointer(
            texCoordLocation,
            2,
            glTarget.FLOAT,
            false,
            0,
            0
        );
        console.log(`初始化 webgl-${index} 完成`);
        console.log("--------------------");
    }

    _copyOnVideoFrame(glTarget, index) {
        var errorCode = glTarget.getError();
        if (errorCode !== glTarget.NO_ERROR) {
            console.error("WebGL error:", errorCode);
        }
        // 根据索引绑定相应的帧缓冲区
        glTarget.bindFramebuffer(glTarget.FRAMEBUFFER, this.frameBuffers[index]);
        glTarget.viewport(0, 0, this.canvasSource.width, this.canvasSource.height);

        // 从源canvas复制当前帧到纹理中
        glTarget.texImage2D(
            glTarget.TEXTURE_2D,
            0,
            glTarget.RGBA,
            glTarget.RGBA,
            glTarget.UNSIGNED_BYTE,
            this.canvasSource
        );

        // 切换到目标canvas的帧缓冲区并绘制纹理
        glTarget.bindFramebuffer(glTarget.FRAMEBUFFER, null);
        glTarget.viewport(
            0,
            0,
            this.canvasTargets[index].width,
            this.canvasTargets[index].height
        );
        this._drawTextureToTargetCanvas(glTarget, index); // 此处调用内部方法
        errorCode = glTarget.getError();
        if (errorCode !== glTarget.NO_ERROR) {
            console.error("WebGL error:", errorCode);
        }
    }

    _drawTextureToTargetCanvas(glTarget, index) {
        glTarget.clearColor(0, 0, 0, 1);
        glTarget.clear(glTarget.COLOR_BUFFER_BIT);

        glTarget.activeTexture(glTarget.TEXTURE0);
        glTarget.bindTexture(glTarget.TEXTURE_2D, this.textures[index]);
        glTarget.uniform1i(
            glTarget.getUniformLocation(this.programs[index], "u_texture"),
            0
        );

        glTarget.drawArrays(glTarget.TRIANGLE_STRIP, 0, 4);
    }

    _createShader(glTarget, type, source) {
        var shader = glTarget.createShader(type);
        glTarget.shaderSource(shader, source);
        glTarget.compileShader(shader);
        var success = glTarget.getShaderParameter(shader, glTarget.COMPILE_STATUS);
        if (success) {
            return shader;
        }
        console.error(glTarget.getShaderInfoLog(shader));
        glTarget.deleteShader(shader);
    }

    _createVertexShaderSource() {
        return `
    attribute vec4 a_position;
    attribute vec2 a_texCoord;
    varying vec2 v_texCoord;
    void main() {
      gl_Position = a_position;
      v_texCoord = a_texCoord;
    }
    `;
    }

    _createFragmentShaderSource(config) {
        const { type, x, y, w, h } = config;
        const width = this._formatShaderNumber(w);
        const height = this._formatShaderNumber(h);
        const offsetX = this._formatShaderNumber(x);
        const offsetY = this._formatShaderNumber(y);
        switch (type) {
            case "scaleOnly":
                return `
        precision mediump float;
        varying vec2 v_texCoord;
        uniform sampler2D u_texture;
        void main() {
          vec2 scaledTexCoord = vec2(v_texCoord.x * ${width}, v_texCoord.y * ${height});
          scaledTexCoord.x += ${offsetX};
          scaledTexCoord.y += ${offsetY};
          gl_FragColor = texture2D(u_texture, scaledTexCoord);
        }
        `;
            case "threePart":
                return `
        precision mediump float;
        varying vec2 v_texCoord;
        uniform sampler2D u_texture;

        void main() {
          if (v_texCoord.y < (1.0 / 3.0)) {
            vec2 newTexCoord = vec2(v_texCoord.x / 3.0, v_texCoord.y * 3.0 * ${height} + ${offsetY});
            gl_FragColor = texture2D(u_texture, newTexCoord);
          } else if (v_texCoord.y < (2.0 / 3.0)) {
            vec2 newTexCoord = vec2(v_texCoord.x / 3.0 + 1.0 / 3.0, v_texCoord.y * 3.0 * ${height} + ${offsetY} - ${height});
            gl_FragColor = texture2D(u_texture, newTexCoord);
          } else {
            vec2 newTexCoord = vec2(v_texCoord.x / 3.0 + 2.0 / 3.0, v_texCoord.y * 3.0 * ${height} + ${offsetY} - 2.0 * ${height});
            gl_FragColor = texture2D(u_texture, newTexCoord);
          }
        }
        `;
            default:
                return `
        precision mediump float;
        varying vec2 v_texCoord;
        uniform sampler2D u_texture;
        void main() {
          gl_FragColor = texture2D(u_texture, v_texCoord);
        }
        `;
        }
    }

    _formatShaderNumber(number) {
        const clampedNumber = Math.max(0, Math.min(1, number));
        return clampedNumber.toFixed(6);
    }
}
