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.util.Utilities;

import javax.media.j3d.Group;
import javax.media.j3d.Node;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;

import javax.vecmath.Matrix3d;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;


/**
 * Base class for transformable objects.
 * Contains an instance of
 * <code>javax.media.j3d.TransformGroup</code> and
 * <code>javax.media.j3d.Node</code> attached to it
 *
 * @author Risto Seene
 * 
 * @see javax.media.j3d.TransformGroup
 */
public class J3DeObject extends HierarchyNode implements Copyable, Cuttable, Deletable{

	/** transformation this node manipulates with */
	private TransformGroup transform;

	/** Java 3D object attached to <code>TransformGroup</code> */
	private Node object;


	/**
	 * Creates an instance of <code>J3DeObject</code> with
	 * the given name, type and index.
	 *
	 * @param name
	 *	the name of this object
	 * @param type
	 *	the type of this object
	 * @param numOfNodes
	 *	the index of this object
	 */
	public J3DeObject(String name, HNodeType type, long numOfNodes){
		super(name, type, numOfNodes);

		transform = new TransformGroup(new Transform3D());
		setCapabilities();
	}


	/**
	 * Sets the capabilities for the <code>TransformGroup</code>
	 * this node controls.
	 *
	 * <br><ul><li><code>TransformGroup.ALLOW_TRANSFORM_WRITE</code></li>
	 * <li><code>TransformGroup.ALLOW_TRANSFORM_READ</code></li></ul>
	 *
	 * @see javax.media.j3d.TransformGroup#setCapability(int capability)
	 */
	private void setCapabilities(){
		transform.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
		transform.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
	}


	/**
	 * Attaches the given Java 3D object to the
	 * <code>TransformGroup</code> inside this node.
	 *
	 * @param object
	 *	object to be attached to
	 */
	public void initObject(Node object){
		this.object = object;
		try{
			transform.addChild(object);
		} catch(Throwable t){
		}
	}


	/**
	 * Returns the <code>TransformGroup</code> object
	 * of this node.
	 *
	 * @return Returns the <code>TransformGroup</code> object
	 * of this node
	 */
	public TransformGroup getObject(){
		return transform;
	}


	/**
	 * Returns the Java 3D <code>Node</code> attached
	 * to this nodes <code>TransformGroup</code>.
	 *
	 * @return Returns Java 3D <code>Node</code> object
	 *	contained in this node
	 */
	protected Node get3DObject(){
		return object;
	}


	/**
	 * Copies the transformation from the given <code>TransformGroup</code> object
	 * to the <code>TransformGroup</code> object of this node.
	 *
	 * @param transform
	 *	transformation object the transformation will be copied from
	 */
	public void setObject(TransformGroup transform){
		Transform3D t = new Transform3D();
		transform.getTransform(t);
		this.transform.setTransform(t);
	}


	/**
	 * Replaces the <code>TransformGroup</code> object of
	 * this node with the given object and sets its capabilities.
	 *
	 * @param node
	 *	new <code>TransformGroup</code> object
	 * @throws Exception
	 *	if the given node is not an
	 *	instance of <code>TransformGroup</code> or it does not
	 *	have any children
	 */
	protected void setNode(Node node) throws Exception{
		transform = (TransformGroup)node;
		setCapabilities();
		object = transform.getChild(0);
	}


	/**
	 * Clones this node.
	 * Should only be called by subclasses
	 *
	 * @param node
	 *	node sent by subclass
	 * @return the same node that was inserted
	 */
	protected HierarchyNode clone(HierarchyNode node){
		try{
			((J3DeObject)node).setNode(getObject().cloneTree());
			return super.clone(node);
		} catch(Throwable t){
			return null;
		}
	}


	/**
	 * Removes this node from its parent.
	 *
	 * @throws Exception
	 *	if cannot remove the <code>TransformGroup</code>
	 *	object of this node from its parent or this node has no parent
	 */
	public void delete() throws Exception{
		((Group)this.getObject().getParent()).removeChild(this.getObject());
		this.getParent().removeChild(this);
		this.setParent(null);
	}


	/**
	 * Alters the translation vector of the <code>TransformGroup</code> object
	 *
	 * @param t
	 *	transformation that contains the translation
	 * @param isLocal
	 *	specifies whether the object is moved along its local axes
	 *	or along the axes of scene	
	 */
	private void translate(Transform3D t, boolean isLocal){
		Transform3D move = new Transform3D();
		transform.getTransform(move);
		if(isLocal){
			move.mul(t);
			transform.setTransform(move);
		} else {
			t.mul(move);
			transform.setTransform(t);
		}
	}


	/**
	 * Moves the object along the X-axis.
	 *
	 * @param x
	 *	specifies how much this object will be moved
	 * @param isLocal
	 *	specifies whether the object is moved along its local axes
	 *	or along the axes of scene	
	 */
	public void translateX(float x, boolean isLocal){
		Transform3D t = new Transform3D();
		t.setTranslation(new Vector3f(x, 0, 0));
		translate(t, isLocal);
	}


