package j3deditor.bin.hierarchy.util;


import j3deditor.bin.hierarchy.J3DeLight;
import j3deditor.bin.hierarchy.J3DeObject;
import j3deditor.bin.hierarchy.J3DePrimitive;
import j3deditor.bin.hierarchy.J3DeScene;
import j3deditor.bin.hierarchy.J3DeShape3D;

import j3deditor.bin.util.ByteConverter;
import j3deditor.bin.util.Utilities;

import com.sun.j3d.utils.geometry.Primitive;
import com.sun.j3d.utils.scenegraph.io.SceneGraphStreamReader;

import java.io.FileInputStream;
import java.io.InputStream;

import java.util.ArrayList;
import java.util.HashMap;

import javax.media.j3d.BranchGroup;
import javax.media.j3d.Light;
import javax.media.j3d.Node;
import javax.media.j3d.Shape3D;
import javax.media.j3d.TransformGroup;


/**
 * Tool for loading saved scenes.
 * Uses <code>com.sun.j3d.utils.scenegraph.io.SceneGraphStreamReader</code>
 * to load <code>BranchGroup</code>.
 *
 * @author Risto Seene
 * 
 * @see com.sun.j3d.utils.scenegraph.io.SceneGraphStreamReader
 */
public class ProjectLoader extends HierarchyWorker{

	/** Specifies the version of this loader. */
	private static final float version = 1.0f;

	/** scene the <code>BranchGroup</code> will be loaded to */
	private J3DeScene scene;

	/** input stream where the <code>SceneGraphStreamReader</code> reads from */
	private InputStream in = null;

	/** path of the scene file */
	private String filePath;


	/**
	 * Creates an instance of <code>ProjectLoader</code>
	 * which is able to load <code>BranchGroup</code> to the given scene.
	 *
	 * @param scene
	 *	an instance of <code>J3DeScene</code>
	 *	this loader can load to
	 */
	public ProjectLoader(J3DeScene scene){
		this.scene = scene;
	}


	/**
	 * Loads the content of the scene from the given file.
	 *
	 * @param filePath
	 *	path of the file the scene will be loaded from
	 */
	public void loadProject(String filePath){
		this.filePath = filePath;
		this.execute();
	}


	/**
	 * Loads the content of the scene from the given stream.
	 * 
	 * @param in
	 * 	stream where the scene will be loaded from
	 */
	public void loadProject(InputStream in){
		this.in = in;
		this.execute();
	}


	/**
	 * Performes the loading process in a background thread.
	 * 
	 * @return nothing
	 * @throws Exception
	 * 	if unable to load a scene
	 */
	protected Void doInBackground() throws Exception{
		try{
			if(this.filePath != null){
				in = new FileInputStream(filePath);
			}
			readData();
			in.close();
		} catch(Throwable t){
			Utilities.out.throwError(t);
			try{
				in.close();
			} catch(Throwable t1){
			}
		}

		return null;
	}


	/**
	 * Reads the transformation attributes of the camera.
	 *
	 * @exception Throwable
	 *	if reading from stream fails
	 */
	private void readData() throws Throwable{
		byte[] buffer;

		buffer = new byte[6];
		if(in.read(buffer) == 6 && ByteConverter.toString(buffer).equals("<J3DE>")){
			buffer = new byte[4];
			if(in.read(buffer) == 4){
				float version = ByteConverter.toFloat(buffer);
				if(version > ProjectLoader.version){
					Utilities.out.throwWarning("File version: " + version + "\nFile loader version: " + ProjectLoader.version);
				          loadVersion_1_0();
				}
			          else if(version == ProjectLoader.version){
			                    loadVersion_1_0();
			          }
			}
			else {
				Utilities.out.throwError("File header corrupted!");
				return;
			}
		}
		else {
			Utilities.out.throwError("Not a Java 3D editor scene file!");
		}
	}
	
	
	/**
	 * Loads information from scene file which version is 1.0.
	 * 
	 * @throws Throwable
	 * 	if an error occurs
	 */
	private void loadVersion_1_0() throws Throwable{
	          scene.getCamera().readData(in);
		
		byte[] buffer;
	          buffer = new byte[7];
	          if(in.read(buffer) == 7 && ByteConverter.toString(buffer).equals("</J3DE>")){
	                    readBranchGroup();
	          }
	          else {
			Utilities.out.throwError("File header corrupted!");
	                    return;
	          }
	}


