import java.lang.*;
import java.io.*;

public class Scene {
    public Colour Ambient; 

    private int numberObjects;
    private int numberLights;
    private Object[] sceneObjects;
    private Light[] sceneLights;

    // Maximum reflection depth we will trace to 
    private static int MaxDepth=2; 

    // Temporary static variables for lighting phase
    // this saves some overhead in memory mangement
    private Vector n;
    private Vector h;
    private Colour kd;		// temporary variable for diffuse
    private Colour ks;		// temporary variable for specular
    
    public Scene() {
	numberObjects = 0;
	sceneObjects = null;

	numberLights = 0;
	sceneLights = null;
	
	// Default ambient light
  	Ambient = new Colour (0.5,0.5,0.5);

	// Initialise static variables
	n = new Vector();
	h = new Vector();
	kd = new Colour();
	ks = new Colour();
    }

    // Note this deletes original scene
    public void setNumberObjects(int n) {
	numberObjects = n;
	sceneObjects = new Object[n];
    }

    public int getNumberObjects() {
	return numberObjects;
    }

    public void setObject(int i, Object o) {
	sceneObjects[i]=o;
    }


    public void setNumberLights(int n) {
	numberLights = n;
	sceneLights = new Light[n];
    }

    public int getNumberLights() {
	return numberLights;
    }

    public void setLight(int i, Light o) {
	sceneLights[i]=o;
    }

    public boolean intersect(Ray ray, Colour colour, int depth) {
	double tmin = java.lang.Double.MAX_VALUE;
	double t;
	int hitObject=-1;

	t = tmin;
	for (int i=0;i<numberObjects;i++) {
	    t = sceneObjects[i].intersect(ray);
	    if(t > 0.0) {
		//intersection found
		if(t < tmin){
		    tmin = t;
		    hitObject = i;
		}
	    }
	}

	if (hitObject > -1) {
	    /* We have an intersection so calculate lighting */
	    Point p = ray.getPointAt(tmin);
	    calculateColour(colour, sceneObjects[hitObject], p, ray, depth);
	    colour.clamp();
	    return true;
	}
	else {
	    return false;
	}
    }
    
    private void calculateColour(Colour colour, Object object, Point p, 
				 Ray ray, int depth) {
	Light light;		// temporary reference to current light
	Vector direction = new Vector();	// ray indicating direction to light

	/* Ambient component */
	colour.copy(object.SurfaceMaterial.Ambient);
	colour.mult(Ambient);

	/* Object normal at the intersection point */
	n.copy(object.normal(p));
	
	
	/* For each light in the scene */
	
	/* Hint: the "nextlight" label can be used to jump back to the
           start if the point is in shadow from a particular light */
	
	nextlight: for (int i=0;i<numberLights;i++) { 
	  light = sceneLights[i];
	  
	  /* Find the direction to the light from the point */
	  
	  if (light instanceof DirectionalLight) {
	    direction.copy(((DirectionalLight)light).Direction); 
	    direction.negate();
	  } else {
	    Vector.subtract(direction, 
			    ((PointLight)light).Origin, p);
	  }

	  
	  double nl = Vector.dot(n,direction);
	  
	  /* does the light illuminate this point? */
	  if (nl > 0.0) {
	    double pnh;

	    direction.normalise();

	    /* Your code here (1) - is the light occluded by another
               object ?*/

	    Vector.subtract(h,ray.Origin,p);
	    h.normalise();
	    Vector.add(h,h,direction);
	    h.normalise();
	    double nh = Vector.dot(n,h);
	    
	    kd.copy(object.SurfaceMaterial.Diffuse);
	    ks.copy(object.SurfaceMaterial.Specular);
	    

	    if (object.SurfaceMaterial.Shininess < 1) {
	      pnh = 0.0;
	    } else {
	      pnh = Math.pow(nh, object.SurfaceMaterial.Shininess);
	    }
	    
	    kd.scale(nl);
	    ks.scale(pnh);
	    kd.add(ks);
	    kd.mult(light.Intensity);
	    colour.add(kd);
	  }
	}

	/* Your code here (2) - do a recursive ray trace. Use specular
           colour to modulate the colour returned by the recursive
           intersect call */

	colour.clamp();
    }