	/**
	 * Moves the object along the Y-axis.
	 *
	 * @param y
	 *	specifies how much this object will be moved
	 * @param isLocal
	 *	specifies whether the object is moved along its local axes
	 *	or along the axes of scene	
	 */
	public void translateY(float y, boolean isLocal){
		Transform3D t = new Transform3D();
		t.setTranslation(new Vector3f(0, y, 0));
		translate(t, isLocal);
	}


	/**
	 * Moves the object along the Z-axis.
	 *
	 * @param z
	 *	specifies how much this object will be moved
	 * @param isLocal
	 *	specifies whether the object is moved along its local axes
	 *	or along the axes of scene	
	 */
	public void translateZ(float z, boolean isLocal){
		Transform3D t = new Transform3D();
		t.setTranslation(new Vector3f(0, 0, z));
		translate(t, isLocal);
	}


	/**
	 * Sets the given absolute translation on each axis.
	 *
	 * @param x
	 *	absolute translation on X-axis
	 * @param y
	 *	absolute translation on Y-axis
	 * @param z
	 *	absolute translation on Z-axis
	 */
	public void translate(float x, float y, float z){
		Transform3D t = new Transform3D();
		transform.getTransform(t);
		Vector3f location = new Vector3f(x, y, z);
		try{
			t.setTranslation(location);
			transform.setTransform(t);
		} catch(Throwable t1){
			Utilities.out.throwWarning(t1);
		}
	}


	/**
	 * Returns the translation vector of this object
	 *
	 * @return Returns the translation vector of this object
	 */
	private Vector3f getTranslation(){
		Transform3D t = new Transform3D();
		transform.getTransform(t);
		Vector3f location = new Vector3f();
		t.get(location);
		return location;
	}


	/**
	 * Returns the position on X-axis.
	 *
	 * @return Returns the position on X-axis
	 */
	public float getTranslationX(){
		return getTranslation().getX();
	}


	/**
	 * Returns the position on Y-axis.
	 *
	 * @return Returns the position on Y-axis
	 */
	public float getTranslationY(){
		return getTranslation().getY();
	}


	/**
	 * Returns the position on Z-axis.
	 *
	 * @return Returns the position on Z-axis
	 */
	public float getTranslationZ(){
		return getTranslation().getZ();
	}


	/**
	 * Alters the scale vector of the <code>TransformGroup</code> object
	 *
	 * @param scale
	 *	scale vector
	 * @param isLocal
	 *	has no effect yet	
	 */
	private void scale(Vector3d scale, boolean isLocal){
		Transform3D t = new Transform3D();
		transform.getTransform(t);
		Transform3D t1 = new Transform3D();
		t1.setScale(scale);

		if(!isLocal){
			/*t1.setScale(scale);
			Matrix3f m = new Matrix3f();
			t.get(m);
			t.setRotation(new Matrix3f(1,0,0,0,1,0,0,0,1));
			t1.setRotation(m);
			t.mul(t1);*/
		}

		t.mul(t1);
		transform.setTransform(t);
	}


	/**
	 * Resizes this object on X-axis.
	 *
	 * @param difference
	 *	specifies how much the object will be resized
	 * @param isLocal
	 *	has no effect yet
	 */
	public void scaleX(float difference, boolean isLocal){
		scale(new Vector3d(difference + 1, 1, 1), isLocal);
	}


	/**
	 * Resizes this object on Y-axis.
	 *
	 * @param difference
	 *	specifies how much the object will be resized
	 * @param isLocal
	 *	has no effect yet
	 */
	public void scaleY(float difference, boolean isLocal){
		scale(new Vector3d(1, difference + 1, 1), isLocal);
	}


	/**
	 * Resizes this object on Z-axis.
	 *
	 * @param difference
	 *	specifies how much the object will be resized
	 * @param isLocal
	 *	has no effect yet
	 */
	public void scaleZ(float difference, boolean isLocal){
		scale(new Vector3d(1, 1, difference + 1), isLocal);
	}


	/**
	 * Resizes this object on all axes.
	 *
	 * @param difference
	 *	specifies how much the object will be resized
	 */
	public void scaleAll(float difference){
		scale(new Vector3d(difference + 1, difference + 1, difference + 1), false);
	}


	/**
	 * Sets the absolute scale vector for this object
	 *
	 * @param scale
	 *	new scale vector
	 */
	private void scale(Vector3d scale){
		Transform3D t = new Transform3D();
		transform.getTransform(t);
		try{
			t.setScale(scale);
			transform.setTransform(t);
		} catch(Throwable t1){
			Utilities.out.throwWarning(t1);
		}
	}


	/**
	 * Sets the given absolute scale on each axis.
	 *
	 * @param x
	 *	absolute scale on X-axis
	 * @param y
	 *	absolute scale on Y-axis
	 * @param z
	 *	absolute scale on Z-axis
	 */
	public void scale(float x, float y, float z){
		scale(new Vector3d(x, y, z));
	}