	/**
	 * Loads the <code>BranchGroup</code> from file
	 *
	 * @see com.sun.j3d.utils.scenegraph.io.SceneGraphStreamReader#readBranchGraph(HashMap hm)
	 */
	private void readBranchGroup(){
		SceneGraphStreamReader sgsReader = null;
		try{
			sgsReader = new SceneGraphStreamReader(in);

			HashMap hashMap = new HashMap();
			BranchGroup branchGroup = sgsReader.readBranchGraph(hashMap);
			buildScene(branchGroup, hashMap);
			sgsReader.close();
		} catch(Throwable t){
			Utilities.out.throwError(t);
			try{
				sgsReader.close();
			} catch(Throwable t1){
			}
		}
	}


	/**
	 * Adds the imported objects to the scene.
	 * For every object creates an instance of <code>J3DeObject</code>,
	 * applies the loaded object to it and adds it to scene.
	 *
	 * <br><b>Note:</b> Because of some problems with
	 * <code>SceneGraphStreamReader</code> doesn't load
	 * object names correctly.<br>
	 *
	 * @param bg
	 *	loaded <code>BranchGroup</code>
	 * @param hm
	 *	hashmap of imported objects and their names
	 */
	private void buildScene(BranchGroup bg, HashMap hm){
		ArrayList<J3DeObject> objects = new ArrayList<J3DeObject>();

		Object[] keys = hm.keySet().toArray();
		for(int i = 0; i < keys.length; i++){
			try{
				//TransformGroup obj = ((TransformGroup)(hm.get(keys[i])));
				TransformGroup obj = (TransformGroup)(bg.getChild((Integer)(hm.get(keys[i]))));
				((BranchGroup)obj.getParent()).removeChild(obj);

				J3DeObject j3deObj = buildObject(obj);
				j3deObj.setName((String)keys[i]);
				objects.add(j3deObj);
			} catch(Throwable t){
				Utilities.out.throwWarning(t);
			}
		}

		J3DeObject[] j3deobjects = new J3DeObject[objects.size()];
		for(int i = 0; i < objects.size(); i++){
			j3deobjects[i] = objects.get(i);
		}
		scene.addNewObjects(j3deobjects);
	}


	/**
	 * Creates an instance of <code>J3DeObject</code>
	 * with the specified <code>TransformGroup</code>
	 *
	 * @param transform
	 *	<code>TransformGroup</code> where the <code>Node</code>
	 *	is attached to
	 */
	private J3DeObject buildObject(TransformGroup transform) throws Exception{
		Node object = transform.getChild(0);
		transform.removeChild(object);
		J3DeObject j3deObject = null;

		if(object instanceof Shape3D){
			j3deObject = new J3DeShape3D();
			((J3DeShape3D)j3deObject).init3DObject(object);
		}
		else if(object instanceof Primitive){
			j3deObject = new J3DePrimitive();
			((J3DePrimitive)j3deObject).init3DObject(object);
		}
		else if(object instanceof Light){
			j3deObject = new J3DeLight();
			((J3DeLight)j3deObject).init3DObject(object);
		}

		j3deObject.setObject(transform);

		return j3deObject;
	}


	/**
	 * Notifies all listeners that object(s) have been added to the scene.
	 */
	protected void done(){
		super.done();
		notifyListeners(new HierarchyEvent(this, HierarchyEvent.OBJECT_ADDITION));
	}

}
