package j3deditor.bin.hierarchy;


import j3deditor.bin.hierarchy.util.HNodeType;
import j3deditor.bin.hierarchy.util.Shape3DType;

import j3deditor.bin.util.Utilities;

import com.sun.j3d.utils.geometry.ColorCube;

import com.sun.j3d.utils.geometry.GeometryInfo;
import com.sun.j3d.utils.geometry.NormalGenerator;

import java.util.ArrayList;
import java.util.Enumeration;

import javax.media.j3d.Appearance;
import javax.media.j3d.Geometry;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.Node;
import javax.media.j3d.Shape3D;


/**
 * Contains and manages an instance of
 * <code>javax.media.j3d.Shape3D</code>.
 *
 * @author Risto Seene
 * 
 * @see javax.media.j3d.Shape3D
 */
public class J3DeShape3D extends J3DeObject{

	/** actual <code>Shape3D</code> object */
	private Shape3D object;

	/** appearance of this object */
	private J3DeAppearance appearance;

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


	/**
	 * Creates an empty <code>J3DeShape3D</code> object.
	 * Requires initialization with <code>init3DObject()</code> method
	 */
	public J3DeShape3D(){
		super("Shape3D", HNodeType.SHAPE3D, numOfNodes);
		numOfNodes++;
	}


	/**
	 * Creates an instance of <code>J3DeShape3D</code> with the given type.
	 *
	 * @param type
	 *	the type of the object
	 */
	public J3DeShape3D(Shape3DType type){
		super("Shape3D" + "[" + numOfNodes + "]", HNodeType.SHAPE3D, numOfNodes);
		numOfNodes++;

		if(type != null && type == Shape3DType.COLORCUBE){
			object = new Shape3D(new ColorCube().getGeometry());
			init3DObject(object);
		}
	}


	/**
	 * Creates an instance of <code>J3DeShape3D</code> that contains
	 * the given <code>Shape3D</code> object.
	 *
	 * @param object
	 *	the object that will be applied to this node
	 */
	public J3DeShape3D(Shape3D object){
		this(Shape3DType.SHAPE3D);

		if(object != null){
			this.object = object;
			init3DObject(object);
		}
	}


	/**
	 * Applies the given <code>Node</code> object to this node.
	 *
	 * @param obj
	 *	object to be applied to this node
	 */
	public void init3DObject(Node obj){
		object = (Shape3D)obj;
		initObject(object);

		if(object.getAppearance() == null){
			object.setAppearance(new Appearance());
		}
		appearance = new J3DeAppearance(object.getAppearance());
		appearance.setParent(this);

		initGeometry();
	}

	/**
	 * Sets Shape3D and Geometry capabilities.
	 */
	private void initGeometry(){
		try{
			object.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
		          object.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE);
			Geometry[] g = getGeometries();
			for(int i = 0; i < g.length; i++){
				J3DeGeometry.setCapabilities(g[i]);
			}
		} catch(Throwable t){
			Utilities.out.throwWarning(t);
		}
	}


	/**
	 * Clones this node.
	 *
	 * @param node
	 *	object sent by subclass, otherwise <code>null</code>
	 * @return Returns the clone of this node
	 */
	public HierarchyNode clone(HierarchyNode node){
		if(node == null){
			node = new J3DeShape3D();
		}

		node = super.clone(node);
		((J3DeShape3D)node).init3DObject(((J3DeObject)node).get3DObject());
		return node;
	}


	/**
	 * 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){
		if(appearance != null){
			return appearance.getChild(index);
		}
		return null;
	}


	/**
	 * Returns the number of children.
	 *
	 * @return Returns the number of children as integer
	 */
	public int getNumberOfChildren(){
		if(appearance != null){
			return appearance.getNumberOfChildren();
		}
		return 0;
	}


	/**
	 * 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){
		if(appearance != null){
			return appearance.getIndexOfChild(child);
		}
		return 0;
	}


	/**
	 * Returns the appearance object of this node.
	 *
	 * @return Returns the appearance object of this node
	 */
	public J3DeAppearance getAppearance(){
		return appearance;
	}


	/**
	 * Returns all geometries which are referenced by this Shape3D.
	 * 
	 * @return all geometries which are referenced by this Shape3D
	 */
	public Geometry[] getGeometries(){
		ArrayList<Geometry> gs = new ArrayList<Geometry>();
		for(Enumeration e = object.getAllGeometries(); e.hasMoreElements(); ){
			gs.add((Geometry)(e.nextElement()));
		}

		return gs.toArray(new Geometry[gs.size()]);
	}
	
	/**
	 * Replaces geometry at the specified index.
	 * 
	 * @param g
	 * 	new geometry
	 * @param index
	 * 	index of geometry
	 */
	public void setGeometry(J3DeGeometry g, int index){
		this.object.setGeometry(g.getGeometry(), index);
	}
	
	/**
	 * Adds new geometry.
	 * 
	 * @param g
	 * 	new geometry
	 */
	public void addGeometry(J3DeGeometry g){
		this.object.addGeometry(g.getGeometry());
	}
	
	/**
	 * Generates normals using <code>NormalGenerator</code>.
	 * 
	 * @param greaseAngle
	 * 	grease angle in degrees
	 *        
	 * @see com.sun.j3d.utils.geometry.NormalGenerator#generateNormals(GeometryInfo geom)
	 */
	public void recalculateNormals(double greaseAngle){
	          try{
	                    Geometry[] g = getGeometries();
			greaseAngle = Math.toRadians(greaseAngle);
	                    for(int i = 0; i < g.length; i++){
	                              GeometryInfo gi = new GeometryInfo((GeometryArray)g[i]);
	                              new NormalGenerator(greaseAngle).generateNormals(gi);
	                              object.setGeometry(gi.getGeometryArray(), i);
	                    }
			initGeometry();
	          } catch(Throwable t){
			Utilities.out.throwWarning(t);
	          }
	}

}
