import {getDisconnectedVertices, getNormalsFromIndices, getNormalsFromVertices} from "../geometry/Figure.js";
import V2 from "../modules/V2.js";
import V3 from "../modules/V3.js";

/**
 * Programa de sombreado Normal Mapping.
 * @author Melissa Méndez Servín.
 */
export default class NormalMapping{

    constructor(gl, WebGL, figure, srcTextures, draw_callback, initialUniforms){
        var vsh = `#version 300 es
                    uniform mat4 u_PVM_matrix;
                    uniform mat4 u_VM_matrix;
                    uniform mat4 u_VMN_matrix;
                    
                    struct Light{
                        vec4 position;
                        vec3 la;
                        vec3 ld;
                        vec3 ls;
                    };

                    uniform Light u_light;

                    in vec4 a_position;
                    in vec3 a_normal;
                    in vec3 a_tangent;
                    in vec3 a_bitangent;
                    in vec2 a_texcoord;
                
                    vec3 v_position; 
                    
                    out vec3 v_normal;
                    out vec2 v_texcoord;
                    out vec3 v_frag_to_light;
                    out vec3 v_view_direction;

                    void main(){
                        v_normal = (u_VMN_matrix * vec4(a_normal, 0)).xyz;
                        vec3 T = normalize(vec3(u_VMN_matrix * vec4(a_tangent, 0)));
                        vec3 B = normalize(vec3(u_VMN_matrix * vec4(a_bitangent, 0)));
                        vec3 N = normalize(v_normal);
                        mat3 TBN = mat3(
                            T.x, B.x, N.x,
                            T.y, B.y, N.y,
                            T.z, B.z, N.z
                        );

                        v_position = (u_VM_matrix * a_position).xyz;
                        v_texcoord = a_texcoord;

                        v_frag_to_light = TBN * (u_light.position.xyz - v_position);
                        v_view_direction = TBN * (-v_position);

                        gl_Position = u_PVM_matrix * a_position;

                    }`;
        var fsh = `#version 300 es
                    precision highp float;
                    
                    struct Material{    
                        sampler2D diffuse_map;
                        sampler2D normal_map;
                        float shininess;
                    };
                    
                    struct Light{
                        vec4 position;
                        vec3 la;
                        vec3 ld;
                        vec3 ls;
                    };

                    uniform Light u_light;
                    uniform Material u_material; 

                    in vec2 v_texcoord;
                    in vec3 v_position;
                    in vec3 v_normal;
                    in vec3 v_frag_to_light;
                    in vec3 v_view_direction;

                    out vec4 glColor;
                
                    void main(){
                        vec3 L = normalize(v_frag_to_light);
                        vec3 N = texture(u_material.normal_map, v_texcoord).rgb * 2.0 + 1.0;
                        N = normalize(N);

                        float cos_angle = max(dot(N, L), 0.0);
                        vec3 diffuse_color = texture(u_material.diffuse_map, v_texcoord).rgb;
                        
                        float specular = 0.0;
            
                        if(cos_angle > 0.0){
                                vec3 R = reflect(-L,N);
                                vec3 V = normalize(v_view_direction);
                                float spec_angle = max(dot(R,V),0.0);
                                specular = pow(spec_angle, u_material.shininess);
                        }
                        float d = length(v_frag_to_light);
                        float attenuation = 1.0/(0.01*d*d + 0.28*d + 0.03);
                        
                        vec3 contribution = vec3(diffuse_color * u_light.la + 
                                            attenuation * (diffuse_color * u_light.ld * cos_angle +
                                                           u_light.ls * specular));
                        glColor = vec4(contribution, 1.0);
                    }`; 

        if (WebGL.programs["NM"])
            this.program = WebGL.programs["NM"];
        else
            this.program = WebGL.createProgram(gl, vsh, fsh, "NM");
            
        this.vertices = (figure.byIndices || figure.getNormals) ?  figure.getVertices() : getDisconnectedVertices(figure.getVertices(), figure.getFaces());
        
        if(figure.getNormals)
            this.normals = figure.getNormals();
        else
            this.normals = (figure.byIndices) ? getNormalsFromIndices(figure.getFaces(), this.vertices) : getNormalsFromVertices(this.vertices);
        
        this.uvs = figure.getTexCoordinates();
        
        let {tangents, bitangents} = this.getTangentAndBitangentVectors(this.vertices, this.normals, this.uvs);
       
        let attributes = {  position: { numComponents: 3, 
                                                 data: this.vertices},
                              normal: { numComponents: 3, 
                                                 data: this.normals},
                            texcoord: { numComponents: 2, 
                                                 data: this.uvs,
                                            normalize: true},
                             tangent: { numComponents: 3, 
                                                 data: tangents},
                            bitangent: { numComponents: 3, 
                                                    data: bitangents},
                         };
        
        if(figure.byIndices){
            let vaoAndIndices = WebGL.setVAOAndAttributes(gl, this.program, attributes, figure.getFaces());
            this.vao = vaoAndIndices.vao;
            this.indexBuffer = vaoAndIndices.indexBuffer;
            this.numIndices = figure.numIndices;
        }else{
            this.vao = WebGL.setVAOAndAttributes(gl, this.program, attributes);  
            this.numElements = this.vertices.length/3;
        }
        this.setUniforms = WebGL.setUniforms(gl, this.program);

        this.texture = WebGL.loadImages( srcTextures, createTextures);
        this.uniforms = Object.assign({}, initialUniforms);

        function createTextures(images){
            WebGL.createTextures(gl, images, srcTextures, draw_callback);
        }
    }
    getTangentAndBitangentVectors(vertices, normals, uvs) {
        let tangents = [];
        let bitangents = [];
        var v0, v1, v2;
        var uv0, uv1, uv2;
        var deltaUV1, deltaUV2;
        var q1,q2;
        var div, det;
        var N, T, B;
        var numVertices = vertices.length/3;

        for (let i=0; i< numVertices; i+=3) {
            v0 = new V3(vertices[3*i], vertices[(3*i)+1], vertices[(3*i)+2]);
            v1 = new V3(vertices[3*(i+1)], vertices[(3*(i+1))+1], vertices[(3*(i+1))+2]);
            v2 = new V3(vertices[3*(i+2)], vertices[(3*(i+2))+1], vertices[(3*(i+2))+2]);
            N =  new V3(normals[i+0], normals[i+2], normals[i+3]);
            
            uv0 = new V2(uvs[2*i], uvs[2*i+1]);
            uv1 = new V2(uvs[2*(i+1)], uvs[(2*(i+1))+1]);
            uv2 = new V2(uvs[2*(i+2)], uvs[(2*(i+2))+1]);
            
            deltaUV1 = uv1.sub(uv0);
            deltaUV2 = uv2.sub(uv0);
            
            q1 = v1.sub(v0);
            q2 = v2.sub(v0);
            
            det = (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);
            div = 1/det;

            T = new V3(   div * (deltaUV2.y * q1.x - deltaUV1.y * q2.x),
                                div * (deltaUV2.y * q1.y - deltaUV1.y * q2.y),
                                div * (deltaUV2.y * q1.z - deltaUV1.y * q2.z));
            B = new V3( div * (- deltaUV2.x * q1.x + deltaUV1.x * q2.x),
                                div * (- deltaUV2.x * q1.y + deltaUV1.x * q2.y),
                                div * (- deltaUV2.x * q1.z + deltaUV1.x * q2.z));
            tangents.push( 
               T.x, T.y, T.z, 
               T.x, T.y, T.z, 
               T.x, T.y, T.z, 
            );
            bitangents.push( 
                B.x, B.y, B.z,
                B.x, B.y, B.z,
                B.x, B.y, B.z,
            );
        }
        for(var i = 0; i < numVertices; i++){
            T = new V3(tangents[3*i], tangents[(3*i)+1], tangents[(3*i)+2]);
            N = new V3(normals[3*i], normals[(3*i)+1], normals[(3*i)+2]);
            T = T.sub(N.scale(N.dotProduct(T))).normalize();
            B = B.sub(N.scale(N.dotProduct(B))).sub(T.scale(T.dotProduct(B)));
            
            tangents[3*i] = T.x;
            tangents[(3*i) +1] = T.y;
            tangents[(3*i) +2] = T.z;
            
            bitangents[3*i] = B.x;
            bitangents[(3*i) +1] = B.y;
            bitangents[(3*i) +2] = B.z;
        }
        return {tangents: tangents, bitangents: bitangents};
      }
}