/* <copyright>
This file contains proprietary software owned by Motorola Mobility, Inc.<br/>
No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.<br/>
(c) Copyright 2011 Motorola Mobility, Inc.  All Rights Reserved.
</copyright> */

// RDGE namespaces
var RDGE = RDGE || {};

/**
*  supported uniform types
*/
RDGE.UNIFORMTYPE = function () {
    this.INT = 0x3F0;
    this.FLOAT = 0x3E8;
    this.FLOAT2 = 0x3E9;
    this.FLOAT3 = 0x3EA;
    this.FLOAT4 = 0x3EB;
    this.MATRIX3 = 0x3EC;
    this.MATRIX4 = 0x3ED;
    this.TEXTURE2D = 0x3EE;
    this.TEXTURECUBE = 0x3EF;
};

/**
* RDGE.RenderObject - contains references to all the data need to render, including vertex buffers, uniform handles, and matrices
* @param shaderHandle
*/
RDGE.RenderObject = function (shaderHandle) {
    this.shader = shaderHandle;
    this.world = null;
    this.bindings = new RDGE.ShaderData();
    this.initRenderProc = null;
    this.renderProc = null;
    this.postRenderProc = null;
};

/**
* Adds a uniform to the render object to bound during render
* @param name - name of the uniform
* @param value - reference to value that will get bound (will be referenced from now on, don't delete the ref)
* @param type  - type of uniform, use RDGE.UNIFORMTYPE
*/
RDGE.RenderObject.prototype.addUniform = function (name, value, type) {
    var uniform = RDGE.globals.gl.getUniformLocation(this.shader, name);
    if (uniform) {
        uniform.debugName = name;
        this.bindings.uniforms.push(new RDGE.UniformPair(uniform, value, type));
    }
    /*
    else
    {
    gl.console.log("ERROR: uniform - " + name + " not found!");
    }
    */
};

/**
* Adds a uniform to the render object to bound during render
* @param name - name of the uniform
* @param value - reference to value that will get bound (will be referenced from now on, don't delete the ref)
* @param type - type of uniform, use RDGE.UNIFORMTYPE
*/
RDGE.RenderObject.prototype.addUniformArray = function (name, value, type, size) {
    var uniform = RDGE.globals.gl.getUniformLocation(this.shader, name);
    if (uniform) {
        for (var index = 0; index < size; index++) {
            uniform.debugName = name + index;
            this.bindings.uniforms.push(new RDGE.UniformPair(uniform, value[index], type));
            uniform += value[index].length;
            value++;
        }
    }
    /*
    else
    {
    gl.console.log("ERROR: uniform - " + name + " not found!");
    }*/
};

/**
* Add texture to uniform
* @param name - handle to the texture
* @param unit - texture slot to use
* @param type - RDGE.UNIFORMTYPE.TEXTURE2D or TEXTURE2D.TEXTURECUBE
*/
RDGE.RenderObject.prototype.addTexture = function (name, unit, type) {
    var uniform = RDGE.globals.gl.getUniformLocation(this.shader, name);
    if (uniform) {
        this.bindings.textures.push(new RDGE.TexUniform(uniform, unit, type));
    }
    /*
    else
    {
    gl.console.log("ERROR: texture uniform - " + name + " not found!");
    }
    */
};

/**
* Adds a vertex buffer to the render object
* @param buffer    - buffer to use
* @param glBufferType  - type of buffer i.e. gl.ARRAY_BUFFER
* @param attribSize  - if using attrib the size of an element (3 for vec3)
* @param attribIndex - the index slot the attrib goes in
* @param glAttribType  - type of the attrib i.e. gl.FLOAT
*/
RDGE.RenderObject.prototype.addBuffers = function (buffer, glBufferType, attribSize, attribIndex, glAttribType) {
    //gl.useProgram(this.shader);
    if (attribSize == undefined || attribIndex == undefined || glAttribType == undefined ||
    attribSize == null || attribIndex == null || glAttribType == null) {
        this.bindings.buffers.push(new RDGE.BufferAttrib(buffer, glBufferType, null, null, null));
    }
    else {
        this.bindings.buffers.push(new RDGE.BufferAttrib(buffer, glBufferType, attribSize, attribIndex, glAttribType));
    }
    //gl.useProgram(null);
};

