/*
 * MoonClock GLRenderer
 * 
 * Giving credit where credit is due:
 * 
 * Source code starting point was derived from the spinning textured cube demo by Apple Computer,
 * https://cvs.khronos.org/svn/repos/registry/trunk/public/webgl/sdk/demos/webkit/SpiritBox.html
 * 
 * Author: John Semler
 */
PACKAGE.Scope('net.jsemler.apps.moonclock.graphicspanel', function($p) {

    var Model = $p.Import('webgl.Model');
    var Ephemeris = $p.Import('net.jsemler.apps.moonclock.ephemeris.Ephemeris');
    var CelestrialMechanics = $p.Import('net.jsemler.libs.celestrialmechanics.CelestrialMechanics');
    var Vector = $p.Import('net.jsemler.libs.math.Vector');
        
    var PI = Math.PI;
    var deltaJulianDate = CelestrialMechanics.deltaJulianDate;
    
    // GLRenderer Class
    $p.Public.Class('GLRenderer').Scope(function($c) {
        var shaders;
        
        $c.Constructor(function(parameters) {
            this.g = {};
            this.modelLoaded = false;
            this.glModel = new Model();
            this.parameters = parameters;
            this.ephemeris = new Ephemeris(parameters);
            this.ephemeris.initialize();
            this.varyingDiameter = true;
            this.showBackside = false;
        });

        $c.Public.Members({

            setDirectionalLights: function(param) {
                this.gl.uniform1i(this.gl.getUniformLocation(shaders, "enableLightDir"), param);
            },

            setVaryingDiameter: function(param) {
                this.varyingDiameter = param;
            },

            setShowBackside: function(param) {
                this.showBackside = param;
            },

            loadModel: function(url) {
                this.glModel.sendRequest(url);
            },

            loadImage: function(url) {
                this.moonTexture = loadImageTexture(this.gl, url);
            },

            init: function (gl) {

                if (!gl) {
                    return;
                }

                shaders = simpleSetup(
                    gl, "shaders/vertex.shader", "shaders/fragment.shader",
                    [ "vNormal", "vColor", "vPosition"],
                    [ 0, 0, 0, 1 ], 10000
                );

                 // Set variables for the shaders
                gl.uniform3f(gl.getUniformLocation(shaders, "lightDir"), 1, 0, 0.5);
                gl.uniform1i(gl.getUniformLocation(shaders, "sampler2d"), 0);
                gl.uniform1i(gl.getUniformLocation(shaders, "enableLightDir"), true);

                // Load moon texture file
                this.moonTexture = loadImageTexture(gl, "resources/moon_8k_color_brim16_256x512.jpg");

                gl.frontFace(gl.CW);
                gl.enable(gl.CULL_FACE);

                // Create some matrices to use later and save their locations in the shaders
                this.g.mvMatrix = new J3DIMatrix4();
                this.g.u_normalMatrixLoc = gl.getUniformLocation(shaders, "u_normalMatrix");
                this.g.normalMatrix = new J3DIMatrix4();
                this.g.u_modelViewProjMatrixLoc = gl.getUniformLocation(shaders, "u_modelViewProjMatrix");
                this.g.mvpMatrix = new J3DIMatrix4();
            },

            update: function (gl) {
                // Enable all of the vertex attribute arrays.
                gl.enableVertexAttribArray(0);
                gl.enableVertexAttribArray(1);
                gl.enableVertexAttribArray(2);

                // Bind the vertex, normal and texCoord array buffers
                gl.bindBuffer(gl.ARRAY_BUFFER, this.g.moonModel.normalObject);
                gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
                gl.bindBuffer(gl.ARRAY_BUFFER, this.g.moonModel.texCoordObject);
                gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 0, 0);
                gl.bindBuffer(gl.ARRAY_BUFFER, this.g.moonModel.vertexObject);
                gl.vertexAttribPointer(2, 3, gl.FLOAT, false, 0, 0);

                // Bind the index element array buffer
                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.g.moonModel.indexObject);
            },

            reshape: function(gl) {
                // change the size of the canvas's backing store to match the size it is displayed.

                if (this.canvas.clientWidth == this.canvas.width && 
                        this.canvas.clientHeight == this.canvas.height) {
                    return;
                }

                this.canvas.width = this.canvas.clientWidth;
                this.canvas.height = this.canvas.clientHeight;

                // Set the viewport for the scene
                gl.viewport(0, 0, this.canvas.clientWidth, this.canvas.clientHeight);
            },

            display: function(gl) {

                // load the model file if load from URL completed
                if (this.glModel.isDataReady()) {
                    this.moonModel = this.glModel.bindData(gl);
                    this.glModel.clearDataReady();
                    this.g.moonModel = this.moonModel;
                    this.update(gl);
                    this.modelLoaded = true;
                }

                if (this.modelLoaded && this.ephemeris.isInitialized()) {

                    if (this.ephemeris.isCalculating()) {
                        return;                    
                    }

                    if (this.ephemeris.isCalculated()) {                    
                        this.parameters.update();
                        this.ephemeris.setCalculated(false);

                    }
                    else {
                        var currentSystemTime = new Date().getTime();
                        var deltaSystemTime = currentSystemTime - this.lastSystemTime;
                        this.lastSystemTime = currentSystemTime;
                        
                        if (this.parameters.isTrackingEnabled()) {
                            this.julianDate += (deltaSystemTime*this.parameters.getRate())/(1000*60*60*24);
                        }
                        
                        var ephemerisTime;
                        
                        if (this.julianDate > this.parameters.getJulianDateEnd()) {
                            this.julianDate = this.parameters.getJulianDateEnd();
                            ephemerisTime = this.julianDate;
                        }
                        else {
                            var deltaTime = deltaJulianDate(this.julianDate);
                            ephemerisTime = this.julianDate + deltaTime;
                        }
                        
                        this.parameters.setJulianDate(ephemerisTime);
                        this.parameters.setFinalEpoch(ephemerisTime);
                        this.ephemeris.calculate();
                        return;
                    }

                }
                else {
                    return;
                }

                // Make sure the canvas is sized correctly.
                this.reshape(gl);

                // Clear the canvas
                gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

                // Reset the current matrix to the "identity"
                this.g.mvMatrix.makeIdentity();

                var 
                    lightVector = this.parameters.getLightVectors(),
                    observeVector = this.parameters.getObserveVectors(),
                    upDirections = this.parameters.getUpDirections(),
                    eye = Vector.multiply(observeVector, 1/Vector.norm(observeVector)*200.0),
                    up  = upDirections;                    
                var diameter = this.parameters.getDiameter();
                var scale = (diameter/(.5 * PI/180.0));

                this.g.perspectiveMatrix = new J3DIMatrix4();
                this.g.perspectiveMatrix.perspective(.75, this.canvas.clientWidth / this.canvas.clientHeight, 190, 210);

                if (this.showBackside) {
                    this.g.perspectiveMatrix.lookat(Vector.multiply(eye,-1), [0, 0, 0], up);
                }
                else {
                    this.g.perspectiveMatrix.lookat(eye, [0, 0, 0], up);
                }

                var
                    librations = this.parameters.getLibrations(),
                    alpha = librations[0]*180/PI,
                    beta  = librations[1]*180/PI,
                    gamma = librations[2]*180/PI;

                gl.uniform3f(gl.getUniformLocation(shaders, "lightDir"), lightVector[0], lightVector[1], lightVector[2]);

                if (this.varyingDiameter) {
                    this.g.mvMatrix.scale(scale, scale, scale);                
                }

                this.g.mvMatrix.rotate(alpha, 0, 0, 1);
                this.g.mvMatrix.rotate(beta,  1, 0, 0);
                this.g.mvMatrix.rotate(gamma, 0, 0, 1);

                // Construct the normal matrix from the model-view matrix and pass it in
                this.g.normalMatrix.load(this.g.mvMatrix);
                this.g.normalMatrix.invert();
                this.g.normalMatrix.transpose();
                this.g.normalMatrix.setUniform(gl, this.g.u_normalMatrixLoc, false);

                // Construct the model-view * projection matrix and pass it in
                this.g.mvpMatrix.load(this.g.perspectiveMatrix);
                this.g.mvpMatrix.multiply(this.g.mvMatrix);
                this.g.mvpMatrix.setUniform(gl, this.g.u_modelViewProjMatrixLoc, false);

                // Bind the texture data
                gl.bindTexture(gl.TEXTURE_2D, this.moonTexture);
                gl.texParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_NEAREST);
                gl.texParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);

                // Draw the Sphere
                gl.drawElements(gl.TRIANGLES, this.g.moonModel.numIndices, gl.UNSIGNED_SHORT, 0);
            },
            
            setJulianDate: function(julianDate) {
                this.julianDate = julianDate;  
            },

            start: function() {
                this.lastSystemTime = new Date().getTime();
                this.julianDate = 2440587.5 + (this.lastSystemTime/1000) /(60*60*24);
                this.canvas = document.getElementById("moonclock-canvas");

                // tell the simulator when to lose context.
                //this.canvas = WebGLDebugUtils.makeLostContextSimulatingCanvas(this.canvas);
                //this.canvas.loseContextInNCalls(2000);

                var that = this;

                var f = function() {
                    that.display(that.gl);
                    that.requestId = window.requestAnimFrame(f, that.canvas);            
                };

                function handleContextLost(e) {
                    e.preventDefault();
                    clearLoadingImages();

                    if (that.requestId !== undefined) {
                        window.cancelAnimFrame(this.requestId);
                        that.requestId = undefined;
                    }
                }

                function handleContextRestored() {
                    that.init();
                    f();
                }

                this.canvas.addEventListener('webglcontextlost', handleContextLost, false);
                this.canvas.addEventListener('webglcontextrestored', handleContextRestored, false);

                this.gl = initWebGL("moonclock-canvas");
                this.init(this.gl);

                if (!this.gl) {
                    return;
                }

                f();
            }    
        });
        
    });
});