import java.awt.*;
import java.util.*;
import Light;
import Ray;

public class Surface {

  //state variables
  private static Color bgnd;
  private static Vector lights;
  private static final float TINY=(float) 0.001;
  private static final float I255=(float) 1/255;

  //temporary variables
  private static Color rcolor;
  private static Enumeration lightSources;
  private static Light light;
  private static Vector3D l=new Vector3D();
  private static Vector3D soffset=new Vector3D();
  private static Ray shadowRay=new Ray(soffset, l);
  private static float diffuse, lambert, nr, nvn, spec, t;

  //instance variables
  private float ir, ig, ib;                  // surface's intrinsic color
  private float ka, kd, ks, ns;              // constants for phong model
  private float kt, kr, nt;

  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*I255;
    nt=index;
  }

  public static final void setVars(Vector lightList, Color background) {

    lights=lightList;
    bgnd=background;
  }

  public final Color Shade(Vector3D p, Vector3D n, Vector3D v) {

    lightSources=lights.elements();

    float r=0;
    float g=0;
    float b=0;
    while (lightSources.hasMoreElements()) {
      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 {
	if (light.lightType == Light.POINT) {
	  l.x=light.lvec.x-p.x;
	  l.y=light.lvec.y-p.y;
	  l.z=light.lvec.z-p.z;
	  l.normalize();
	}
	else {
	  l.x=-light.lvec.x;
	  l.y=-light.lvec.y;
	  l.z=-light.lvec.z;
	}

	// Check if the surface point is in shadow
	soffset.x=p.x+TINY*l.x;
	soffset.y=p.y+TINY*l.y;
	soffset.z=p.z+TINY*l.z;
	if (shadowRay.trace())
	  break;

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

	    if (spec>0) {
	      spec=ks*((float) Math.pow(spec, ns));
	      r+=spec*light.ir;
	      g+=spec*light.ig;
	      b+=spec*light.ib;
	    }
	  }
	}
      }
    }

    // Compute illumination due to reflection
    if (kr>0||kt>0) {
      float vn=v.dot(n);
      if (kr>0) {
	if (vn<0) {
	  t=vn*2;
	  Vector3D reflect=new Vector3D(v.x-t*n.x, v.y-t*n.y, v.z-t*n.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);
	  if (reflectedRay.trace()) {
	    rcolor=reflectedRay.object.Shade(reflectedRay);
	    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();
	  }
	}
      }

      // Compute illumination due to refraction
      if (kt>0) {
	Vector3D refract;
	if (vn>0) {
	  if (nr!=1) { 
	    nr=(float) 1/nt;
	    nvn=nr*vn;
	    t=(float) Math.sqrt(1-nr*nr+nvn*nvn)-nvn;
	    refract=new Vector3D(t*n.x+nr*v.x, t*n.y+nr*v.y, t*n.z+nr*v.z);
	  }
	  else
	    refract=new Vector3D(v);
	}
	else {
	  if (nr!=1) {
	    nvn=nt*vn;
	    t=(float) Math.sqrt(1-nt*nt+nvn*nvn)+nvn;
	    refract=new Vector3D(-t*n.x+nt*v.x, -t*n.y+nt*v.y, -t*n.z+nt*v.z);
	  }
	  else
	    refract=new Vector3D(v);
	}
	
	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);
	if (refractedRay.trace()) {
	  rcolor=refractedRay.object.Shade(refractedRay);
	  r+=kt*rcolor.getRed();
	  g+=kt*rcolor.getGreen();
	  b+=kt*rcolor.getBlue();
	}
	else {
	  r+=kt*bgnd.getRed();
	  g+=kt*bgnd.getGreen();
	  b+=kt*bgnd.getBlue();
	}
      }
    }

    if (r>1)
      r=1;
    if (g>1)
      g=1;
    if (b>1)
      b=1;
    return new Color(r, g, b);
  }
}