/**
* bind the matrices, vertices and floats to shader uniforms
*/
RDGE.RenderObject.prototype.bindUniforms = function () {
    for (var uniIndex = 0; uniIndex < this.bindings.uniforms.length; uniIndex++) {
        var bind = this.bindings.uniforms[uniIndex];
        switch (bind.type) {
            case RDGE.UNIFORMTYPE.INT:
                RDGE.globals.gl.uniform1i(bind.uniform, bind.value);
                break;
            case RDGE.UNIFORMTYPE.FLOAT:
                RDGE.globals.gl.uniform1f(bind.uniform, bind.value);
                break;
            case RDGE.UNIFORMTYPE.FLOAT2:
                RDGE.globals.gl.uniform2fv(bind.uniform, bind.value);
                break;
            case RDGE.UNIFORMTYPE.FLOAT3:
                RDGE.globals.gl.uniform3fv(bind.uniform, bind.value);
                break;
            case RDGE.UNIFORMTYPE.FLOAT4:
                RDGE.globals.gl.uniform4fv(bind.uniform, bind.value);
                break;
            case RDGE.UNIFORMTYPE.MATRIX3:
                RDGE.globals.gl.uniformMatrix3fv(bind.uniform, false, bind.value);
                break;
            case RDGE.UNIFORMTYPE.MATRIX4:
                RDGE.globals.gl.uniformMatrix4fv(bind.uniform, false, bind.value);
                break;
            default:
                //          gl.console.log("RDGE.RenderObject: trying to bind unknown texture type");
                break;
        }
    }
};

/**
* binds the texture uniform to texture slots
*/
RDGE.RenderObject.prototype.bindTextures = function () {
    for (var uniIndex = 0; uniIndex < this.bindings.textures.length; uniIndex++) {
        var bind = this.bindings.textures[uniIndex];
        var error = 0;
        switch (bind.type) {
            case RDGE.UNIFORMTYPE.TEXTURE2D:
                RDGE.globals.gl.activeTexture(RDGE.globals.gl.TEXTURE0 + bind.unit);
                RDGE.globals.gl.uniform1i(bind.uniform, bind.unit);
                break;
            case RDGE.UNIFORMTYPE.TEXTURECUBE:
                RDGE.globals.gl.activeTexture(RDGE.globals.gl.TEXTURE0 + bind.unit);
                RDGE.globals.gl.uniform1i(bind.uniform, bind.unit);
                break;
            default:
                //        gl.console.log("RDGE.RenderObject: trying to bind unknown texture type");
                break;
        }
    }
};

/**
* Binds all buffers and enables any vertexAttribs
*/
RDGE.RenderObject.prototype.bindBuffers = function () {
    for (var bufIndex = 0; bufIndex < this.bindings.buffers.length; bufIndex++) {
        var bind = this.bindings.buffers[bufIndex];
        RDGE.globals.gl.bindBuffer(bind.glBufferType, bind.buffer);

        if (bind.glAttribType != null) {
            // enable the attribute and point buffer to it
            RDGE.globals.gl.enableVertexAttribArray(bind.attribIndex);

            RDGE.globals.gl.vertexAttribPointer(bind.attribIndex, bind.attribSize, bind.glAttribType, false, 0, 0);
        }
    }
};

RDGE.RenderObject.prototype.unBindBuffers = function () {
    for (var bufIndex = 0; bufIndex < this.bindings.buffers.length; bufIndex++) {
        var bind = this.bindings.buffers[bufIndex];

        if (bind.glAttribType != null) {
            // enable the attribute and point buffer to it
            RDGE.globals.gl.disableVertexAttribArray(bind.attribIndex);
        }

        RDGE.globals.gl.bindBuffer(bind.glBufferType, null);
    }
};