    public void read(SceneReader is) 
	throws Exception {

	/*
	 * read the objects
	 */

	numberObjects = is.readInt();
	System.out.println("Scene has "+ numberObjects+ " objects");
	setNumberObjects(numberObjects);
		
	for(int i=0; i<numberObjects; ++i){
	    int objectcode = -1;

	    try {
		objectcode = is.readInt();
	    } catch (Exception e) {
		System.out.println("no ocode");
	    }

	    Object tmp;
	    
	    /*I don't see any way around this*/
	    switch(objectcode){
	    case 0 : 
		tmp = new Sphere();
		try {
		    tmp.read(is);
		} catch (IOException e) {
		    throw new Exception("IO error in read sphere\n" 
					+ e.getMessage());
		} catch (NumberFormatException e) {
		    throw new Exception("Number format error in read sphere\n"
					+ e.getMessage());
		} 
		setObject(i,tmp);
		break;
	    case 1 : 
		tmp = new Plane(); 
		try {
		    tmp.read(is);
		} catch (IOException e) {
		    throw new Exception("IO error in read Plane\n" 
					+ e.getMessage());
		} catch (NumberFormatException e) {
		    throw new Exception("Number format error in read Plane\n"
					+ e.getMessage());
		} 
		setObject(i,tmp);
		break; 
		case 2 : 
		tmp = new IndexedFaceSet(); 
		try {
		    tmp.read(is);
		} catch (IOException e) {
		    throw new Exception("IO error in read IndexedFace\n" 
					+ e.getMessage());
		} catch (NumberFormatException e) {
		    throw new Exception("Number format error in read IndexedFace\n"
					+ e.getMessage());
		} 
		setObject(i,tmp);
		break; 
	    default:
		break;
	    }
	}

	/*
	 * read the ambient light
	 */

	try {
	    Ambient.read(is);
	} catch (IOException e) {
	    throw new Exception("IO error in read ambient\n" 
				+ e.getMessage());
	} catch (NumberFormatException e) {
	    throw new Exception("Number format error in read ambient\n"
				+ e.getMessage());
	} 
	
	/*
	 * read the directional and point light
	 */

	numberLights = is.readInt();
	System.out.println("Scene has "+ numberLights+ " lights");
	setNumberLights(numberLights);

	for(int i=0; i<numberLights; ++i){
	    int objectcode = -1;

	    try {
		objectcode = is.readInt();
	    } catch (Exception e) {
		System.out.println("no ocode");
	    }

	    Light tmp;
	    
	    /*I don't see any way around this*/
	    switch(objectcode){
	    case 0 : 
		tmp = new PointLight();
		try {
		    tmp.read(is);
		} catch (IOException e) {
		    throw new Exception("IO error in read pointlight\n" 
					+ e.getMessage());
		} catch (NumberFormatException e) {
		    throw new Exception("Number format error in read directionalight\n"
					+ e.getMessage());
		} 
		setLight(i,tmp);
		break;

	    case 1 : 
		tmp = new DirectionalLight();
		try {
		    tmp.read(is);
		} catch (IOException e) {
		    throw new Exception("IO error in read directionallight\n" 
					+ e.getMessage());
		} catch (NumberFormatException e) {
		    throw new Exception("Number format error in read directionalight\n"
					+ e.getMessage());
		} 
		setLight(i,tmp);
		break;
	    }
	}
	System.out.println("Scene read");
    }

    public void write(SceneWriter os) throws IOException {
	os.writeInt(numberObjects);
	os.writeString("\n");
	for(int i=0; i<numberObjects; ++i){
	    if (sceneObjects[i] instanceof Sphere) {
		os.writeInt(0);
	    } else if (sceneObjects[i] instanceof Plane) {
		os.writeInt(1);
	    } else {
		os.writeInt(2);
	    }
	    os.writeChar(' ');
	    sceneObjects[i].write(os);
	    os.writeString("\n");
	}

	Ambient.write(os);
	os.writeInt(numberLights);
	os.writeString("\n");
	for(int i=0; i<numberLights; ++i){
	    if (sceneLights[i] instanceof PointLight) {
		os.writeInt(0);
	    } else {
		os.writeInt(1);
	    }
	    os.writeChar(' ');
	    sceneLights[i].write(os);
	    os.writeString("\n");
	}
    }

    public void print(SceneWriter os) throws IOException {
	os.writeString("Number of objects " + numberObjects + "\n");
	for(int i=0; i<numberObjects; ++i){
	    sceneObjects[i].print(os);
	}
	os.writeString("Ambient light is ");
	Ambient.write(os);
	os.writeString("\n");

	os.writeString("Number of lights " + numberLights + "\n");
	for(int i=0; i<numberLights; ++i){
	    sceneLights[i].print(os);
	}
    }
}