let os = require("os");
let isMac = false;
if (os.platform() === "darwin") {
    isMac = true;
}
function WebGLRender() {
    this.gl = null
    this.glProgram = null
    this.vertexBuffer = null
    this.samplerUniform = null
    this.vShader = null
    this.fShader = null
    this.haveInit = false
    this.texture = null
    this.canvas = null
    this.viewMode = 0
    this.rotation = null
    this.bkColor = null
    this.setbkColor = false
    this.bkAlpha = 0.0
    this.bkRed = 0.0
    this.bkGreen = 0.0
    this.bkBlue = 0.0
    this.type = 0;
    if (isMac) {
        this.mirrorEnable = false
    }
    else{
        this.mirrorEnable = true
    }
    this.vertex = [
        -1, -1,
         1, -1,
        -1,  1,
         1,  1]

    this.prevViewPort = { w: 0, h: 0, canvasW: 0, canvasH: 0 }
}

WebGLRender.prototype = {
    setViewMode (mode) {
        this.viewMode = mode
    },
    setWebGLView() {
        const { w, h, prevViewPort } = this

        if (w && h && this.canvas && this.gl) {
            const rect = this.canvas.getBoundingClientRect()
            const canvasH = rect.height
            const canvasW = rect.width
            if (!(canvasW && canvasH)) return
            const isSame = w === prevViewPort.w
                && h === prevViewPort.h
                && canvasW === prevViewPort.canvasW
                && canvasH === prevViewPort.canvasH
            if (!isSame) {
                console.log(w, h, canvasW, canvasH)
            }
            
            let newW
            let newH
            const ratioW = w / canvasW
            const ratioH = h / canvasH
            
            if (this.viewMode === 1) {
                if (ratioW > ratioH) {
                    newH = canvasH
                    newW = w * canvasH / h
                } else {
                    newW = canvasW
                    newH = h * canvasW / w
                }
                const offsetX = -(newW - canvasW) / 2
                const offsetY = -(newH - canvasH) / 2
                Object.assign(prevViewPort, { w, h, canvasW, canvasH, offsetX, offsetY, newW, newH })
    
                if (offsetX, offsetY, newW, newH) {
                    this.gl = this.canvas.getContext('webgl')
                    this.gl.viewport(offsetX, offsetY, newW, newH)
                }
            } else {
                Object.assign(prevViewPort, { w, h, canvasW, canvasH, offsetX: 0, offsetY: 0, newW: canvasW, newH: canvasH })
    
                if (canvasW, canvasH) {
                    this.gl = this.canvas.getContext('webgl')
                    this.gl.viewport(0, 0, canvasW, canvasH)
                }
            }
        }
    },

    setupI420(){
        this.type = 1;
    },

    initGLfromCanvas (canvas) {
        if (canvas) {
            this.canvas = canvas
            let ret = false
            do {
                ret = this.initGL(canvas); if (!ret) { console.log('initGL failed'); break }
                ret = this.initShaders(this.type); if (!ret) { console.log('initShaders failed'); break }
                ret = this.initUniforms(this.type); if (!ret) { console.log('initUniforms failed'); break }

            } while (false)
            if (ret) {
                this.initTexture(this.type)
            }
            this.haveInit = ret
            return ret
        }
        return false
    },
    
    initBkColor(bkColor) {
        this.bkAlpha = 1;
        this.bkColor = bkColor
        this.bkRed = this.bkColor  >> 16 & 0xff / 255.0
        this.bkGreen = this.bkColor  >> 8 & 0xff / 255.0
        this.bkBlue = this.bkColor & 0xff  / 255.0;
        this.setbkColor = true
    },

    initGL (canvas) {
        try {
            this.gl = canvas.getContext('webgl')
        } catch (e) {
            console.log('gl engine init failed', e)
            return false
        }
        if (this.gl) {
            return true
        }else{
            return false
        }
    },
    initShaders (type) {
        const VSHADER_SOURCE =
            'attribute vec2 aPos;                    ' +
            'attribute vec2 aVertexTextureCoord;     ' +
            'varying highp vec2 vTextureCoord;       ' +
            'void main(void){                        ' +
            '    gl_Position = vec4(aPos, 0.0, 1.0);        ' +
            '    vTextureCoord = aVertexTextureCoord;' +
            '}'

        const FSHADER_SOURCE =
            'varying highp vec2 vTextureCoord;                                                              ' +
            'uniform sampler2D uSampler;                                                                    ' +
            'void main(void) {                                                                              ' +
            '    highp vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));     ' +
            '    gl_FragColor = vec4(textureColor.b, textureColor.g, textureColor.r, 1.0); ' +
            '}'

        const  FSHADER_SOURCE_I420 =
            "precision highp float;" +
            "varying vec2 vTextureCoord;" +
            "uniform sampler2D s_textureY;" +
            "uniform sampler2D s_textureU;" +
            "uniform sampler2D s_textureV;" +
            "void main() {" +
            "    float y, u, v, r, g, b;" +
            "    y = texture2D(s_textureY, vTextureCoord).r;" +
            "    u = texture2D(s_textureU, vTextureCoord).r;" +
            "    v = texture2D(s_textureV, vTextureCoord).r;" +
            "    u = u - 0.5;" +
            "    v = v - 0.5;" +
            "    r = y + 1.403 * v;" +
            "    g = y - 0.344 * u - 0.714 * v;" +
            "    b = y + 1.770 * u;" +
            "    gl_FragColor = vec4(r, g, b, 1.0);" +
            "  }\n";


        // compile shaders
        var vertexShader = this.makeShader(VSHADER_SOURCE, this.gl.VERTEX_SHADER)
        this.vShader = vertexShader
        var fragmentShader;
        if (type == 0) {
            fragmentShader = this.makeShader(FSHADER_SOURCE, this.gl.FRAGMENT_SHADER)
        }
        else{
            fragmentShader = this.makeShader(FSHADER_SOURCE_I420, this.gl.FRAGMENT_SHADER)
        }
        this.fShader = fragmentShader
        if (!vertexShader || !fragmentShader) {
            return false
        }
        // create program
        this.glProgram = this.gl.createProgram()

        // attach and link shaders to the program
        this.gl.attachShader(this.glProgram, vertexShader)
        this.gl.attachShader(this.glProgram, fragmentShader)
        this.gl.linkProgram(this.glProgram)

        if (!this.gl.getProgramParameter(this.glProgram, this.gl.LINK_STATUS)) {
            console.log('Unable to initialize the shader program.')
            return false
        } else {
            // use program
            this.gl.useProgram(this.glProgram)
            return true
        }
    },
    makeShader (src, type) {
        //compile the vertex shader
        const shader = this.gl.createShader(type)
        this.gl.shaderSource(shader, src)
        this.gl.compileShader(shader)

        if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
            console.log(`Error compiling shader: ${  this.gl.getShaderInfoLog(shader)}`)
            return null
        }
        return shader
    },

    initUniforms (type) {
        if (type == 0) {
            this.samplerUniform = this.gl.getUniformLocation(this.glProgram, 'uSampler')
        }
        else{
            this.ySamplerUniform = this.gl.getUniformLocation(this.glProgram, 's_textureY')
            this.uSamplerUniform = this.gl.getUniformLocation(this.glProgram, 's_textureU')
            this.vSamplerUniform = this.gl.getUniformLocation(this.glProgram, 's_textureV')

            this.gl.uniform1i(this.ySamplerUniform, 0);
            this.gl.uniform1i(this.uSamplerUniform, 1);
            this.gl.uniform1i(this.vSamplerUniform, 2);
        }
        return true
    },
    initTexture (type) {

         // vertex data
        const vertexBuffer = this.gl.createBuffer()
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, vertexBuffer)
        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.vertex), this.gl.STATIC_DRAW)

        // set position attribute
        const aVertexPosition = this.gl.getAttribLocation(this.glProgram, 'aPos')
        this.gl.vertexAttribPointer(aVertexPosition, 2, this.gl.FLOAT, false, 0, 0)
        this.gl.enableVertexAttribArray(aVertexPosition)

        if (type == 0) {
            this.texture = this.gl.createTexture()
            this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture)
            //this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, true);
            this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR)
            this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR)
            this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE)
            this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE)
        }
        else{
            this.yTexture = this.gl.createTexture()
            this.gl.bindTexture(this.gl.TEXTURE_2D, this.yTexture)
            //this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, true);
            this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR)
            this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR)
            this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE)
            this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE)

            this.uTexture = this.gl.createTexture()
            this.gl.bindTexture(this.gl.TEXTURE_2D, this.uTexture)
            //this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, true);
            this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR)
            this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR)
            this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE)
            this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE)

            this.vTexture = this.gl.createTexture()
            this.gl.bindTexture(this.gl.TEXTURE_2D, this.vTexture)
            //this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, true);
            this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR)
            this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR)
            this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE)
            this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE)
        }
    },

    setMirrorEnable(enable){
        this.mirrorEnable = enable; 
    },

    setRotation(rotation){
        if(this.rotation === rotation){
            return;
        }
        this.rotation = rotation;

        let triangleTexCoords;
        // default flip-None
        if (this.mirrorEnable) {
            triangleTexCoords = [
                1.0, 1.0,
                0.0, 1.0,
                1.0, 0.0,
                0.0, 0.0
            ]
        }
        else{
            triangleTexCoords = [
                0.0, 1.0,
                1.0, 1.0,
                0.0, 0.0,
                1.0, 0.0
            ]
        }

        // texture coordinate data
        const trianglesTexCoordBuffer = this.gl.createBuffer()
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, trianglesTexCoordBuffer)
        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(triangleTexCoords), this.gl.STATIC_DRAW)
        const vertexTexCoordAttribute = this.gl.getAttribLocation(this.glProgram, 'aVertexTextureCoord')
        this.gl.enableVertexAttribArray(vertexTexCoordAttribute)
        this.gl.vertexAttribPointer(vertexTexCoordAttribute, 2, this.gl.FLOAT, false, 0, 0)
    },

    resize (canvas) {
        // Lookup the size the browser is displaying the canvas.
        const devicePixelRatio = (window.devicePixelRatio || 1)
        const displayWidth = canvas.clientWidth * devicePixelRatio
        const displayHeight = canvas.clientHeight * devicePixelRatio

        let deltaW = Math.abs(canvas.width - displayWidth);
        let deltaH = Math.abs(canvas.height - displayHeight);
        // Check if the canvas is not the same size.
        if (deltaW >= 1.0 || deltaH >= 1.0) {
            // Make the canvas the same size
            canvas.width = displayWidth
            canvas.height = displayHeight

            console.log("devicePixelRatio:", devicePixelRatio);
        }
    },

    drawVideoFrame(videoFrame, width, height){
        this.setRotation(0);
        this.draw(videoFrame, width, height);
    },

    draw (rgbaBuf, w, h) {
        if (!this.haveInit) {
            console.log('please init gl first. can not draw')
            return
        }
        this.resize(this.canvas)

        const canvas_w = this.canvas.width
        const canvas_h = this.canvas.height

        let new_w = 0, new_h = 0

        if (this.viewMode == 0) {
            if (w < canvas_w && h < canvas_h) {
                const scale_w = (canvas_w - w) / canvas_w * 1.0
                const scale_h = (canvas_h - h) / canvas_h * 1.0
                if (scale_w < scale_h) {
                    new_w = canvas_w
                    new_h = (canvas_w - w) / w * 1.0 * h + h
                    const view_port_x = 0
                    const view_port_y = (canvas_h - new_h) / 2.0
                    this.gl.viewport(view_port_x, view_port_y, new_w, new_h)
                } else {
                    new_h = canvas_h
                    new_w = (canvas_h - h) / h * 1.0 * w + w
                    const view_port_x = canvas_w / 2.0 - new_w / 2.0
                    const view_port_y = 0
                    this.gl.viewport(view_port_x, view_port_y, new_w, new_h)
                }
            } else {
                const scale_w = (w - canvas_w) / w * 1.0
                const scale_h = (h - canvas_h) / h * 1.0
                if (scale_w < scale_h) {
                    new_h = canvas_h
                    new_w = w - (h - canvas_h) / h * 1.0 * w
                    const view_port_x = canvas_w / 2.0 - new_w / 2.0
                    const view_port_y = 0
                    this.gl.viewport(view_port_x, view_port_y, new_w, new_h)
                }
                else {
                    new_w = canvas_w
                    new_h = h - (w - canvas_w) / w * 1.0 * h
                    const view_port_x = 0
                    const view_port_y = canvas_h / 2.0 - new_h / 2.0
                    this.gl.viewport(view_port_x, view_port_y, new_w, new_h)
                }
            }
        } else if (this.viewMode == 1){
            // 将宽高比最小的一组进行放大即可
            const scale_w = w * 1.0 / canvas_w;
            const scale_h = h * 1.0 / canvas_h;
            if (scale_w < scale_h) {
                // 宽度比例小于高度比例，此时将宽度放大或缩小到 canvas 画布中，高度自动调整
                const dw = canvas_w;
                const dh = h * (canvas_w * 1.0 / w );
                const halfOfDh = (dh - canvas_h)/ 2.0;
                this.gl.viewport(0, -halfOfDh, dw, dh);
            }
            else{
                const dh = canvas_h;
                const dw = w * (canvas_h * 1.0 / h);
                const halfOfDw = (dw - canvas_w)/ 2.0;
                this.gl.viewport(-halfOfDw, 0, dw, dh);
            }
        }
        else{
            this.gl.viewport(0, 0, canvas_w, canvas_h)
        }
        
        if(this.setbkColor){
            this.gl.clearColor(this.bkRed, this.bkGreen, this.bkBlue, this.bkAlpha)
        }
        else{
            // Set clear color to black, fully opaque
            this.gl.clearColor(0.0, 0.0, 0.0, 1.0);
        }
        // Enable depth testing
        this.gl.enable(this.gl.DEPTH_TEST);
        // Near things obscure far things
        this.gl.depthFunc(this.gl.LEQUAL);
        // Clear the color as well as the depth buffer.
        this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);

        this.gl.activeTexture(this.gl.TEXTURE0);
        this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);

        this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, w, h, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, rgbaBuf)
        this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
    },

    drawI420VideoFrame(Y, U, V, width, height) {
        this.setRotation(0);
        this.drawI420(Y, width, height, U, width/2, height/2, V, width/2, height/2);
    },

    drawI420 (Y, Ywidth, Yheight, U, Uwidth, Uheight, V, Vwidth, Vheight) {
        let w = Ywidth;
        let h = Yheight;
        if (!this.haveInit) {
            console.log('please init gl first. can not draw')
            return
        }
        this.resize(this.canvas)

        const canvas_w = this.canvas.width
        const canvas_h = this.canvas.height

        let new_w = 0, new_h = 0

        if (this.viewMode == 0) {
            if (w < canvas_w && h < canvas_h) {
                const scale_w = (canvas_w - w) / canvas_w * 1.0
                const scale_h = (canvas_h - h) / canvas_h * 1.0
                if (scale_w < scale_h) {
                    new_w = canvas_w
                    new_h = (canvas_w - w) / w * 1.0 * h + h
                    const view_port_x = 0
                    const view_port_y = (canvas_h - new_h) / 2.0
                    this.gl.viewport(view_port_x, view_port_y, new_w, new_h)
                } else {
                    new_h = canvas_h
                    new_w = (canvas_h - h) / h * 1.0 * w + w
                    const view_port_x = canvas_w / 2.0 - new_w / 2.0
                    const view_port_y = 0
                    this.gl.viewport(view_port_x, view_port_y, new_w, new_h)
                }
            } else {
                const scale_w = (w - canvas_w) / w * 1.0
                const scale_h = (h - canvas_h) / h * 1.0
                if (scale_w < scale_h) {
                    new_h = canvas_h
                    new_w = w - (h - canvas_h) / h * 1.0 * w
                    const view_port_x = canvas_w / 2.0 - new_w / 2.0
                    const view_port_y = 0
                    this.gl.viewport(view_port_x, view_port_y, new_w, new_h)
                }
                else {
                    new_w = canvas_w
                    new_h = h - (w - canvas_w) / w * 1.0 * h
                    const view_port_x = 0
                    const view_port_y = canvas_h / 2.0 - new_h / 2.0
                    this.gl.viewport(view_port_x, view_port_y, new_w, new_h)
                }
            }
        } else if (this.viewMode == 1){
            // 将宽高比最小的一组进行放大即可
            const scale_w = w * 1.0 / canvas_w;
            const scale_h = h * 1.0 / canvas_h;
            if (scale_w < scale_h) {
                // 宽度比例小于高度比例，此时将宽度放大或缩小到 canvas 画布中，高度自动调整
                const dw = canvas_w;
                const dh = h * (canvas_w * 1.0 / w );
                const halfOfDh = (dh - canvas_h)/ 2.0;
                this.gl.viewport(0, -halfOfDh, dw, dh);
            }
            else{
                const dh = canvas_h;
                const dw = w * (canvas_h * 1.0 / h);
                const halfOfDw = (dw - canvas_w)/ 2.0;
                this.gl.viewport(-halfOfDw, 0, dw, dh);
            }
        }
        else{
            this.gl.viewport(0, 0, canvas_w, canvas_h)
        }
        
        if(this.setbkColor){
            this.gl.clearColor(this.bkRed, this.bkGreen, this.bkBlue, this.bkAlpha)
        }
        else{
            // Set clear color to black, fully opaque
            this.gl.clearColor(0.0, 0.0, 0.0, 1.0);
        }
        // Clear the color as well as the depth buffer.
        this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);

        this.gl.activeTexture(this.gl.TEXTURE0 + 0);
        this.gl.bindTexture(this.gl.TEXTURE_2D, this.yTexture);
        this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.LUMINANCE, Ywidth, Yheight, 0, this.gl.LUMINANCE, this.gl.UNSIGNED_BYTE, Y);

        this.gl.activeTexture(this.gl.TEXTURE0 + 1);
        this.gl.bindTexture(this.gl.TEXTURE_2D, this.uTexture);
        this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.LUMINANCE, Uwidth, Uheight, 0, this.gl.LUMINANCE, this.gl.UNSIGNED_BYTE, U);

        this.gl.activeTexture(this.gl.TEXTURE0 + 2);
        this.gl.bindTexture(this.gl.TEXTURE_2D, this.vTexture);
        this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.LUMINANCE, Vwidth, Vheight, 0, this.gl.LUMINANCE, this.gl.UNSIGNED_BYTE, V);
        this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
    }

}

module.exports = exports = WebGLRender