RDGE.RenderObject.prototype.initialize = function (initRenderProc) {
    initRenderProc(this);
};

RDGE.RenderObject.prototype.clear = function () {
    this.world = RDGE.mat4.identity();
    this.bindings = new RDGE.ShaderData();
};

/***
* Shader data proto
*/
RDGE.ShaderData = function () {
    this.uniforms = [];
    this.textures = [];
    this.buffers = [];
};

/***
* Structure to contain reference data for binding to during render
*/
RDGE.UniformPair = function (uniform, value, type) {
    this.uniform = uniform;
    this.value = value;
    this.type = type;
};

RDGE.TexUniform = function (uniform, unit, type) {
    this.uniform = uniform;
    this.unit = unit;
    this.type = type;
};

RDGE.BufferAttrib = function (buffer, glBufferType, attribSize, attribIndex, glAttribType) {
    // buffer data
    this.buffer = buffer;
    this.glBufferType = glBufferType;

    // attribute data (can be null)
    this.attribSize = attribSize;
    this.glAttribType = glAttribType;
    this.attribIndex = attribIndex;
};

RDGE.setActiveTexture = function (id, texture) {
    RDGE.globals.gl.activeTexture(id);
    RDGE.globals.gl.bindTexture(RDGE.globals.gl.TEXTURE_2D, texture);
};

RDGE.renderProcDefault = function (primSet) {
    //gl.disable(gl.DEPTH_TEST);
    //gl.disable(gl.CULL_FACE);
    var activeCam = RDGE.globals.cameraManager.getActiveCamera();
    RDGE.globals.gl.mvMatrix = activeCam.view;
    RDGE.globals.gl.mvMatrix = RDGE.mat4.mul(RDGE.globals.gl.mvMatrix, primSet.parentMesh.world);
    RDGE.globals.gl.invMvMatrix = RDGE.mat4.inverse(RDGE.globals.gl.mvMatrix);
    RDGE.globals.gl.normalMatrix = RDGE.mat4.transpose(RDGE.globals.gl.invMvMatrix);

    // update shadow light MV matrix
    RDGE.globals.gl.useProgram(arrayPeek(primSet.material.shader).shaderHandle);
    // Bind the texture

    RDGE.globals.gl.activeTexture(RDGE.globals.gl.TEXTURE0);
    RDGE.globals.gl.bindTexture(RDGE.globals.gl.TEXTURE_2D, arrayPeek(primSet.material.tex.set1).diff);

    RDGE.globals.gl.activeTexture(RDGE.globals.gl.TEXTURE1);
    RDGE.globals.gl.bindTexture(RDGE.globals.gl.TEXTURE_2D, arrayPeek(primSet.material.tex.set2).diff);

    RDGE.globals.gl.activeTexture(RDGE.globals.gl.TEXTURE2);
    RDGE.globals.gl.bindTexture(RDGE.globals.gl.TEXTURE_2D, arrayPeek(primSet.material.tex.set1).spec);

    RDGE.globals.gl.activeTexture(RDGE.globals.gl.TEXTURE3);
    RDGE.globals.gl.bindTexture(RDGE.globals.gl.TEXTURE_2D, arrayPeek(primSet.material.tex.set2).spec);

    RDGE.globals.gl.activeTexture(RDGE.globals.gl.TEXTURE4);
    RDGE.globals.gl.bindTexture(RDGE.globals.gl.TEXTURE_2D, arrayPeek(primSet.material.tex.env));

    RDGE.globals.gl.activeTexture(RDGE.globals.gl.TEXTURE5);
    RDGE.globals.gl.bindTexture(RDGE.globals.gl.TEXTURE_2D, arrayPeek(primSet.material.tex.envDiff));

    // stickers
    RDGE.setActiveTexture(RDGE.globals.gl.TEXTURE7, RDGE.globals.cam.stickerTexture[0]);
    RDGE.setActiveTexture(RDGE.globals.gl.TEXTURE8, RDGE.globals.cam.stickerTexture[1]);
    RDGE.setActiveTexture(RDGE.globals.gl.TEXTURE9, RDGE.globals.cam.stickerTexture[2]);
    RDGE.setActiveTexture(RDGE.globals.gl.TEXTURE10, RDGE.globals.cam.stickerTexture[3]);
    RDGE.setActiveTexture(RDGE.globals.gl.TEXTURE11, RDGE.globals.cam.stickerTexture[4]);
    RDGE.setActiveTexture(RDGE.globals.gl.TEXTURE12, RDGE.globals.cam.stickerTexture[5]);
    RDGE.setActiveTexture(RDGE.globals.gl.TEXTURE13, RDGE.globals.cam.stickerTexture[6]);
    RDGE.setActiveTexture(RDGE.globals.gl.TEXTURE14, RDGE.globals.cam.stickerTexture[7]);

    // copy current cams matrix
    for (var i = 0; i < 8; i++) {
        primSet.parentMesh.stickers[i].load(RDGE.globals.cam.stickers[i]);
        primSet.parentMesh.stickersPos[i].setvec(RDGE.globals.cam.stickersPos[i]);
    }
    RDGE.setActiveTexture(RDGE.globals.gl.TEXTURE15, arrayPeek(primSet.material.tex.set1).norm);
    RDGE.setActiveTexture(RDGE.globals.gl.TEXTURE6, arrayPeek(primSet.material.tex.set2).norm);

    //bind buffers and attribs
    arrayPeek(primSet.material.renderObj).bindBuffers();

    // bind shader uniforms
    arrayPeek(primSet.material.renderObj).bindTextures();

    arrayPeek(primSet.material.renderObj).bindUniforms();

    RDGE.globals.gl.drawElements(RDGE.globals.gl.TRIANGLES, primSet.size, RDGE.globals.gl.UNSIGNED_SHORT, primSet.indexInBuffer * 2);
};

