import WebGL from "../modules/WebGLUtils.js";
import M3 from "../modules/M3.js";
import V2 from "../modules/V2.js";

var vsh = `#version 300 es
            uniform mat3 u_matrix;
            uniform int numPointsForLines;
            uniform vec4 slice;
            uniform mat2 changeOfBasis;
            uniform float unit;
            uniform bool axis;
            uniform vec4 axisColor;
            uniform vec4 gridColor;
            out vec4 lineColor;
            
            void main(){
                //Asigna por pares de vértices -1 al primero y 1 al segundo
                int odd = gl_VertexID % 2;
                int minusOneOrOne = odd * 2 - 1;

                //Obtenemos el índice, juntamos por pares
                int i = gl_VertexID - (odd * 1);
                // n - n/2 ya que i es par
                i -= i/2;            

                //Total de horizontales 
                int horizontalLimit = int(slice.x + slice.y);
                vec2 endpoint;
                if( i <= horizontalLimit){ //Líneas horizontales
                    //Calculamos el índice actual para graficar negativos también
                    i -= int(slice.x);
                    int k = (minusOneOrOne < 0) ? int(slice.z) : int(slice.w); //escalamos la coordenada x
                    endpoint = changeOfBasis * vec2( k * minusOneOrOne, float(i));
                }else{ //Líneas verticales
                    i = i - horizontalLimit - int(slice.z);
                    int k = (minusOneOrOne < 0) ? int(slice.x) : int(slice.y); //escalamos la coordenada y
                    endpoint = changeOfBasis * vec2(float(i), k * minusOneOrOne);
                }
                vec3 pos = u_matrix * vec3(endpoint * unit, 1);
                gl_Position = vec4(pos.xy, 0, 1);

                //Eje XY 
                if(axis && i == 0)
                    lineColor =  normalize(axisColor);
                else
                    lineColor = normalize(gridColor);
            }`;
var fsh = `#version 300 es
            precision highp float;
            
            in vec4 lineColor;
            out vec4 glColor;

            void main(){
                glColor = lineColor;
            }
            `;

/**
 * Grid. 
 * Clase que dibuja una plano cartesiano o rejilla 2D dados los vectores base.
 * @author Melissa Méndez Servín.
 */
export default class Grid{
    /**
     * Dibuja un plano cartesiado estático (o rejilla) ajustado de acuerdo al 
     * tamaño del canvas y el clipspace. 
     * @param {WebGLRenderingContext} gl el WebGLRenderingContext sobre el cual se 
     *                                   construirá el plano.
     * @param {Number} unit la unidad con la que se creará el plano.
     * @param {Boolean} axis true si se quiere marcar el eje XY, false en otro caso.
     * @param {Object} basis constructor con los vectores base definidos como x y y. 
     */
    constructor(gl, unit=50, axis=true, basis=null, colors=null){
        if(!gl) return;

        this.program = WebGL.createProgram(gl,vsh,fsh);
        this.vao = gl.createVertexArray();
        this.basis = basis || {x: new V2(1,0), y: new V2(0,1)};
        gl.bindVertexArray(this.vao);   
        gl.bindVertexArray(null); 

        //Uniforms que no cambian.
        this.uniforms = { unit: unit, 
                          axis: axis,
                          gridColor : [123, 143, 159, 255],
                          axisColor : [0, 0, 0, 255],
                        };
        if(colors){
            if(colors.length > 1)
                this.uniforms.axisColor = colors[1];
            if(colors[0] != null)
                this.uniforms.gridColor = colors[0];
        }

        this.setUniforms = WebGL.setUniforms(gl, this.program);
    }
    /**
     * Renderiza la cuadrícula.
     * limits = { width: viewportWidth, height: viewportHeight,
     *            l: left, r: right, t: top, b: bottom};
     * @param {WebGLRenderingContext} gl el WebGLRenderingContext sobre el cual se 
     *                                   construirá el plano.
     * @param {Object} limits (opcional) medidas para la proyección (clipspace) y/o 
     *                         del viewport.
     */
    draw(gl, limits, basis, axis){
        if(!limits)
            var limits = {};
        var {width, height} = (limits.width == undefined || limits.height == undefined) ? gl.canvas: limits;

        if(limits.l == undefined || limits.r == undefined || limits.t == undefined || limits.b == undefined)
            Object.assign(limits, { l: -width/2, r: width/2, t: height/2, b: -height/2});   

        gl.useProgram(this.program);
        gl.bindVertexArray(this.vao);

        var projectionMatrix = M3.projection(limits.l, limits.r, limits.t, limits.b);
        
        var currBasis = basis || this.basis;
        var vecZero = M3.multiplyVector(projectionMatrix,[0,0,1]);
        
        this.uniforms.u_matrix = projectionMatrix;
        this.uniforms.changeOfBasis = [currBasis.x.x, currBasis.x.y, currBasis.y.x, currBasis.y.y];
        
        var lines = this.getNumPointsForLines(limits,vecZero);
        this.uniforms.slice = [lines.nX, lines.pX, lines.nY, lines.pY];
        this.uniforms.numPointsForLines = lines.totalPoints;
        
        if(axis != undefined)
            this.uniforms.axis = axis;

        this.setUniforms(this.uniforms);

        gl.drawArrays(gl.LINES, 0, lines.totalPoints);
    }
    /**
     * Obtiene el número de puntos necesarios para construir el plano XY de abcisas 
     * y ordenadas, y cubrir el canvas, de acuerdo con el vector cero dado, definido
     * por la matriz de proyección, dentro del rango [-1,1] (clipspace). 
     * @param {Object} limits límites del clipspace.
     * @param {Number[]} vecZero el vector cero transformado por la matriz de proyección.
     */
    getNumPointsForLines(limits, vecZero){
        var width = limits.r - limits.l;
        var height = limits.t - limits.b;

        var hPortion = (vecZero[1] * 0.5) + 0.5;
        var vPortion = (vecZero[0] * 0.5) + 0.5;
        
        let unit = this.uniforms.unit;
        let e = 2; //verificar**

        var nHorizontalLines = Math.ceil((e * height * hPortion)/unit);
        var nVerticalLines = Math.ceil((e * width * vPortion)/unit);
        var pHorizontalLines = Math.ceil((e * height * (1 - hPortion))/unit);
        var pVerticalLines = Math.ceil((e * width * (1 - vPortion))/unit);

        var numLines = nHorizontalLines + nVerticalLines + pHorizontalLines + pVerticalLines;
        // numLines * 2 puntos
        numLines = Math.ceil(numLines) * 2;
        let lines = {totalPoints: numLines, nX: nHorizontalLines, nY: nVerticalLines,
                     pX: pHorizontalLines, pY: pVerticalLines};
        return lines;
    }
}