
var splash = (function () {

    // private
    var gl;
    var sierpenskiMesh;
    var shaderProgram;
    var grainTexture;
    var OFFSETS = [vec3.create([0, 0.5, 0]),
                   vec3.create([0.707106781186547, -0.5, 0]),
                   vec3.create([-0.353553389127747, -0.5, -0.612372436541917]),
                   vec3.create([-0.353553393524327, -0.5, 0.61237243400355])];
    var animating = true;
    var lastTime = 0;
    var theta = 2.5 * Math.PI / 2;

    function initGL(canvas) {
        var img, canvas_div, body;
        
        try {
            gl = canvas.getContext("experimental-webgl");
            gl.viewportWidth = canvas.width;
            gl.viewportHeight = canvas.height;
        } catch (e) {
        }
        if (!gl) {
            // webGL not supported by this browser
            //alert("Could not initialise WebGL, sorry :-(");
            
            // Create static image tag
            // <img src="images/card_logo.png" alt="seirpinski's sponge">
            img = document.createElement("img");
            img.src = "images/card_logo.png";
            img.alt = "seirpinski's sponge";

            canvas_div = document.getElementById("canvas_div");

            // replace canvas with static image.
            body = document.getElementById("body");
            body.replaceChild(img, canvas_div);
        }
    }

    function getShader(gl, id) {
        var shaderScript = document.getElementById(id);
        if (!shaderScript) {
            return null;
        }

        var str = "";
        var k = shaderScript.firstChild;
        while (k) {
            if (k.nodeType === 3) {
                str += k.textContent;
            }
            k = k.nextSibling;
        }

        var shader;
        if (shaderScript.type === "x-shader/x-fragment") {
            shader = gl.createShader(gl.FRAGMENT_SHADER);
        } else if (shaderScript.type === "x-shader/x-vertex") {
            shader = gl.createShader(gl.VERTEX_SHADER);
        } else {
            return null;
        }
        
        gl.shaderSource(shader, str);
        gl.compileShader(shader);
        
        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            alert(gl.getShaderInfoLog(shader));
            return null;
        }
        
        return shader;
    }

    function initShaders() {
        var fragmentShader = getShader(gl, "test_fsh");
        var vertexShader = getShader(gl, "test_vsh");
        
        shaderProgram = gl.createProgram();
        gl.attachShader(shaderProgram, vertexShader);
        gl.attachShader(shaderProgram, fragmentShader);
        gl.linkProgram(shaderProgram);
        
        if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
            alert("Could not initialise shaders");
        }
        
        gl.useProgram(shaderProgram);

        // get attrib locations
        shaderProgram.attribs = {};
        var attribs = ["position", "normal"];
        var i;
        for (i = 0; i < attribs.length; i++) {
            shaderProgram.attribs[attribs[i]] = gl.getAttribLocation(shaderProgram, attribs[i]);
            gl.enableVertexAttribArray(shaderProgram.attribs[attribs[i]]);
        }

        // TODO: go back to light_color, and light_dir array.
        // get uniform locations
        shaderProgram.uniforms = {};
        var uniforms = { 
            color: "color", 
            model_vec_mat: "model_vec_mat", 
            proj_view_model_mat: "proj_view_model_mat", 
            ambient_color: "ambient_color", 
            light_color0: "light_color[0]", 
            light_color1: "light_color[1]",
            light_dir0: "light_dir[0]",
            light_dir1: "light_dir[1]",
            grain_size: "grain_size",
            grain_angle: "grain_angle",
            grain_texture: "grain_texture"
        };

        var key;
        for (key in uniforms) {
            if (uniforms.hasOwnProperty(key)) {
                shaderProgram.uniforms[key] = gl.getUniformLocation(shaderProgram, uniforms[key]);
            }
        }
    }

    function drawScene() {
        gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
        
        var projMat = mat4.create();
        mat4.perspective(30, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, projMat);

        var viewMat = mat4.create();
        mat4.identity(viewMat);
        mat4.translate(viewMat, [0.0, 0.0, -13.0]);

        var modelMat = mat4.create();
        mat4.identity(modelMat);
        mat4.rotate(modelMat, theta, vec3.create([0.2, 0.3, 0]));

        var projViewModelMat = mat4.create(projMat);
        mat4.multiply(projViewModelMat, viewMat);
        mat4.multiply(projViewModelMat, modelMat);

        // set uniforms
        gl.uniform4fv(shaderProgram.uniforms.color, [1, 1, 1, 1]);
        gl.uniformMatrix4fv(shaderProgram.uniforms.model_vec_mat, false, modelMat);
        gl.uniformMatrix4fv(shaderProgram.uniforms.proj_view_model_mat, false, projViewModelMat);
        gl.uniform3fv(shaderProgram.uniforms.ambient_color, [0.05, 0.05, 0.05]);

        gl.uniform3fv(shaderProgram.uniforms.light_dir0, [0.0, 1.0, 0.0]);
        gl.uniform3fv(shaderProgram.uniforms.light_color0, [1.0, 1.0, 1.0]);

        var n = vec3.create([1.0, 0.2, 0.2]);
        vec3.normalize(n);
        gl.uniform3fv(shaderProgram.uniforms.light_dir1, n);
        gl.uniform3fv(shaderProgram.uniforms.light_color1, [0.2, 0.2, 0.2]);

        gl.uniform1f(shaderProgram.uniforms.grain_angle, Math.PI / 6);
        gl.uniform1f(shaderProgram.uniforms.grain_size, 5);

        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, grainTexture);
        gl.uniform1i(shaderProgram.grain_texture, 0);
        
        // draw the sierpenskiMesh
        gl.bindBuffer(gl.ARRAY_BUFFER, sierpenskiMesh.position);
        gl.vertexAttribPointer(shaderProgram.attribs.position, sierpenskiMesh.position.itemSize, gl.FLOAT, false, 0, 0);

        gl.bindBuffer(gl.ARRAY_BUFFER, sierpenskiMesh.normal);
        gl.vertexAttribPointer(shaderProgram.attribs.normal, sierpenskiMesh.normal.itemSize, gl.FLOAT, false, 0, 0);

        gl.drawArrays(gl.TRIANGLES, 0, sierpenskiMesh.position.numItems);
    }

    function initTexture() {
        grainTexture = gl.createTexture();
        grainTexture.image = new Image();
        grainTexture.image.onload = function () {
            gl.bindTexture(gl.TEXTURE_2D, grainTexture);
            gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, grainTexture.image);
            gl.generateMipmap(gl.TEXTURE_2D);
            gl.bindTexture(gl.TEXTURE_2D, null);
        };
        grainTexture.image.src = "images/diag.png";
    }

    function animate() {
        var timeNow = new Date().getTime();
        if (lastTime !== 0) {
            var elapsed = timeNow - lastTime;
            theta += (elapsed / 1000.0) * 0.1;
        }
        lastTime = timeNow;
    }

    function tick() {
        if (animating) {
            requestAnimFrame(tick);
        }
        animate();
        drawScene();
    }

    function tetraVerts(center, height) {
        var v = OFFSETS.map(function (x) {
            var result = vec3.create(x);
            vec3.scale(result, height);
            vec3.add(result, center);
            return result;
        });

        return [v[0], v[1], v[2], 
                v[0], v[2], v[3], 
                v[0], v[3], v[1], 
                v[3], v[2], v[1]];
    }

    function sierpenskiVerts(center, height, n) {
        if (n === 0) {
            return tetraVerts(center, height);
        } else {
            return OFFSETS.map(function (x) {
                var newCenter = vec3.create(x);
                vec3.scale(newCenter, height / 2);
                vec3.add(newCenter, center);
                return sierpenskiVerts(newCenter, height / 2, n - 1);
            }).reduce(function (x, y) { return x.concat(y); }, []);
        }
    }

    function flatten(a) {
        var size = 0, i, j, index = 0, result;

        // determine size of flattened array
        for (i = 0; i < a.length; i++) {
            size += a[i].length || 0;
        }

        // allocate new float32 array
        result = new Float32Array(size);

        // fill up result
        for (i = 0; i < a.length; i++) {
            for (j = 0; j < (a[i].length || 0); j++) {
                result[index++] = a[i][j];
            }
        }
        return result;
    }

    function makeSierpenskiMesh() {
        var CENTER = vec3.create([0, 0, 0]);
        var HEIGHT = 4;
        var SUBDIVS = 2;

        // generate positions
        var vec3Array = sierpenskiVerts(CENTER, HEIGHT, SUBDIVS);
        var posArray = flatten(vec3Array);

        // generate normals
        var normalArray = new Float32Array(posArray.length);
        var v0 = vec3.create();
        var v1 = vec3.create();
        var v2 = vec3.create();
        var u = vec3.create();
        var v = vec3.create();
        var n = vec3.create();
        var i, j;

        for (i = 0; i < normalArray.length; i += 9) {
            for (j = 0; j < 3; j++) {
                v0[j] = posArray[i + j];
                v1[j] = posArray[i + 3 + j];
                v2[j] = posArray[i + 6 + j];
            }
            vec3.subtract(v0, v1, u);
            vec3.subtract(v0, v2, v);
            vec3.cross(u, v, n);
            vec3.normalize(n);
            for (j = 0; j < 9; j++) {
                normalArray[i + j] = n[j % 3];
            }
        }

        // set up position attrib buffer
        var positionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
        positionBuffer.itemSize = 3;
        positionBuffer.numItems = vec3Array.length;
        gl.bufferData(gl.ARRAY_BUFFER, posArray, gl.STATIC_DRAW);

        // set up normal attrib buffer
        var normalBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
        normalBuffer.itemSize = 3;
        normalBuffer.numItems = vec3Array.length; // same as positions
        gl.bufferData(gl.ARRAY_BUFFER, normalArray, gl.STATIC_DRAW);

        return {
            position: positionBuffer, 
            normal: normalBuffer
        };
    }

    // public
    return {
        start: function () {
            var canvas = document.getElementById("canvas");
            initGL(canvas);
            if (gl) {
                initShaders();
                initTexture();

                sierpenskiMesh = makeSierpenskiMesh();

                gl.clearColor(1, 1, 1, 1);
                gl.enable(gl.DEPTH_TEST);

                tick();
            }
        },
        
        startAnimation: function () {
            if (!animating) {
                lastTime = new Date().getTime();
                requestAnimFrame(tick);
            }
            animating = true;
        },

        stopAnimation: function () {
            animating = false;
        }
    };
}());
