import java.io.*;
import java.awt.*;
import java.util.*;
import Vector3D;
import Ray;
import Light;

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*I255;  // These are used to multiply by colors in [0, 255]
    nt = index;
  }

  //   1. the point of intersection (p)
  //   2. a unit-length surface normal (n)
  //   3. a unit-length vector towards the ray's origin (v)
  public Color Shade(Vector3D p, Vector3D n, Vector3D v, Vector lights, 
                     Vector objects, Color bgnd) {
    Enumeration lightSources = lights.elements();

    float r = 0;
    float g = 0;
    float 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);
        if (shadowRay.trace(objects))
          continue;

        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);
        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

    r = (r > 1f) ? 1f : r;
    g = (g > 1f) ? 1f : g;
    b = (b > 1f) ? 1f : b;
    return new Color(r, g, b);
  }
}
