package RayTracer;

import java.awt.Color;
import java.util.Enumeration;
import java.util.Vector;

public class Surface {
    public float ir, ig, ib;        // surface's intrinsic color
    public float ka, kd, ks, ns;    // constants for phong model
    public float kt, kr, nt;
    private static final float TINY = 0.001f;
    private static final float I255 = 0.00392156f;  // 1/255

    public Surface(float rval, float gval, float bval, float a, float d, float s, float n, float r, float t, float index) {
        ir = rval; ig = gval; ib = bval;
        ka = a; kd = d; ks = s; ns = n;
        kr = r*I255; kt = t; nt = index;
    }

	public Color Shade(Vector3D p, Vector3D n, Vector3D v, Vector lights, Vector objects, Color bgnd, float illuminationIntensity) {
		Enumeration lightSources = lights.elements();

		float r = 0;
		float g = 0;
		float b = 0;
		while (lightSources.hasMoreElements()) {
			Light light = (Light) lightSources.nextElement();
			Vector3D l;
			Vector3D lvec = null;
			
			switch (light.getLightType()) {
				case Light.AMBIENT:
					r += ka*ir*light.getRedIntensity();
					g += ka*ig*light.getGreenIntensity();
					b += ka*ib*light.getBlueIntensity();
					
					break;
				case Light.POINT:
					lvec = light.getLightDirection();
					/*
					l = new Vector3D(lvec.x - p.x, lvec.y - p.y, lvec.z - p.z);
					l.normalize();
					*/
				case Light.DIRECTIONAL:
					if (lvec == null) {
						lvec = light.getLightDirection();
						l = new Vector3D(-lvec.x, -lvec.y, -lvec.z);
					} else {
						l = new Vector3D(lvec.x - p.x, lvec.y - p.y, lvec.z - p.z);
						l.normalize();
					}
					
					// Check if the surface point is in shadow
					Vector3D poffset = new Vector3D(p.x + TINY*l.x, p.y + TINY*l.y, p.z + TINY*l.z);
					Ray shadowRay = new Ray(poffset, l);
					if (shadowRay.trace(objects))
						break;

					float lambert = Vector3D.dot(n,l);
					if (lambert > 0) {
						if (kd > 0) {
							float diffuse = kd*lambert;
							r += diffuse*ir*light.getRedIntensity();
							g += diffuse*ig*light.getGreenIntensity();
							b += diffuse*ib*light.getBlueIntensity();
						}
						if (ks > 0) {
							lambert *= 2;
							float spec = v.dot(lambert*n.x - l.x, lambert*n.y - l.y, lambert*n.z - l.z);
							if (spec > 0) {
								spec = ks*((float) Math.pow((double) spec, (double) ns));
								r += spec*light.getRedIntensity();
								g += spec*light.getGreenIntensity();
								b += spec*light.getBlueIntensity();
							}
						}
					}
					
					break;
			}
			
			/*
			if (light.lightType == Light.AMBIENT) {
				r += ka*ir*light.ir;
				g += ka*ig*light.ig;
				b += ka*ib*light.ib;
			} else {
				Vector3D l;
				if (light.lightType == Light.POINT) {
					l = new Vector3D(light.lvec.x - p.x, light.lvec.y - p.y, light.lvec.z - p.z);
					l.normalize();
				} else {
					l = new Vector3D(-light.lvec.x, -light.lvec.y, -light.lvec.z);
				}

				// Check if the surface point is in shadow
				Vector3D poffset = new Vector3D(p.x + TINY*l.x, p.y + TINY*l.y, p.z + TINY*l.z);
				Ray shadowRay = new Ray(poffset, l);
				if (shadowRay.trace(objects))
					break;

				float lambert = Vector3D.dot(n,l);
				if (lambert > 0) {
					if (kd > 0) {
						float diffuse = kd*lambert;
						r += diffuse*ir*light.ir;
						g += diffuse*ig*light.ig;
						b += diffuse*ib*light.ib;
					}
					if (ks > 0) {
						lambert *= 2;
						float spec = v.dot(lambert*n.x - l.x, lambert*n.y - l.y, lambert*n.z - l.z);
						if (spec > 0) {
							spec = ks*((float) Math.pow((double) spec, (double) ns));
							r += spec*light.ir;
							g += spec*light.ig;
							b += spec*light.ib;
						}
					}
				}
			}
			*/
		}

        // Compute illumination due to reflection
        if (kr > 0) {
            float t = v.dot(n);
            if (t > 0) {
                t *= 2;
                Vector3D reflect = new Vector3D(t*n.x - v.x, t*n.y - v.y, t*n.z - v.z);
                Vector3D poffset = new Vector3D(p.x + TINY*reflect.x, p.y + TINY*reflect.y, p.z + TINY*reflect.z);
                Ray reflectedRay = new Ray(poffset, reflect);
				reflectedRay.illuminosityIntensity = illuminationIntensity;

				if (reflectedRay.illuminosityIntensity > I255) {
					reflectedRay.illuminosityIntensity *= kr;
	
					if (reflectedRay.trace(objects)) {
					    Color rcolor = reflectedRay.Shade(lights, objects, bgnd);
					    r += kr*rcolor.getRed();
					    g += kr*rcolor.getGreen();
					    b += kr*rcolor.getBlue();
					} else {
					    r += kr*bgnd.getRed();
					    g += kr*bgnd.getGreen();
					    b += kr*bgnd.getBlue();
					}
				}
            }
        }

        // Add code for refraction here
		
		
		if (kt > 0) {
			//refraction exists no matter what direction the surface is facing because it's transparent!
			//n, v are normalized
			//n faces outward.
			//v faces toward the ray-origin
			
			boolean entering = true;
			float indexRatio = nt;
			float cosThetaI = v.dot(n);
			
			//figure out whether ray is entering or exiting
			//DONE: just figure out whether dotting with the surface is positive or negative
			entering = (cosThetaI < 0);
						
			if (!entering)
				indexRatio = 1 / nt;
			
			Vector3D uComponent = new Vector3D(indexRatio * v.x, indexRatio * v.y, indexRatio * v.z);
			float cosThetaR = (float)Math.sqrt((double)(1f - indexRatio * indexRatio * (1f - cosThetaI * cosThetaI)));
			float nConst = cosThetaR - indexRatio * cosThetaI;
			Vector3D nComponent = new Vector3D(nConst * n.x, nConst * n.y, nConst * n.z);
			
			Vector3D trasmit = uComponent.sub(nComponent);	//I added sub and add to Vector3D
			
			Vector3D poffset = new Vector3D(p.x + TINY*trasmit.x, p.y + TINY*trasmit.y, p.z + TINY*trasmit.z);
			Ray transmittedRay = new Ray(poffset, trasmit);	//needs to just shift...
			transmittedRay.illuminosityIntensity = illuminationIntensity;
						
			if (transmittedRay.illuminosityIntensity > I255) {
				transmittedRay.illuminosityIntensity *= kt;	
	
				if (transmittedRay.trace(objects)) {
					Color tColor = transmittedRay.Shade(lights, objects, bgnd);	
					//When do I stop?
					//DONE: have steadily decreasing light RAY intensity
					r += kt * tColor.getRed();
					g += kt * tColor.getGreen();
					b += kt * tColor.getBlue();
				} else {
					r += kt * bgnd.getRed();
					g += kt * bgnd.getGreen();
					b += kt * bgnd.getBlue();
				}
			}				
		}
		
		//make sure "over exposed" intensities top out at max
        r = (r > 1f) ? 1f : r;
        g = (g > 1f) ? 1f : g;
        b = (b > 1f) ? 1f : b;
        return new Color(r, g, b);
    }
}