RDGE.renderProcLines = function (renderObj, r, g, b, a) {
    RDGE.globals.gl.useProgram(renderObj.shader);

    renderObj.lineColor[0] = r;
    renderObj.lineColor[1] = g;
    renderObj.lineColor[2] = b;
    renderObj.lineColor[3] = a;

    //bind buffers and attribs
    renderObj.bindBuffers();

    // bind shader uniforms
    renderObj.bindUniforms();

    // draw the AABBs
    RDGE.globals.gl.drawArrays(RDGE.globals.gl.LINES, 0, renderObj.numPoints / 3);

    RDGE.globals.gl.useProgram(null);
};

RDGE.renderProcScreenQuad = function (quad) {
    RDGE.globals.gl.disable(RDGE.globals.gl.DEPTH_TEST);
    RDGE.globals.gl.useProgram(quad.shader);

    //bind buffers and attribs
    quad.renderObj.bindBuffers();

    // bind shader uniforms
    quad.renderObj.bindTextures();
    quad.renderObj.bindUniforms();

    // render
    var offset = 0;
    // Bind the texture
    RDGE.globals.gl.bindTexture(RDGE.globals.gl.TEXTURE_2D, quad.texture);
    RDGE.globals.gl.drawArrays(RDGE.globals.gl.TRIANGLES, 0, 6);

    RDGE.globals.gl.useProgram(null);
    RDGE.globals.gl.enable(RDGE.globals.gl.DEPTH_TEST);
};

// post render proc
RDGE.postRenderProcDefault = function (primSet) {
    RDGE.globals.gl.useProgram(arrayPeek(primSet.material.renderObj).shader);

    //bind buffers and attribs
    //arrayPeek(primSet.material.renderObj).unBindBuffers();

    RDGE.globals.gl.useProgram(null);
};

