package j3deditor.bin.hierarchy;


import j3deditor.bin.hierarchy.util.Copyable;
import j3deditor.bin.hierarchy.util.Cuttable;
import j3deditor.bin.hierarchy.util.Deletable;
import j3deditor.bin.hierarchy.util.HNodeType;
import j3deditor.bin.hierarchy.util.Pasteable;
import j3deditor.bin.hierarchy.util.ProjectLoader;
import j3deditor.bin.hierarchy.util.VirtualScene3D;

import j3deditor.bin.util.Utilities;

import java.util.ArrayList;

import javax.media.j3d.BranchGroup;


/**
 * Represents one scene.
 * Contains an instance of <code>javax.media.j3d.BranchGroup</code>
 * and list of nodes attached directly to it.
 *
 * @author Risto Seene
 * 
 * @see javax.media.j3d.BranchGroup
 */
public class J3DeScene extends HierarchyNode implements Pasteable, VirtualScene3D{

	/** contains objects attached to this scene */
	private ArrayList<HierarchyNode> objectList;

	/** array that contains selected objects */
	private HierarchyNode[] selectedObjects = null;

	/** array that contains copied objects */
	private HierarchyNode[] copiedObjects = null;

	/** root object of this scene */
	private BranchGroup branchGroup = null;

	/** the name of the file bounded to this scene */
	private String fileName = null;


	/** number of <code>J3DeScene</code> nodes */
	private static long numOfNodes = 0;


	/**
	 * Creates an instance of <code>J3DeScene</code> with the given name.
	 *
	 * @param name
	 *	the name of the new scene
	 */
	public J3DeScene(String name){
		super(name + "[" + numOfNodes + "]", HNodeType.SCENE, numOfNodes);
		numOfNodes++;

		objectList = new ArrayList<HierarchyNode>();
		createScene();
	}


	/**
	 * Creates an instance of <code>J3DeScene</code> with the given name
	 * and if possible loads its contents from specified file.
	 *
	 * @param name
	 *	the name of the new scene
	 * @param filePath
	 *	the path of file the contents of the scene
	 *	will be loaded from
	 */
	public J3DeScene(String name, String filePath){
		this(name);

		if(filePath != null){
			new ProjectLoader(this).loadProject(filePath);
		}
	}


	/**
	 * Initializes the <code>BranchGroup</code>
	 * and camera objects
	 *
	 * @see javax.media.j3d.BranchGroup#setCapability(int capability)
	 */
	private void createScene(){
		branchGroup = new BranchGroup();
		branchGroup.setCapability(BranchGroup.ALLOW_DETACH);

		J3DeCamera camera = new J3DeCamera();
		camera.setParent(this);
		objectList.add(camera);

		setSelectedObject(camera);
	}


	/**
	 * Removes the specified child node.
	 *
	 * @param child
	 *	child node to be removed
	 */
	protected void removeChild(HierarchyNode child){
		try{
			objectList.remove(objectList.indexOf(child));
		} catch(Throwable t){
		}
	}


	/**
	 * Returns the child at the specified index.
	 *
	 * @param index
	 *	index of child
	 * @return Returns the child with the specified index
	 */
	public HierarchyNode getChild(int index){
		return objectList.get(index);
	}


	/**
	 * Returns the number of children.
	 *
	 * @return Returns the number of children as integer
	 */
	public int getNumberOfChildren(){
		return objectList.size();
	}


	/**
	 * Returns the index of specified child.
	 *
	 * @param child
	 *	child node which index will be returned
	 * @return Returns the index of specified child
	 */
	public int getIndexOfChild(HierarchyNode child){
		return objectList.indexOf(child);
	}


	/**
	 * Tests if this node has child which name
	 * equals to the name of the given node.
	 *
	 * @param object
	 *	node which name will be tested
	 * @return Returns <code>true</code> if the name of the given
	 *	node equals to the name of this node,
	 *	otherwise <code>false</code>
	 */
	protected boolean hasNamedChild(HierarchyNode object){
		String name = object.toString();
		for(int i = 0; i < objectList.size(); i++){
			if(!(objectList.get(i).equals(object)) && name.equals(objectList.get(i).toString())){
				return true;
			}
		}
		return false;
	}


	/**
	 * Returns the <code>BranchGroup</code> object of
	 * this scene.
	 *
	 * @return Returns the <code>BranchGroup</code>
	 *	object of this scene
	 */
	public BranchGroup getBranchGroup(){
		return branchGroup;
	}


	/**
	 * Returns the <code>J3DeCamera</code> object that
	 * manages the <code>ViewingPlatform</code> of this scene.
	 *
	 * @return Returns the camera of this scene
	 */
	public J3DeCamera getCamera(){
		J3DeCamera camera = null;
		for(int i = 0; i < objectList.size(); i++){
			if(objectList.get(i) instanceof J3DeCamera){
				camera = (J3DeCamera)objectList.get(i);
				break;
			}
		}
		return camera;
	}


	/**
	 * Adds the specified object to this scene.
	 *
	 * @param object
	 *	object to be added
	 */
	public void addNewObject(J3DeObject object){
		branchGroup.detach();
		object.setParent(this);
		objectList.add(object);
		branchGroup.addChild(object.getObject());

		object.updateName();
		setSelectedObject(object);
	}


