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

/**
 * Programa de sombreado para simular multiples fuentes de luz puntual, 
 * usando el modelo de ilumación de Phong con materiales.
 * @author Melissa Méndez Servín.
 */
export default class MultipleLights{

    constructor(gl, WebGL, figure, initialUniforms, DIR_LIGHT, NUM_POINT_LIGHTS, NUM_SPOTLIGHTS){
        var vsh = `#version 300 es
                    uniform mat4 u_PVM_matrix;
                    uniform mat4 u_VM_matrix;
                    uniform mat4 u_VMN_matrix;
                    
                    in vec4 a_position;
                    in vec3 a_normal;
                
                    out vec3 v_normal; 
                    out vec3 v_position; 

                    void main(){
                    
                        v_position = (u_VM_matrix * a_position).xyz;
                        v_normal = (u_VMN_matrix * vec4(a_normal, 0)).xyz;
            
                        gl_Position = u_PVM_matrix * a_position;
                    }`;
        var fsh = `#version 300 es
                    precision highp float;
                    
                    struct Material{
                        vec3 ka;
                        vec3 kd;
                        vec3 ks;
                        float shininess;
                    };
                    struct DirectLight{
                        vec4 direction;

                        vec3 la;
                        vec3 ld;
                        vec3 ls;
                    };
                    struct PointLight{
                        vec4 position;

                        vec3 la;
                        vec3 ld;
                        vec3 ls;
                    };
                    struct SpotLight{
                        vec4 position;
                        vec4 direction;

                        vec3 la;
                        vec3 ld;
                        vec3 ls;

                        float cut_off;
                        float fall_off;
                    };

                    uniform DirectLight u_dir_light;
                    uniform PointLight u_point_lights[${NUM_POINT_LIGHTS}];
                    uniform Spotlight u_spot_lights[${NUM_SPOTLIGHTS}];
                    uniform Material u_material; 

                    in vec3 v_normal;
                    in vec3 v_position;
                    
                    out vec4 glColor;
                    
                    vec3 calDirLight(DirectLight light, vec3 N){
                        vec3 L = normalize(-light.direction.xyz);
            
                        float cos_angle = max(dot(N, L), 0.0);
                        vec3 contribution = vec3(u_material.ka * light.la + 
                                                 u_material.kd * light.ld * cos_angle);
                            
                        if(cos_angle > 0.0){
                            float specular = 0.0;
                            vec3 R = reflect(-L,N);
                            vec3 V = normalize(-v_position);
                            float spec_angle = max(dot(R,V),0.0);
                            specular = pow(spec_angle, u_material.shininess);
                        
                            contribution += (u_material.ks * light.ls * specular);
                        }    
                        
                        return contribution;
                    }
                    vec3 calcPointLight(PontLight light, vec3 N){
                        vec3 L = normalize(light.position.xyz - v_position);
                        
                        float cos_angle = max(dot(N, L), 0.0);
                        float specular = 0.0;

                        if(cos_angle > 0.0){
                            vec3 R = reflect(-L,N);
                            vec3 V = normalize(-v_position);
                            float spec_angle = max(dot(R,V),0.0);
                            specular = pow(spec_angle, u_material.shininess);
                        }
                        float d = length(u_light.position.xyz - v_position);
                        float attenuation = 1.0/(0.009*3.14*3.14 *d*d);

                        vec3 contribution = vec3(u_material.ka * u_light.la + 
                                                 attenuation * (u_material.kd * u_light.ld * cos_angle +
                                                                u_material.ks * u_light.ls * specular));
                        return contribution;
                    }
                    vec3 calcSpotLight(Spotlight light, vec3 N){
                        vec3 L = normalize(light.position.xyz - v_position);
            
                        float cos_angle = max(dot(N, L), 0.0);
                        float specular = 0.0;
                        float spot_factor = 0.0;

                        if(cos_angle > 0.0){
                            vec3 R = reflect(-L,N);
                            vec3 V = normalize(-v_position);
                            float spec_angle = max(dot(R,V),0.0);
                            specular = pow(spec_angle, u_material.shininess);
                            vec3 D = normalize(light.direction.xyz);
                            float spot_angle = max(dot(D,-L), 0.0);
                            if(spot_angle > light.cut_off)
                                spot_factor = pow(spot_angle, light.fall_off);
                        }

                        float attenuation = 0.0;
                        if(spot_factor > 0.0){
                            float d = length(light.position.xyz - v_position);
                            attenuation = spot_factor/(0.009*3.14*3.14 *d*d);
                        }
                        vec3 contribution = vec3(u_material.ka * light.la + 
                                                attenuation * (u_material.kd * light.ld * cos_angle +
                                                               u_material.ks * light.ls * specular));
                        return contribution;
                    }
                    void main(){
                        vec3 N = normalize(v_normal);
                        
                        vec3 contribution = calDirLight(u_dir_light, N);
                        
                        for(int i = 0; i < ${NUM_POINT_LIGHTS}; i++){
                            contribution += calcPointLight(u_point_lights[i]);
                        }
                        for(int i = 0; i < ${NUM_SPOTLIGHTS}; i++){
                            contribution += calcSpotLight(u_spot_lights[i], N);
                        }
                        glColor = vec4(contribution, 1.0);
                    }`; 

        if (WebGL.programs["MultipleL"])
            this.program = WebGL.programs["MultipleL"];
        else
            this.program = WebGL.createProgram(gl, vsh, fsh, "MultipleL");
            
        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);
        
        let attributes = {  position: { numComponents: 3, 
                                                 data: this.vertices},
                              normal: { numComponents: 3, 
                                                 data: this.normals}
                         };
        
        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.uniforms = Object.assign({}, initialUniforms);
    }
}