/*global define,Float32Array*/

/**
 * Module defining GLPlot. Created by vwoeltje on 11/12/14.
 */
define(
    [],
    function () {
        "use strict";

        // WebGL shader sources (for drawing plain colors)
        var FRAGMENT_SHADER = [
                "precision mediump float;",
                "uniform vec4 uColor;",
                "void main(void) {",
                "gl_FragColor = uColor;",
                "}"
            ].join('\n'),
            VERTEX_SHADER = [
                "attribute vec2 aVertexPosition;",
                "uniform vec2 uDimensions;",
                "uniform vec2 uOrigin;",
                "void main(void) {",
                "gl_Position = vec4(2.0 * ((aVertexPosition - uOrigin) / uDimensions) - vec2(1,1), 0, 1);",
                "}"
            ].join('\n');

        /**
         * Create a new chart which uses WebGL for rendering.
         *
         * @constructor
         * @param {CanvasElement} canvas the canvas object to render upon
         * @throws {Error} an error is thrown if WebGL is unavailable.
         */
        function GLChart(canvas) {
            var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"),
                vertexShader,
                fragmentShader,
                program,
                aVertexPosition,
                uColor,
                uDimensions,
                uOrigin,
                buffer;

            // Ensure a context was actually available before proceeding
            if (!gl) {
                throw new Error("WebGL unavailable.");
            }

            // Initialize shaders
            vertexShader = gl.createShader(gl.VERTEX_SHADER);
            gl.shaderSource(vertexShader, VERTEX_SHADER);
            gl.compileShader(vertexShader);
            fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
            gl.shaderSource(fragmentShader, FRAGMENT_SHADER);
            gl.compileShader(fragmentShader);

            // Assemble vertex/fragment shaders into programs
            program = gl.createProgram();
            gl.attachShader(program, vertexShader);
            gl.attachShader(program, fragmentShader);
            gl.linkProgram(program);
            gl.useProgram(program);

            // Get locations for attribs/uniforms from the
            // shader programs (to pass values into shaders at draw-time)
            aVertexPosition = gl.getAttribLocation(program, "aVertexPosition");
            uColor = gl.getUniformLocation(program, "uColor");
            uDimensions = gl.getUniformLocation(program, "uDimensions");
            uOrigin = gl.getUniformLocation(program, "uOrigin");
            gl.enableVertexAttribArray(aVertexPosition);

            // Create a buffer to holds points which will be drawn
            buffer = gl.createBuffer();

            // Use a line width of 2.0 for legibility
            gl.lineWidth(2.0);

            // Enable blending, for smoothness
            gl.enable(gl.BLEND);
            gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

            // Utility function to handle drawing of a buffer;
            // drawType will determine whether this is a box, line, etc.
            function doDraw(drawType, buf, color, points) {
                gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
                gl.bufferData(gl.ARRAY_BUFFER, buf, gl.DYNAMIC_DRAW);
                gl.vertexAttribPointer(aVertexPosition, 2, gl.FLOAT, false, 0, 0);
                gl.uniform4fv(uColor, color);
                gl.drawArrays(drawType, 0, points);
            }

            return {
                /**
                 * Clear the chart.
                 */
                clear: function () {
                    gl.viewport(0, 0, canvas.width, canvas.height);
                    gl.clear(gl.COLOR_BUFFER_BIT + gl.DEPTH_BUFFER_BIT);
                },
                /**
                 * Set the logical boundaries of the chart.
                 * @param {number[]} dimensions the horizontal and
                 *        vertical dimensions of the chart
                 * @param {number[]} origin the horizontal/vertical
                 *        origin of the chart
                 */
                setDimensions: function (dimensions, origin) {
                    gl.uniform2fv(uDimensions, dimensions);
                    gl.uniform2fv(uOrigin, origin);
                },
                /**
                 * Draw the supplied buffer as a line strip (a sequence
                 * of line segments), in the chosen color.
                 * @param {Float32Array} buf the line strip to draw,
                 *        in alternating x/y positions
                 * @param {number[]} color the color to use when drawing
                 *        the line, as an RGBA color where each element
                 *        is in the range of 0.0-1.0
                 * @param {number} points the number of points to draw
                 */
                drawLine: function (buf, color, points) {
                    doDraw(gl.LINE_STRIP, buf, color, points);
                },
                /**
                 * Draw a rectangle extending from one corner to another,
                 * in the chosen color.
                 * @param {number[]} min the first corner of the rectangle
                 * @param {number[]} max the opposite corner
                 * @param {number[]} color the color to use when drawing
                 *        the rectangle, as an RGBA color where each element
                 *        is in the range of 0.0-1.0
                 */
                drawSquare: function (min, max, color) {
                    doDraw(gl.TRIANGLE_FAN, new Float32Array(
                        min.concat([min[0], max[1]]).concat(max).concat([max[0], min[1]])
                    ), color, 4);
                }
            };
        }
        return GLChart;
    }
);