import java.io.*;
import java.awt.*;
import java.util.*;
import Vector3D;
import Surface;
import Ray;
import Triangle;

// A "Renderable" Triangle Mesh
// This just holds a bunch of triangles, which are the actual objects rendered.
// The Ray.Object will never be a mesh but rather one of the triangles.
class TriangleMesh implements Renderable {
  final static int CHUNKSIZE = 100;

  Vector3D vertList[];
  Triangle triList[];
  Surface surfList[];
  int vertices, triangles, surfaces = 0;

  public TriangleMesh (Surface currentSurface, StreamTokenizer st) throws IOException {

    vertList = new Vector3D[CHUNKSIZE];
    vertices = 0;
    triList = new Triangle[CHUNKSIZE];
    triangles = 0;
    surfList = new Surface[CHUNKSIZE];
    surfaces = 0;

  scan: while (true) {
    switch (st.nextToken()) {
    default:
      break scan;
    case StreamTokenizer.TT_WORD:
      if (st.sval.equals("v")) {
	float x = (float) getNumber(st);
	float y = (float) getNumber(st);
	float z = (float) getNumber(st);
	if (vertices == vertList.length) growVertList();
	vertList[vertices++] = new Vector3D(x, y, z);
      } else
	if (st.sval.equals("f")) {
	  int v0 = (int) getNumber(st);
	  int v1 = (int) getNumber(st);
	  while (st.nextToken() == StreamTokenizer.TT_NUMBER) {
	    st.pushBack();
	    int v2 = (int) getNumber(st);
	    if (v2 == v0) continue;
	    if (triangles == triList.length) growTriList();
	    triList[triangles] = new Triangle(vertList[v0], vertList[v1], vertList[v2]);
	    triList[triangles].setSurface(currentSurface);
	    v1 = v2;
	    triangles += 1;
	  }
	  st.pushBack();
	} else
	  if (st.sval.equals("surface")) {
	    float r = (float) getNumber(st);
	    float g = (float) getNumber(st);
	    float b = (float) getNumber(st);
	    float ka = (float) getNumber(st);
	    float kd = (float) getNumber(st);
	    float ks = (float) getNumber(st);
	    float ns = (float) getNumber(st);
	    float kt = (float) getNumber(st);
	    float kr = (float) getNumber(st);
	    float index = (float) getNumber(st);
	    if (surfaces == surfList.length) growSurfList();
	    surfList[surfaces++] =
	      currentSurface = new Surface(r, g, b, ka, kd, ks, ns, kt, kr, index);
	  } 
	  else 
	    if (st.sval.equals("end"))
	      break scan;
	    else {
	      System.err.println("ERROR: line "+st.lineno()+": unexpected token :"+st.sval);
	      break scan;
	    }
      break;
    }
  }
  }

  public boolean intersect(Ray ray) {
    int hit = 0;
    // Just call intersect for each of the triangles.
    for(int i = 0; i < triangles; i++) {
      if (triList[i].intersect(ray)) hit++;
    }
    return (hit > 0);
  }

  public Color Shade(Ray ray, Vector lights, Vector objects, Color bgnd) {
    // This should not really happen, but just in case.
    return ray.object.Shade(ray, lights, objects, bgnd);
  }

  public String toString() {
    return ("triangle mesh "+triangles+" triangles "+vertices+" vertices "+surfaces+" surfaces");
  }

  private void growTriList() {
    Triangle newList[] = new Triangle[triList.length+CHUNKSIZE];
    System.arraycopy(triList, 0, newList, 0, triList.length);
    triList = newList;
  }

  private void growVertList() {
    Vector3D newList[] = new Vector3D[vertList.length+CHUNKSIZE];
    System.arraycopy(vertList, 0, newList, 0, vertList.length);
    vertList = newList;
  }

  private void growSurfList() {
    Surface newList[] = new Surface[surfList.length+CHUNKSIZE];
    System.arraycopy(surfList, 0, newList, 0, surfList.length);
    surfList = newList;
  }

  private double getNumber(StreamTokenizer st) throws IOException {
    if (st.nextToken() != StreamTokenizer.TT_NUMBER) {
      System.err.println("ERROR: line "+st.lineno()+": expected number");
      throw new IOException(st.toString());
    }
    return st.nval;
  }
}
