import { GLSLCodeGenerator } from "../codegen/glsl/GLSLCodeGenerator.js";
import { ParticleAttributeSpecification } from "../particle/ParticleAttributeSpecification.js";
import { TransformFeedback } from "./TransformFeedback.js";

/**
 * Fragment shader is there to satisfy requirement of 2 shaders to compile shader program in WebGL, in actuality it is unused
 * @type {string}
 */
export const WRITE_WHITE_FRAGMENT_SHADER = [
    '#version 300 es',
    'precision lowp float;',

    'out vec4 fragColor;',

    'void main() {',
    '   fragColor = vec4(1.0, 1.0, 1.0, 1.0);',
    '}'
].join('\n');

/**
 *
 * @param {string} string
 * @return {string}
 */
function addLineNumbers(string) {

    const lines = string.split('\n');

    for (let i = 0; i < lines.length; i++) {

        lines[i] = (i + 1) + ': ' + lines[i];

    }

    return lines.join('\n');

}

/**
 *
 * @param {WebGLRenderingContext|WebGL2RenderingContext} gl
 * @param shader
 * @param type
 * @return {string}
 */
export function getShaderErrors(gl, shader, type) {

    const status = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
    const log = gl.getShaderInfoLog(shader).trim();

    if (status && log === '') return '';

    // --enable-privileged-webgl-extension
    // console.log( '**' + type + '**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) );

    const source = gl.getShaderSource(shader);

    return 'THREE.WebGLShader: gl.getShaderInfoLog() ' + type + '\n' + log + addLineNumbers(source);

}

export class GLSLSimulationShader {

    constructor() {

        /**
         *
         * @type {NodeGraph}
         */
        this.graph = null;

        /**
         *
         * @type {FunctionModuleRegistry}
         * @private
         */
        this.__function_registry = null;


        this.__tf = new TransformFeedback();
    }

    /**
     *
     * @param {FunctionModuleRegistry} function_registry
     */
    setFunctionRegistry(function_registry) {
        this.__function_registry = function_registry;
    }

    /**
     *
     * @param {NodeGraph} graph
     */
    setGraph(graph) {
        this.graph = graph;
    }

    /**
     *
     * @param {ParticleAttributeSpecification[]} attributes
     */
    setAttributes(attributes) {
        this.__tf.setAttributes(attributes);
    }

    get attributes() {
        return this.__tf.attributes;
    }

    /**
     *
     * @param {ParticleAttributeSpecification[]} attributes
     */
    setUniforms(attributes) {
        this.__tf.setUniforms(attributes);
    }

    get uniforms() {
        return this.__tf.uniforms;
    }

    /**
     * @param {NodeGraph} graph
     * @param {ParticleAttributeSpecification[]} attributes
     * @param {ParticleAttributeSpecification[]} uniforms
     * @returns {GLSLSimulationShader}
     */
    static from(graph, attributes = [], uniforms = []) {
        const r = new GLSLSimulationShader();

        r.setGraph(graph);
        r.setAttributes(attributes);
        r.setUniforms(uniforms);

        return r;
    }

    /**
     *
     * @return {String}
     */
    getSourceCode() {
        return this.__tf.getSourceCode();
    }

    build() {

        const gen = new GLSLCodeGenerator();

        const code = gen.generate({
            graph: this.graph,
            modules: this.__function_registry,
            attributes: this.attributes,
            uniforms: this.uniforms
        });

        this.__tf.setSourceCode(code);
    }

    /**
     *
     * @param {Array} uniform_values
     * @param {EmitterAttributeData} attributeSource
     */
    execute(uniform_values, attributeSource) {
        this.__tf.execute(uniform_values, attributeSource);

        this.__copyFromOutput(attributeSource);
    }

    /**
     * GL context uses it's own internal buffers that are not shared with JS context, because of this - it is necessary to copy data out
     * @param {EmitterAttributeData} target
     * @private
     */
    __copyFromOutput(target) {
        this.__tf.readOutput(target);
    }

    dispose() {
        this.__tf.dispose();
    }

    /**
     *
     * @param {WebGL2RenderingContext} gl
     */
    compile(gl) {
        this.__tf.compile(gl);
    }
}