RDGE.renderProcDepthMap = function (primSet) {
    RDGE.globals.gl.useProgram(g_depthMap.shader)

    //bind buffers
    arrayPeek(primSet.material.renderObj).bindBuffers();

    g_depthMap.bindUniforms();

    RDGE.globals.gl.enable(RDGE.globals.gl.DEPTH_TEST);
    RDGE.globals.gl.enable(RDGE.globals.gl.CULL_FACE);
    RDGE.globals.gl.enable(RDGE.globals.gl.POLYGON_OFFSET_FILL);
    RDGE.globals.gl.cullFace(RDGE.globals.gl.FRONT);

    RDGE.globals.gl.drawElements(RDGE.globals.gl.TRIANGLES, primSet.size, RDGE.globals.gl.UNSIGNED_SHORT, primSet.indexInBuffer * 2);

    RDGE.globals.gl.cullFace(RDGE.globals.gl.BACK);
    RDGE.globals.gl.disable(RDGE.globals.gl.POLYGON_OFFSET_FILL);
    RDGE.globals.gl.disable(RDGE.globals.gl.CULL_FACE);
    //gl.disable(gl.DEPTH_TEST);

    RDGE.globals.gl.useProgram(null);
};

RDGE.renderProcShadowReceiver = function (primSet) {

    // ---- initial pass, render shadow to target
    RDGE.globals.gl.bindFramebuffer(RDGE.globals.gl.FRAMEBUFFER, primSet.shadowTarget.frameBuffer);
    RDGE.globals.gl.viewport(0, 0, primSet.shadowTarget.frameBuffer.width, primSet.shadowTarget.frameBuffer.height);
    RDGE.globals.gl.clearDepth(g_farZ);
    RDGE.globals.gl.clear(RDGE.globals.gl.COLOR_BUFFER_BIT | RDGE.globals.gl.DEPTH_BUFFER_BIT);

    RDGE.globals.gl.useProgram(arrayPeek(primSet.material.shader).shaderHandle);

    // Bind the texture
    RDGE.globals.gl.activeTexture(RDGE.globals.gl.TEXTURE0);
    RDGE.globals.gl.bindTexture(RDGE.globals.gl.TEXTURE_2D, g_depthMap.depthRT);

    // bind shader uniforms
    arrayPeek(primSet.material.renderObj).bindTextures();

    //gl.disable(gl.DEPTH_TEST);
    //gl.enable(gl.CULL_FACE);

    RDGE.globals.gl.mvMatrix = RDGE.mat4.mul(g_defaultView, primSet.parentMesh.world);

    arrayPeek(primSet.material.renderObj).bindUniforms();
    error = RDGE.globals.gl.getError();

    //bind buffers and attribs
    arrayPeek(primSet.material.renderObj).bindBuffers();
    RDGE.globals.gl.drawElements(RDGE.globals.gl.TRIANGLES, primSet.size, RDGE.globals.gl.UNSIGNED_SHORT, primSet.indexInBuffer * 2);

    //gl.enable(gl.DEPTH_TEST);
    //gl.disable(gl.CULL_FACE);

    RDGE.globals.gl.useProgram(null);

    RDGE.globals.gl.bindTexture(RDGE.globals.gl.TEXTURE_2D, primSet.shadowTarget);
    RDGE.globals.gl.generateMipmap(RDGE.globals.gl.TEXTURE_2D);
    RDGE.globals.gl.bindTexture(RDGE.globals.gl.TEXTURE_2D, null);
    RDGE.globals.gl.bindFramebuffer(RDGE.globals.gl.FRAMEBUFFER, theSceneRTT.frameBuffer);
    RDGE.globals.gl.viewport(0, 0, theSceneRTT.frameBuffer.width, theSceneRTT.frameBuffer.height);

    //----------change buffers render blur pass to quad

    RDGE.globals.gl.bindFramebuffer(RDGE.globals.gl.FRAMEBUFFER, primSet.shadowTargetFinal.frameBuffer);
    RDGE.globals.gl.viewport(0, 0, primSet.shadowTargetFinal.frameBuffer.width, primSet.shadowTargetFinal.frameBuffer.height);
    RDGE.globals.gl.clearDepth(g_farZ);
    RDGE.globals.gl.clear(RDGE.globals.gl.COLOR_BUFFER_BIT | RDGE.globals.gl.DEPTH_BUFFER_BIT);

    primSet.screenQuad.setTexture(primSet.shadowTarget);
    primSet.screenQuad.render(RDGE.renderProcScreenQuad);

    RDGE.globals.gl.bindTexture(RDGE.globals.gl.TEXTURE_2D, primSet.shadowTargetFinal);
    RDGE.globals.gl.generateMipmap(RDGE.globals.gl.TEXTURE_2D);
    RDGE.globals.gl.bindTexture(RDGE.globals.gl.TEXTURE_2D, null);
    RDGE.globals.gl.bindFramebuffer(RDGE.globals.gl.FRAMEBUFFER, theSceneRTT.frameBuffer);
    RDGE.globals.gl.viewport(0, 0, theSceneRTT.frameBuffer.width, theSceneRTT.frameBuffer.height);

    //----------change buffers render blur pass to quad again


    RDGE.globals.gl.bindFramebuffer(RDGE.globals.gl.FRAMEBUFFER, primSet.shadowTarget.frameBuffer);
    RDGE.globals.gl.viewport(0, 0, primSet.shadowTarget.frameBuffer.width, primSet.shadowTarget.frameBuffer.height);
    RDGE.globals.gl.clearDepth(g_farZ);
    RDGE.globals.gl.clear(RDGE.globals.gl.COLOR_BUFFER_BIT | RDGE.globals.gl.DEPTH_BUFFER_BIT);

    primSet.screenQuad.setTexture(primSet.shadowTargetFinal);
    primSet.screenQuad.render(RDGE.renderProcScreenQuad);

    RDGE.globals.gl.bindTexture(RDGE.globals.gl.TEXTURE_2D, primSet.shadowTarget);
    RDGE.globals.gl.generateMipmap(RDGE.globals.gl.TEXTURE_2D);
    RDGE.globals.gl.bindTexture(RDGE.globals.gl.TEXTURE_2D, null);
    RDGE.globals.gl.bindFramebuffer(RDGE.globals.gl.FRAMEBUFFER, theSceneRTT.frameBuffer);
    RDGE.globals.gl.viewport(0, 0, theSceneRTT.frameBuffer.width, theSceneRTT.frameBuffer.height);
};

