import java.applet.*;
import java.awt.*;
import java.io.*;
import java.net.*;
import java.util.*;
import Renderable;
import Vector3D;
import Ray;
import Surface;
import Sphere;
import RayTrace;

/***************************************************
*
*   An instructional Ray-Tracing Renderer written
*   for MIT 6.837  Fall '98 by Leonard McMillan.
*   Modified by Tomas Lozano-Perez for Fall '01
*
****************************************************/

//    The following Applet demonstrates a simple ray tracer

public class RayTraceApplet extends Applet implements Runnable {
  final static int CHUNKSIZE = 100;
  Image screen;
  Graphics gc;
  Vector objectList;
  Vector lightList;
  Surface currentSurface;

  Vector3D eye, lookat, up;
  float fov;

  Color background;
  int width, height;

  RayTrace RT;

  public void init( ) {
    // initialize the off-screen rendering buffer
    width = size().width;
    height = size().height;
    screen = createImage(width, height);
    gc = screen.getGraphics();
    gc.setColor(getBackground());
    gc.fillRect(0, 0, width, height);

    fov = 30;               // default horizonal field of view

    // Initialize various lists
    objectList = new Vector(CHUNKSIZE, CHUNKSIZE);
    lightList = new Vector(CHUNKSIZE, CHUNKSIZE);
    currentSurface = new Surface(0.8f, 0.2f, 0.9f, 0.2f, 0.4f, 0.4f, 10.0f, 0f, 0f, 1f);

    // Parse the scene file
    String filename = getParameter("datafile");
    showStatus("Parsing " + filename);
    InputStream is = null;
    try {
      is = new URL(getDocumentBase(), filename).openStream();
      ReadInput(is);
      is.close();
    } catch (IOException e) {
      showStatus("Error reading "+filename);
      System.err.println("Error reading "+filename);
      System.exit(-1);
    }

    // Initialize more defaults if they weren't specified
    if (eye == null) eye = new Vector3D(0, 0, 10);
    if (lookat == null) lookat = new Vector3D(0, 0, 0);
    if (up  == null) up = new Vector3D(0, 1, 0);
    if (background == null) background = new Color(0,0,0);

    RT = new RayTrace(objectList, lightList, screen);
    RT.setBackground(background);
    RT.setView(eye, lookat, up, fov);
  }


  double getNumber(StreamTokenizer st) throws IOException {
    if (st.nextToken() != StreamTokenizer.TT_NUMBER) {
      System.err.println("ERROR: number expected in line "+st.lineno());
      throw new IOException(st.toString());
    }
    return st.nval;
  }

  void ReadInput(InputStream is) throws IOException {
    StreamTokenizer st = new StreamTokenizer(is);
    st.commentChar('#');
  scan: while (true) {
    switch (st.nextToken()) {
    default:
      break scan;
    case StreamTokenizer.TT_WORD:
      if (st.sval.equals("sphere")) {
	System.out.println("sphere");
	objectList.addElement(new Sphere(currentSurface, st));
      } else
	if (st.sval.equals("triangles")) {
	  System.out.println("triangles");
	  objectList.addElement(new TriangleMesh(currentSurface, st));
	} else
	  if (st.sval.equals("eye")) {
	    System.out.println("eye");
	    eye = new Vector3D((float) getNumber(st), (float) getNumber(st), (float) getNumber(st));
	  } else
	    if (st.sval.equals("lookat")) {
	      System.out.println("lookat");
	      lookat = new Vector3D((float) getNumber(st), (float) getNumber(st), (float) getNumber(st));
	    } else
	      if (st.sval.equals("up")) {
		System.out.println("up");
		up = new Vector3D((float) getNumber(st), (float) getNumber(st), (float) getNumber(st));
	      } else
		if (st.sval.equals("fov")) {
		  System.out.println("fov");
		  fov = (float) getNumber(st);
		} else
		  if (st.sval.equals("background")) {
		    System.out.println("background");
		    background = new Color((float) getNumber(st), (float) getNumber(st), (float) getNumber(st));
		  } else
		    if (st.sval.equals("light")) {
		      System.out.println("light");
		      float r = (float) getNumber(st);
		      float g = (float) getNumber(st);
		      float b = (float) getNumber(st);
		      if (st.nextToken() != StreamTokenizer.TT_WORD) {
			System.err.println("ERROR: in line "+st.lineno()+" at "+st.sval);
			throw new IOException(st.toString());
		      }
		      if (st.sval.equals("ambient")) {
			System.out.println("ambient");
			lightList.addElement(new Light(Light.AMBIENT, null, r, g, b));
		      } else
			if (st.sval.equals("directional")) {
			  System.out.println("directional");
			  Vector3D v = new Vector3D((float) getNumber(st), (float) getNumber(st), (float) getNumber(st));
			  lightList.addElement(new Light(Light.DIRECTIONAL, v, r, g, b));
			} else
			  if (st.sval.equals("point")) {
			    System.out.println("point");
			    Vector3D v = new Vector3D((float) getNumber(st), (float) getNumber(st), (float) getNumber(st));
			    lightList.addElement(new Light(Light.POINT, v, r, g, b));
			  } else {
			    System.err.println("ERROR: in line "+st.lineno()+" at "+st.sval);
			    throw new IOException(st.toString());
			  }
		    } else
		      if (st.sval.equals("surface")) {
			System.out.println("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 kr = (float) getNumber(st);
			float kt = (float) getNumber(st);
			float index = (float) getNumber(st);
			currentSurface = new Surface(r, g, b, ka, kd, ks, ns, kr, kt, index);
		      }
      break;
    }
  }
    is.close();
    if (st.ttype != StreamTokenizer.TT_EOF) {
      System.err.println("ERROR: in line "+st.lineno()+" at "+st.sval);
      throw new IOException(st.toString());
    }
  }
    
  boolean finished = false;
    
  public void paint(Graphics g) {
    if (finished)
      g.drawImage(RT.getScreen(), 0, 0, this);
  }

  // this overide avoid the unnecessary clear on each paint()
  public void update(Graphics g) {
    paint(g);
  }


  Thread raytracer;

  public void start() {
    if (raytracer == null) {
      raytracer = new Thread(this);
      raytracer.start();
    } else {
      raytracer.resume();
    }
  }

  public void stop() {
    if (raytracer != null) {
      raytracer.suspend();
    }
  }

  public void run() {
    Graphics g = getGraphics();
    long time = System.currentTimeMillis();
    for (int j = 0; j < height; j++) {
      showStatus("Scanline "+j);
      RT.renderScanLine(j);
      g.drawImage(RT.getScreen(), 0, 0, this);        // doing this less often speed things up a bit
    }
    g.drawImage(RT.getScreen(), 0, 0, this);
    time = System.currentTimeMillis() - time;
    showStatus("Rendered in "+(time/60000)+":"+((time%60000)*0.001));
    finished = true;
  }

}
