import java.awt.*;
import java.util.*;

class Surface {
    public double ir, ig, ib;        // surface's intrinsic color
    public double ka, kd, ks, ns;    // constants for phong model
    public double kt, kr, nt;
    private static final double TINY = 0.001;
    private static final double I255 = 0.00392156;  // 1/255

    public Surface(double rval, double gval, double bval, double a, double d, double s, double n, double r, double t, double index) {
        ir = rval; ig = gval; ib = bval;
        ka = a; kd = d; ks = s; ns = n;
        kr = r; kt = t; nt = index;
    }

    public Color Shade(Vector3D p, Vector3D n, Vector3D v, Vector lights, Vector objects, Color bgnd, boolean isInside, double factor)
	{
		// To avoid infinite loop of refraction
		if (factor < I255)
			return new Color((float)0, (float)0, (float)0);

        Enumeration lightSources = lights.elements();

        double r = 0;
        double g = 0;
        double b = 0;
        while (lightSources.hasMoreElements())
		{
            Light light = (Light) lightSources.nextElement();
            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, factor);
                if (shadowRay.blocked(objects))
                    break;

                double lambert = Vector3D.dot(n,l);

				if (lambert > 0)
				{
				//	System.out.println("Lambert = "+lambert);
                    
					if (kd > 0) {
                        double diffuse = kd*lambert;
                        r += diffuse*ir*light.ir;
                        g += diffuse*ig*light.ig;
                        b += diffuse*ib*light.ib;
                    }
                    if (ks > 0) {
                        lambert *= 2;
                        double spec = v.dot(lambert*n.x - l.x, lambert*n.y - l.y, lambert*n.z - l.z);
                        if (spec > 0) {
                            spec = ks*Math.pow(spec, ns);
                            r += spec*light.ir;
                            g += spec*light.ig;
                            b += spec*light.ib;
                        }
                    }
                }
            }
        }

        // Compute illumination due to reflection
        if (kr > 0)
		{
            double 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, factor*kr);
				double f = kr*I255;
                if (reflectedRay.trace(objects)) 
				{
                    Color rcolor = reflectedRay.Shade(lights, objects, bgnd);
                    r += f*rcolor.getRed();
                    g += f*rcolor.getGreen();
                    b += f*rcolor.getBlue();
                }
				else
				{
                    r += f*bgnd.getRed();
                    g += f*bgnd.getGreen();
                    b += f*bgnd.getBlue();
                }
            }
        }

        // Compute illumination due to refraction
        if (kt > 0) 
		{
			double nt2 = nt;
			// if inside, flip nt2
			if (isInside)
				nt2 = 1/nt;

			// cosine of internal angle is v dot n
            double cosi = v.dot(n);

            if (cosi > 0)
			{
				double t = 1 - nt2*nt2*(1 - cosi*cosi);

				if (t>=0)
				{
					
					double cosr = Math.sqrt(t);
					t = cosr - nt2*cosi;

					Vector3D refract = new Vector3D(-t*n.x - nt2*v.x, -t*n.y - nt2*v.y, -t*n.z - nt2*v.z);
					Vector3D poffset = new Vector3D(p.x + TINY*refract.x, p.y + TINY*refract.y, p.z + TINY*refract.z);
					Ray refractedRay = new Ray(poffset, refract, factor*kt);
					double f = kt*I255;
					if (refractedRay.trace(objects)) 
					{
						Color rcolor = refractedRay.Shade(lights, objects, bgnd);
						r += f*rcolor.getRed();
						g += f*rcolor.getGreen();
						b += f*rcolor.getBlue();
					}
					else
					{
						r += f*bgnd.getRed();
						g += f*bgnd.getGreen();
						b += f*bgnd.getBlue();
					}
				}
				else
				{
					System.out.println("During refraction, negative value inside square root.");

				}
			}
        }

        r = (r > 1.0) ? 1.0 : r;
        g = (g > 1.0) ? 1.0 : g;
        b = (b > 1.0) ? 1.0 : b;
        return new Color((float)r, (float)g, (float)b);
    }
}