RDGE.renderProcShadowProjection = function (primSet) {
    RDGE.globals.gl.useProgram(arrayPeek(primSet.material.shader).shaderHandle);
    // Bind the texture

    var error = RDGE.globals.gl.getError();
    RDGE.globals.gl.activeTexture(RDGE.globals.gl.TEXTURE0);
    error = RDGE.globals.gl.getError(RDGE.globals.gl.bindTexture(RDGE.globals.gl.TEXTURE_2D, g_depthMap.shadowTarget));
    RDGE.globals.gl.bindTexture(RDGE.globals.gl.TEXTURE_2D, RDGE.globals.meshMan.getModelByName("backdropReceiver").mesh.shadowToProject);

    // bind shader uniforms
    arrayPeek(primSet.material.renderObj).bindTextures();

    RDGE.globals.gl.mvMatrix = RDGE.mat4.mul(g_defaultView, primSet.parentMesh.world);

    arrayPeek(primSet.material.renderObj).bindUniforms();

    //bind buffers and attribs
    arrayPeek(primSet.material.renderObj).bindBuffers();

    RDGE.globals.gl.drawElements(RDGE.globals.gl.TRIANGLES, primSet.size, RDGE.globals.gl.UNSIGNED_SHORT, primSet.indexInBuffer * 2);

    RDGE.globals.gl.useProgram(null);
};