import java.util.*;
import java.io.*;
import javax.vecmath.*;
import javax.media.j3d.*;
import com.sun.j3d.utils.geometry.*;

class Joint {
    String  name;           // name of joint, as read from file

    Joint   parent;            // parent joint that this is connected to
    Joint   firstChild;        // first of children joints
    Joint   lastChild;         // last of children joints
    Joint   nextSibling;       // next joint at same level

    Transform3D originalTransform;
    TransformGroup transformGroup;  // Scene graph group from this joint
    Bone    parentBone;         // Bone to parent joint, if any
    
    float xOffset, yOffset, zOffset;   // relative offsets
    float xRot, yRot, zRot;   // relative rotations
    
    public final static float DEGtoRAD = ((float)Math.PI)/180.0f;
    
    // CONSTRUCTORS
    
    /**
    * Class Constructor
    **/
    Joint() {
        // create Transform group for this joint and all its children
        Transform3D transform3D = new Transform3D();
        transformGroup = new TransformGroup(transform3D);
        transformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        transformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
        transformGroup.setCapability(TransformGroup.ALLOW_CHILDREN_READ);
        
        // put a colorcube on the joint
        transformGroup.addChild(new ColorCube(0.02f));
    	//{{INIT_CONTROLS
	    //}}
    }

    /**
    * Class Constructor that loads info for named Joint from disk
    **/
    Joint(BufferedReader input) throws IOException {
        this();
        loadJointInfo(input);
        getOriginalOrientation();
    }


    /**
    * Class Constructor that loads info for named Joint from disk,
    * and also uses information about parent joint to extend skeleton and
    * scene graph
    **/
    Joint(Joint parentJoint, String nameStr, BufferedReader input) 
                                            throws IOException {
        this();
        loadJointInfo(nameStr, input);
        parent = parentJoint;
        if (parent != null) {
            parentBone = new Bone(parent, this);
        }
        getOriginalOrientation();
    }


    
    // PUBLIC METHODS
    
    public Bone getBoneToJoint() {
        return parentBone;
    }
    
    public TransformGroup getTransformGroup() {
        return transformGroup;
    }
    
    public void setOriginalOrientation() {
        Transform3D newTransform = new Transform3D(originalTransform);
        transformGroup.setTransform(newTransform);        
    }
    
    public void setPosition(Vector3f newPosition) {
        xOffset = newPosition.x;
        yOffset = newPosition.y;
        zOffset = newPosition.z;
                
        originalTransform.setTranslation(newPosition);
        transformGroup.setTransform(originalTransform);
    }
    
    public void setRotationsRadians(float newXrot, float newYrot, float newZrot) {
        xRot = newXrot;
        yRot = newYrot;
        zRot = newZrot;
        
        Transform3D newTransform = new Transform3D(originalTransform);
        Transform3D rotation = new Transform3D();
        
        rotation.rotX(xRot);
        rotation.rotY(yRot);
        rotation.rotZ(zRot);
        newTransform.mul(rotation);
        
        transformGroup.setTransform(newTransform);
    }

    public void setRotationsDegrees(float newXrot, float newYrot, float newZrot) {
        setRotationsRadians(newXrot*DEGtoRAD, newYrot*DEGtoRAD, newZrot*DEGtoRAD);
    }
    

    // PRIVATE METHODS

    // Add a child joint that starts from the end of a 
    //  bone starting at this joint
    private void addChildJoint(String newJointName, BufferedReader input)
                                                      throws IOException {
        Joint newChild = new Joint(this, newJointName, input);
        
        // add child group and node of bone going to child, to scene graph
        transformGroup.addChild(newChild.getTransformGroup());
        transformGroup.addChild(newChild.parentBone.getBoneShape());

        if (firstChild == null) firstChild = lastChild = newChild;
        else {
            lastChild.nextSibling = newChild;
            lastChild = newChild;
        }
    }
    
    // read in joint info, given no info yet - typically root joint
    private void loadJointInfo(BufferedReader input) throws IOException{
        StringTokenizer firstLine = new StringTokenizer(input.readLine());

        if (firstLine.nextToken().equals("ROOT")) {
            loadJointInfo(firstLine.nextToken(), input);
        }
    }

    private void loadJointInfo(String nameStr, BufferedReader input) throws IOException {
        StringTokenizer line;

        name = nameStr;

        input.readLine();    // read off the first '{' line

        String firstToken = null;
        do {
            line = new StringTokenizer(input.readLine());
            firstToken = line.nextToken();

            if (firstToken.equals("OFFSET")) {
                xOffset = Float.valueOf(line.nextToken()).floatValue() / 25.0f;
                yOffset = Float.valueOf(line.nextToken()).floatValue() / 25.0f;
                zOffset = Float.valueOf(line.nextToken()).floatValue() / 25.0f;
            }
            else if (firstToken.equals("JOINT")) {
                addChildJoint(line.nextToken(), input);
            }
            else if (firstToken.equals("End")) {
                addChildJoint("End Site", input);
            }
        } while(!firstToken.equals("}"));
    }
    
    private void getOriginalOrientation() {
        Transform3D transform = new Transform3D();
        transform.setTranslation(new Vector3f(xOffset, yOffset, zOffset));
        transformGroup.setTransform(transform);
        
        originalTransform = transform;
    }
	//{{DECLARE_CONTROLS
	//}}
}
