import { compileShader, linkProgram } from "./shader";
import { zcout } from "./loglevel";
import { getSharedTracer } from "./tracing/sharedtracer";
// Some no-op tracing helpers for concise code and good dead code removal in bundlers
let tracer = null;
let traceStart = (name) => { };
let traceEnd = () => { };
if (!(typeof Z_TRACING)) {
    tracer = getSharedTracer();
    traceStart = (name) => { tracer.start(name); };
    traceEnd = () => { tracer.end(); };
}
export class ImageBlitGl {
    // If _sharedContext is true we will assert the required state
    // before each draw and reset previous state when complete
    constructor(_gl, _sharedContext = true) {
        this._gl = _gl;
        this._sharedContext = _sharedContext;
        this._isWebGL2 = false;
        this._program = null;
        this._vertexBuffer = null;
        this._isWebGL2 = _gl.getParameter(_gl.VERSION).indexOf("WebGL 2") >= 0;
        if (!this._isWebGL2) {
            this._instancedArraysExtension = this._gl.getExtension("ANGLE_instanced_arrays");
        }
    }
    resetGLContext() {
        this._program = null;
        this._vertexBuffer = null;
    }
    destroy() {
        this.resetGLContext();
    }
    doBlit() {
        var _a;
        const gl = this._gl;
        const gl2 = gl; // Must check _isWebGL2 before use
        let previousState;
        if (this._sharedContext) {
            previousState = {
                enabledScissorTest: gl.isEnabled(gl.SCISSOR_TEST),
                enabledDepthTest: gl.isEnabled(gl.DEPTH_TEST),
                enabledBlend: gl.isEnabled(gl.BLEND),
                enabledCullFace: gl.isEnabled(gl.CULL_FACE),
                enabledStencilTest: gl.isEnabled(gl.STENCIL_TEST),
                program: gl.getParameter(gl.CURRENT_PROGRAM),
                boundArrayBuffer: gl.getParameter(gl.ARRAY_BUFFER_BINDING),
                posAttribState: this._getVertexAttribState(gl, 0)
            };
        }
        // We've stored all the GL state we will need to restore, so this is the
        // first moment we can initialize our WebGL objects safely
        if (!this._program)
            this._prepareContext();
        if (!this._program || !this._vertexBuffer) {
            // Didn't fully initialize our WebGL objects, bail out but reset the bits we may have changed
            if (this._sharedContext && previousState) {
                this._resetVertexAttribState(gl, 0, previousState.posAttribState);
                gl.bindBuffer(gl.ARRAY_BUFFER, previousState.boundArrayBuffer);
                gl.useProgram(previousState.program);
            }
            return;
        }
        if (this._sharedContext) {
            // This state is initially set in _prepareContext so we
            // only need to reset it if _sharedContext is true
            gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
            gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
            if (this._isWebGL2) {
                gl2.vertexAttribDivisor(0, 0);
            }
            else {
                (_a = this._instancedArraysExtension) === null || _a === void 0 ? void 0 : _a.vertexAttribDivisorANGLE(0, 0);
            }
            gl.enableVertexAttribArray(0);
            this._setGlobalState(gl);
            gl.useProgram(this._program);
        }
        // Do the drawing...
        gl.clear(gl.COLOR_BUFFER_BIT);
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
        // Reset the state
        if (this._sharedContext && previousState) {
            if (previousState.enabledBlend)
                gl.enable(gl.BLEND);
            if (previousState.enabledCullFace)
                gl.enable(gl.CULL_FACE);
            if (previousState.enabledDepthTest)
                gl.enable(gl.DEPTH_TEST);
            if (previousState.enabledScissorTest)
                gl.enable(gl.SCISSOR_TEST);
            if (previousState.enabledStencilTest)
                gl.enable(gl.STENCIL_TEST);
            this._resetVertexAttribState(gl, 0, previousState.posAttribState);
            gl.bindBuffer(gl.ARRAY_BUFFER, previousState.boundArrayBuffer);
            gl.useProgram(previousState.program);
        }
    }
    _getVertexAttribState(gl, index) {
        let divisor = 0;
        if (this._isWebGL2) {
            traceStart("getWebGL2Divisor");
            divisor = gl.getVertexAttrib(index, gl.VERTEX_ATTRIB_ARRAY_DIVISOR);
            traceEnd();
        }
        else if (this._instancedArraysExtension) {
            traceStart("getExtDivisor");
            divisor = gl.getVertexAttrib(index, this._instancedArraysExtension.VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE);
            traceEnd();
        }
        return {
            size: gl.getVertexAttrib(index, gl.VERTEX_ATTRIB_ARRAY_SIZE),
            type: gl.getVertexAttrib(index, gl.VERTEX_ATTRIB_ARRAY_TYPE),
            normalized: gl.getVertexAttrib(index, gl.VERTEX_ATTRIB_ARRAY_NORMALIZED),
            stride: gl.getVertexAttrib(index, gl.VERTEX_ATTRIB_ARRAY_STRIDE),
            offset: gl.getVertexAttribOffset(index, gl.VERTEX_ATTRIB_ARRAY_POINTER),
            bufferBinding: gl.getVertexAttrib(index, gl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING),
            enabled: gl.getVertexAttrib(index, gl.VERTEX_ATTRIB_ARRAY_ENABLED),
            divisor
        };
    }
    _resetVertexAttribState(gl, index, state) {
        gl.bindBuffer(gl.ARRAY_BUFFER, state.bufferBinding);
        gl.vertexAttribPointer(index, state.size, state.type, state.normalized, state.stride, state.offset);
        if (state.divisor != 0) {
            if (this._isWebGL2) {
                gl.vertexAttribDivisor(index, state.divisor);
            }
            else if (this._instancedArraysExtension) {
                this._instancedArraysExtension.vertexAttribDivisorANGLE(index, state.divisor);
            }
        }
        if (!state.enabled)
            gl.disableVertexAttribArray(index);
    }
    _setGlobalState(gl) {
        gl.disable(gl.SCISSOR_TEST);
        gl.disable(gl.DEPTH_TEST);
        gl.disable(gl.BLEND);
        gl.disable(gl.CULL_FACE);
        gl.disable(gl.STENCIL_TEST);
    }
    _prepareContext() {
        var _a;
        const gl = this._gl;
        gl.clearColor(0.5, 0.5, 1.0, 1.0);
        let vertexData = new Float32Array([
            -1.0, -1.0,
            -1.0, 1.0,
            1.0, -1.0,
            1.0, 1.0
        ]);
        this._vertexBuffer = gl.createBuffer();
        if (!this._vertexBuffer) {
            zcout("Couldn't create vertex buffer");
            return;
        }
        gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW);
        gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
        if (this._isWebGL2) {
            gl.vertexAttribDivisor(0, 0);
        }
        else {
            (_a = this._instancedArraysExtension) === null || _a === void 0 ? void 0 : _a.vertexAttribDivisorANGLE(0, 0);
        }
        gl.enableVertexAttribArray(0);
        let err = gl.getError();
        if (err != gl.NO_ERROR) {
            zcout(`Error setting up vertex buffer in ImageBlitGl::_prepareContext(): 0x${err.toString(16)}`);
            return;
        }
        ;
        const program = gl.createProgram();
        if (!program) {
            zcout("Couldn't create program");
            return;
        }
        const blitVsSource = `
            attribute vec4 aPosition;
            varying highp vec2 vUV;

            void main(void) {
              gl_Position = aPosition;
              vUV = 0.5 * (aPosition.xy + vec2(1.0, 1.0));
            }
        `;
        const blitFsSource = `
          varying highp vec2 vUV;
          uniform sampler2D uTexture;

          void main(void) {
            gl_FragColor = texture2D(uTexture, vUV);
          }
        `;
        let vertexShader = compileShader(gl, gl.VERTEX_SHADER, blitVsSource);
        let fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, blitFsSource);
        gl.attachShader(program, vertexShader);
        gl.attachShader(program, fragmentShader);
        gl.bindAttribLocation(program, 0, "aPosition");
        linkProgram(gl, program);
        let uTextureLoc = gl.getUniformLocation(program, "uTexture");
        gl.useProgram(program);
        gl.uniform1i(uTextureLoc, 0);
        err = gl.getError();
        if (err != gl.NO_ERROR) {
            zcout(`Error in ImageBlitGl::_prepareContext(): 0x${err.toString(16)}`);
        }
        this._program = program;
        this._setGlobalState(gl);
    }
}
