package modeler;

import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Iterator;

import javax.swing.tree.DefaultMutableTreeNode;
import javax.vecmath.Matrix4f;

import modeler.shape.Shape;

import javax.media.opengl.GL;
import javax.media.opengl.glu.*;

public abstract class SceneNode extends DefaultMutableTreeNode {

  /** The name of this node */
  protected String name;
  
  /**
   * Empty constructor required for I/O
   */
  protected SceneNode() {
    
  }
  
  /**
   * Sets the name of this node
   * 
   * @param name
   */
  public void setName(String name) {
    
    this.name = name;
    
  }

  /**
   * Render this node.  The current model view matrix is supplied.  Default just called 
   * render on the children of this node
   * @param gl
   * @param currMat
   */
  public int render(GL gl, GLU glu) {
	  // call render for each child
	  // and count the number of tris drawn
	  int outCount = 0;
	  for (int ctr = 0; ctr < getChildCount(); ctr++)
		  outCount += ((SceneNode) getChildAt(ctr)).render(gl, glu);
	  return outCount;
  }
  
  /**
   * Export this file to data that can be read by the ray tracer
   * @param fileName
   * @throws FileNotFoundException 
   * @throws FileNotFoundException 
   */
  public static void export(String fileName, SceneNode root) throws FileNotFoundException {
    
    //First export the camera, lights and materials
    PrintWriter pw = new PrintWriter(new BufferedOutputStream(new FileOutputStream(fileName)));
    pw.println("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
    pw.println("<scene>");
    HashMap matMap = new HashMap();
    root.exportMainData(matMap, pw);
    pw.println("  <surface type=\"ray1.surface.Model\">");
    pw.println("     <data>"+fileName+".mesh</data>");
    pw.println("  </surface>");
    pw.println("  <image>512 512</image>");
    pw.println("</scene>");
    pw.close();
    
    //Export the meshes
    PrintWriter mw = new PrintWriter(new BufferedOutputStream(new FileOutputStream(fileName+".mesh")));
    Matrix4f mat = new Matrix4f();
    mat.setIdentity();
    root.exportMeshData(matMap, mw, mat);
    mw.close();
    
    
  }
  
  /**
   * Exports this nodes data for the main xml output file
   */
  protected void exportMainData(HashMap materialsMap, PrintWriter pw) {
    if(children != null ) {
    	for (Iterator iter = children.iterator(); iter.hasNext();) {
    		SceneNode currNode = (SceneNode) iter.next();
    		currNode.exportMainData(materialsMap, pw);
    	}
    }
  }
  
  /**
   * Exports this nodes mesh data
   */
  protected void exportMeshData(HashMap materialsMap, PrintWriter pw, Matrix4f mat) {
    if(children != null ) {
    for (Iterator iter = children.iterator(); iter.hasNext();) {
      SceneNode currNode = (SceneNode) iter.next();
      currNode.exportMeshData(materialsMap, pw, new Matrix4f(mat));
    }
    }
  }
  
  /**
   * Writes the treeable out to the data stream
   * @param out
   * @throws IOException 
   */
  public void save(ObjectOutputStream out) throws IOException {
    
    //Write the class of this treeable
    out.writeObject(this.getClass().getName());

    //Write this treeables name
    out.writeObject(name);
    
    //Write that nodes data
    saveData(out);

    //Write the children
    if(children != null ) {
    	out.writeInt(children.size());
    	for (Iterator iter = children.iterator(); iter.hasNext();) {
    		SceneNode currChild = (SceneNode) iter.next();
    		currChild.save(out);
    	}
    }
    else {
    	out.writeInt(0);
    }
  }
  
  /**
   * Reads this treeable from the data stream
   * @param in
   * @throws ClassNotFoundException 
   * @throws IOException 
   */
  public static SceneNode read(ObjectInputStream in) throws IOException, ClassNotFoundException {
    
    //Constructs an instance of the class to be read
    String className = (String) in.readObject();
    Class currClass = Class.forName(className);
    if(!SceneNode.class.isAssignableFrom(currClass))
      throw new Error("modeler.SceneNode.read(): Read a non-SceneNode class: "+currClass);
    SceneNode currNode;
    try {
      currNode = (SceneNode) currClass.newInstance();
    }
    catch (Exception e) {
      e.printStackTrace();
      throw new Error("modeler.SceneNode.read(): Instantiating new SceneNode node.");
    }
    
    //Reads the data
    currNode.name = (String) in.readObject();
    currNode.readData(in);
    
    //Adds its children
    int numChildren = in.readInt();
    for(int i = 0; i < numChildren; i++)
      currNode.add(read(in));
    
    return currNode;
    
  }
  
  /**
   * Writes this object to the stream.
   * @param out
   * @throws IOException 
   */
  protected abstract void saveData(ObjectOutputStream out) throws IOException;
  
  /**
   * Writes this objec to the stream
   * @param in
   * @throws IOException 
   */
  protected abstract void readData(ObjectInputStream in) throws IOException;
  
  /**
   * Rebuilds all meshes
   */
  public void rebuild() {
    if(this instanceof Shape) {
      ((Shape) this).mesh = null;
      return;
    }
    if(children != null ) {
    	for (Iterator iter = children.iterator(); iter.hasNext();) {
    		SceneNode currChild = (SceneNode) iter.next();
    		currChild.rebuild();
    	}
    }
  }
  
  /**
   * @see java.lang.Object#toString()
   */
  public String toString() {
    
    return name;
    
  }
}
