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

export default class TextureMapping{
    /**
     * 
     * @param {WebGLRenderingContext} gl 
     * @param {*} WebGL 
     * @param {*} attributes 
     */
    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;
    
        out vec3 v_position; 
        out vec3 v_local_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_local_position = normalize(a_position.xyz);
            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 specular_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_local_position;
        in vec3 v_frag_to_light;
        in vec3 v_view_direction;

        out vec4 glColor;
    
        void main(){

            float u = atan(v_local_position.z, v_local_position.x)/(2.0*3.14159);
            float v = asin(v_local_position.y)/3.14159;
            vec2 v_texcoord = vec2(u,v);

            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_intensity = texture(u_material.specular_map, v_texcoord).r;
            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 ambient = vec3(0.5);
            vec3 contribution = vec3(diffuse_color * ambient * u_light.la + 
                               (diffuse_color * u_light.ld * cos_angle +
                                               specular_intensity * u_light.ls * specular));
            //glColor = vec4(contribution, 1.0);
            glColor = vec4(diffuse_color + vec3(0.1),1.0);
        }`; 

        if (WebGL.programs["TM"])
            this.program = WebGL.programs["TM"];
        else
            this.program = WebGL.createProgram(gl, vsh, fsh, "MM");
        
            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.texcoords = (figure.byIndices || figure.flat) ? figure.getTexCoordinates() : getDisconnectedTexcoords(figure.getTexCoordinates(), figure.getFaces());
        

        let {tangents, bitangents} = this.getTangentAndBitangentVectors(this.vertices, this.normals, this.texcoords);
        let attributes = {  position: { numComponents: 3, 
                                                 data: this.vertices},
                              normal: { numComponents: 3, 
                                                 data: this.normals},
                              texcoord: { numComponents: 2, 
                                        data: this.texcoords},
                                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({ texture_factor: 1.5}, 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};
      }
}

function getTangentAndBitangentVectorsByIndices(faces, vertices, normals, uvs) {
    let tangents = new Array(faces.length).fill(0);
    let bitangents = new Array(faces.length).fill(0);
    var v0, v1, v2;
    var uv0, uv1, uv2;
    var deltaUV1, deltaUV2;
    var q1,q2;
    var div, det;
    var N, T, B;
    let i0, i1, i2;

    for (var i = 0; i<faces.length; i+=3) {
        i0 = faces[i+0]*3;
        i1 = faces[i+1]*3;
        i2 = faces[i+2]*3;

        v0 = new V3(vertices[i0], vertices[i0 + 1], vertices[i0 + 2]);
        v1 = new V3(vertices[i1], vertices[i1 + 1], vertices[i1 + 2]);
        v2 = new V3(vertices[i2], vertices[i2 + 1], vertices[i2 + 2]);
        
        N =  new V3(normals[i+0], normals[i+2], normals[i+3]);
        
        uv0 = new V2( 0.5 + Math.atan(v0.x, v0.z)/(2*Math.PI), 0.5 + Math.asin(v0.y)/Math.PI);
        uv1 = new V2( 0.5 + Math.atan(v1.x, v1.z)/(2*Math.PI), 0.5 + Math.asin(v1.y)/Math.PI);
        uv2 = new V2( 0.5 + Math.atan(v2.x, v2.z)/(2*Math.PI), 0.5 + Math.asin(v2.y)/Math.PI);
        
        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
        var tmp = new V3(tangents[i0], tangents[i0+1], tangents[i0+2]);
        tmp = tmp.add(T);
        tangents[i0+0] = tmp.x;
        tangents[i0+1] = tmp.y;
        tangents[i0+2] = tmp.z;
    
        tmp = new V3(tangents[i1], tangents[i1+1], tangents[i1+2]);
        tmp = tmp.add(T);
        tangents[i1+0] = tmp.x;
        tangents[i1+1] = tmp.y;
        tangents[i1+2] = tmp.z;
    
        tmp = new V3(tangents[i2], tangents[i2+1], tangents[i2+2]);
        tmp = tmp.add(T);
        tangents[i2+0] = tmp.x;
        tangents[i2+1] = tmp.y;
        tangents[i2+2] = tmp.z;
        //Bitangents 
        tmp = new V3(bitangents[i0], bitangents[i0+1], bitangents[i0+2]);
        tmp = tmp.add(B);
        bitangents[i0+0] = tmp.x;
        bitangents[i0+1] = tmp.y;
        bitangents[i0+2] = tmp.z;
    
        tmp = new V3(bitangents[i1], bitangents[i1+1], bitangents[i1+2]);
        tmp = tmp.add(B);
        bitangents[i1+0] = tmp.x;
        bitangents[i1+1] = tmp.y;
        bitangents[i1+2] = tmp.z;
    
        tmp = new V3(bitangents[i2], bitangents[i2+1], bitangents[i2+2]);
        tmp = tmp.add(B);
        bitangents[i2+0] = tmp.x;
        bitangents[i2+1] = tmp.y;
        bitangents[i2+2] = tmp.z;
    }
    for(var i = 0; i < faces.length/3; 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};
}