	/**
	 * Adds an array of objects to this scene.
	 *
	 * @param objects
	 *	array of objects to be added
	 */
	public void addNewObjects(J3DeObject[] objects){
		for(int i = 0; i < objects.length; i++){
			addNewObject(objects[i]);
		}

		setSelectedObjects(objects);
	}


	/**
	 * Copies all selected objects from this scene
	 *
	 * @param copy
	 *	specifies whether the selected objects will just be
	 *	copied or deleted after copying (cut)
	 */
	private void copyObjects(boolean copy){
		ArrayList<HierarchyNode> nodes = new ArrayList<HierarchyNode>();
		for(int i = 0; i < selectedObjects.length; i++){
			if(copy && selectedObjects[i] instanceof Copyable){
				HierarchyNode object = selectedObjects[i];
				if(object.getParent() != null){
					branchGroup.detach();
					object = object.clone(null);
				}
				nodes.add(object);
			} else if(!copy && selectedObjects[i] instanceof Cuttable){
				nodes.add(selectedObjects[i]);
			}
		}

		if(nodes.size() > 0){
			copiedObjects = new HierarchyNode[nodes.size()];
		}
		for(int j = 0; j < nodes.size(); j++){
			copiedObjects[j] = nodes.get(j);
		}
	}


	/**
	 * Copies all selected objects from this scene.
	 */
	public void copyObjects(){
		copyObjects(true);
	}


	/**
	 * Cuts all selected objects from this scene.
	 */
	public void cutObjects(){
		copyObjects(false);
		deleteObjects(copiedObjects);
	}


	/**
	 * Pastes specified objects to this scene.
	 *
	 * @param objects
	 *	array of objects to be pasted
	 */
	public void pasteContent(HierarchyNode[] objects){
		ArrayList<HierarchyNode> nodes = new ArrayList<HierarchyNode>();
		boolean cut = false;

		for(int i = 0; i < objects.length; i++){
			if(objects[i] instanceof J3DeObject){
				HierarchyNode object = objects[i];
				if(object.getParent() != null){
					branchGroup.detach();
					object = object.clone(null);
					cut = true;
				}
				try{
					addNewObject((J3DeObject)object);
					nodes.add(object);
				} catch(Throwable t){
					copiedObjects = null;
					Utilities.out.throwError(t);
					return;
				}
			}
		}

		if(nodes.size() > 0){
			selectedObjects = new HierarchyNode[nodes.size()];
		}
		for(int j = 0; j < nodes.size(); j++){
			selectedObjects[j] = nodes.get(j);
		}
		if(cut){
			copiedObjects = null;
		}
	}


	/**
	 * Deletes specified objects from this scene.
	 *
	 * @param objects
	 *	objects to be deleted
	 */
	public void deleteObjects(HierarchyNode[] objects){
		branchGroup.detach();

		try{
			for(int i = 0; i < objects.length; i++){
				if(objects[i] instanceof Deletable){
					((Deletable)objects[i]).delete();
				}
			}
		} catch(Throwable t){
			Utilities.out.throwError(t);
		}

		selectedObjects = null;
	}


	/**
	 * Sets one object to be selected.
	 *
	 * @param object
	 *	object to be set selected
	 */
	public void setSelectedObject(HierarchyNode object){
		if(object != null){
			HierarchyNode[] objects = new HierarchyNode[1];
			objects[0] = object;
			selectedObjects = objects;
		}
	}


	/**
	 * Sets the array of selected objects.
	 *
	 * @param objects
	 *	array of objects to be set selected
	 */
	public void setSelectedObjects(HierarchyNode[] objects){
		selectedObjects = objects;
	}


	/**
	 * Returns the array of selected objects.
	 *
	 * @return Returns the array of selected objects
	 */
	public HierarchyNode[] getSelectedObjects(){
		return selectedObjects;
	}


	/**
	 * Returns the array of copied objects.
	 *
	 * @return Returns the array of copied objects
	 */
	public HierarchyNode[] getCopiedObjects(){
		return copiedObjects;
	}
	
	
	/**
	 * Detatces BranchGroup.
	 */
	public void detach(){
		try{
			this.branchGroup.detach();
		} catch(Throwable t){}
	}


	/**
	 * Detaches <code>BranchGroup</code> object and removes
	 * this scene from <code>HierarchyManager</code> that contains it.
	 */
	public void deleteScene(){
		branchGroup.detach();
	          ((HierarchyManager)getParent()).removeScene(this);
		
		this.deleteObjects(objectList.toArray(new HierarchyNode[objectList.size()]));
	          this.objectList = null;
		this.selectedObjects = null;
		this.copiedObjects = null;
		this.branchGroup = null;
	}


	/**
	 * Returns the name of file bounded to this scene.
	 *
	 * @return Returns the file name bounded to this scene
	 */
	public String getFile(){
		return fileName;
	}


	/**
	 * Sets the name of the file bounded to this scene.
	 *
	 * @param fileName
	 *	new name of the file
	 */
	public void setFile(String fileName){
		this.fileName = fileName;
	}

}