	/**
	 * Returns the scale vector of this object
	 *
	 * @return Returns the scale vector of this object
	 */
	private Vector3f getScale(){
		Transform3D t = new Transform3D();
		transform.getTransform(t);
		Vector3d vector = new Vector3d();
		t.getScale(vector);

		return new Vector3f(vector);
	}


	/**
	 * Returns the scale on X-axis.
	 *
	 * @return Returns the scale on X-axis
	 */
	public float getScaleX(){
		return getScale().getX();
	}


	/**
	 * Returns the scale on Y-axis.
	 *
	 * @return Returns the scale on Y-axis
	 */
	public float getScaleY(){
		return getScale().getY();
	}


	/**
	 * Returns the scale on Z-axis.
	 *
	 * @return Returns the scale on Z-axis
	 */
	public float getScaleZ(){
		return getScale().getZ();
	}


	/**
	 * Alters the rotation matrix of the <code>TransformGroup</code> object
	 *
	 * @param t
	 *	transformation that contains the rotation
	 * @param isLocal
	 *	specifies whether the object is totated around its local axes
	 *	or around the axes of scene	
	 */
	private void rotate(Transform3D t, boolean isLocal){
		Transform3D rot = new Transform3D();
		transform.getTransform(rot);
		if(isLocal){
			Vector3d scale = new Vector3d();
			rot.getScale(scale);
			t.setScale(scale);
			rot.setScale(new Vector3d(1, 1, 1));
			rot.mul(t);
			transform.setTransform(rot);
		} else {
			Vector3f position = new Vector3f();
			rot.get(position);
			t.setTranslation(position);
			rot.setTranslation(new Vector3f());
			t.mul(rot);
			transform.setTransform(t);
		}
	}


	/**
	 * Rotates the object around the X-axis.
	 *
	 * @param angle
	 *	specifies how much the angle will be changed
	 * @param isLocal
	 *	specifies whether the object is rotated around its local axes
	 *	or around the axes of scene	
	 */
	public void rotateX(float angle, boolean isLocal){
		Transform3D t = new Transform3D();
		t.rotX(angle * Math.PI / 180);
		rotate(t, isLocal);
	}


	/**
	 * Rotates the object around the Y-axis.
	 *
	 * @param angle
	 *	specifies how much the angle will be changed
	 * @param isLocal
	 *	specifies whether the object is rotated around its local axes
	 *	or around the axes of scene	
	 */
	public void rotateY(float angle, boolean isLocal){
		Transform3D t = new Transform3D();
		t.rotY(angle * Math.PI / 180);
		rotate(t, isLocal);
	}


	/**
	 * Rotates the object around the Z-axis.
	 *
	 * @param angle
	 *	specifies how much the angle will be changed
	 * @param isLocal
	 *	specifies whether the object is rotated around its local axes
	 *	or around the axes of scene	
	 */
	public void rotateZ(float angle, boolean isLocal){
		Transform3D t = new Transform3D();
		t.rotZ(angle * Math.PI / 180);
		rotate(t, isLocal);
	}


	/**
	 * Sets the given absolute angle on each axis.
	 *
	 * @param x
	 *	absolute angle around X-axis in degrees
	 * @param y
	 *	absolute angle around Y-axis in degrees
	 * @param z
	 *	absolute angle around Z-axis in degrees
	 */
	public void rotate(float x, float y, float z){
		Transform3D t = new Transform3D();
		transform.getTransform(t);
		try{
			t.setEuler(new Vector3d(Math.PI * x / 180, Math.PI * y / 180, Math.PI * z / 180));
			transform.setTransform(t);
		} catch(Throwable t1){
			Utilities.out.throwWarning(t1);
		}
	}


	/**
	 * Returns the rotation matrix of this object
	 *
	 * @return Returns the rotation matrix of this object
	 */
	private Matrix3d getRotation(){
		Transform3D t = new Transform3D();
		transform.getTransform(t);
		Matrix3d matrix = new Matrix3d();
		t.get(matrix);

		return matrix;
	}


	/**
	 * Returns the rotation around X-axis.
	 *
	 * @return Returns the rotation around X-axis in degrees
	 */
	public float getRotationX(){
		Matrix3d m = getRotation();
		return (float)(180 * Math.atan2(-m.getElement(1, 2), m.getElement(1, 1)) / Math.PI);
	}


	/**
	 * Returns the rotation around Y-axis.
	 *
	 * @return Returns the rotation around Y-axis in degrees
	 */
	public float getRotationY(){
		Matrix3d m = getRotation();
		return (float)(180 * Math.atan2(-m.getElement(2, 0), m.getElement(0, 0)) / Math.PI);
	}


	/**
	 * Returns the rotation around Z-axis.
	 *
	 * @return Returns the rotation around Z-axis in degrees
	 */
	public float getRotationZ(){
		Matrix3d m = getRotation();
		return (float)(180 * Math.asin(m.getElement(1, 0)) / Math.PI);
	}

}
