package com.jme3.scene.plugins.blender.animations;

import java.util.Arrays;

import com.jme3.animation.Bone;
import com.jme3.animation.BoneTrack;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;

/**
 * The purpose of this class is to imitate bone's movement when calculating inverse kinematics.
 * @author Marcin Roguski (Kaelthas)
 */
public class CalculationBone extends Node {
	private Bone bone;
	/** The bone's tracks. Will be altered at the end of calculation process. */
	private BoneTrack track;
	/** The starting position of the bone. */
	private Vector3f startTranslation;
	/** The starting rotation of the bone. */
	private Quaternion startRotation;
	/** The starting scale of the bone. */
	private Vector3f startScale;
	private Vector3f[] translations;
	private Quaternion[] rotations;
	private Vector3f[] scales;

	public CalculationBone(Bone bone, int boneFramesCount) {
		this.bone = bone;
		this.startRotation = bone.getModelSpaceRotation().clone();
		this.startTranslation = bone.getModelSpacePosition().clone();
		this.startScale = bone.getModelSpaceScale().clone();
		this.reset();
		if(boneFramesCount > 0) {
			this.translations = new Vector3f[boneFramesCount];
			this.rotations = new Quaternion[boneFramesCount];
			this.scales = new Vector3f[boneFramesCount];
			
			Arrays.fill(this.translations, 0, boneFramesCount, this.startTranslation);
			Arrays.fill(this.rotations, 0, boneFramesCount, this.startRotation);
			Arrays.fill(this.scales, 0, boneFramesCount, this.startScale);
		}
	}
	
	/**
	 * Constructor. Stores the track, starting transformation and sets the transformation to the starting positions.
	 * @param bone
	 *        the bone this class will imitate
	 * @param track
	 *        the bone's tracks
	 */
	public CalculationBone(Bone bone, BoneTrack track) {
		this(bone, 0);
		this.track = track;
		this.translations = track.getTranslations();
		this.rotations = track.getRotations();
		this.scales = track.getScales();
	}

	public int getBoneFramesCount() {
		return this.translations==null ? 0 : this.translations.length;
	}
	
	/**
	 * This method returns the end point of the bone. If the bone has parent it is calculated from the start point
	 * of parent to the start point of this bone. If the bone doesn't have a parent the end location is considered
	 * to be 1 point up along Y axis (scale is applied if set to != 1.0);
	 * @return the end point of this bone
	 */
	//TODO: set to Z axis if user defined it this way
	public Vector3f getEndPoint() {
		if (this.getParent() == null) {
			return new Vector3f(0, this.getLocalScale().y, 0);
		} else {
			Node parent = this.getParent();
			return parent.getWorldTranslation().subtract(this.getWorldTranslation()).multLocal(this.getWorldScale());
		}
	}

	/**
	 * This method resets the calculation bone to the starting position.
	 */
	public void reset() {
		this.setLocalTranslation(startTranslation);
		this.setLocalRotation(startRotation);
		this.setLocalScale(startScale);
	}

	@Override
	public int attachChild(Spatial child) {
		if (this.getChildren() != null && this.getChildren().size() > 1) {
			throw new IllegalStateException(this.getClass().getName() + " class instance can only have one child!");
		}
		return super.attachChild(child);
	}

	public Spatial rotate(Quaternion rot, int frame) {
		Spatial spatial = super.rotate(rot);
		this.updateWorldTransforms();
		if (this.getChildren() != null && this.getChildren().size() > 0) {
			CalculationBone child = (CalculationBone) this.getChild(0);
			child.updateWorldTransforms();
		}
		rotations[frame].set(this.getLocalRotation());
		translations[frame].set(this.getLocalTranslation());
		if (scales != null) {
			scales[frame].set(this.getLocalScale());
		}
		return spatial;
	}

	public void applyCalculatedTracks() {
		if(track != null) {
			track.setKeyframes(track.getTimes(), translations, rotations, scales);
		} else {
			bone.setUserControl(true);
			bone.setUserTransforms(translations[0], rotations[0], scales[0]);
			bone.setUserControl(false);
			bone.updateWorldVectors();
		}
	}

	@Override
	public String toString() {
		return bone.getName() + ": " + this.getLocalRotation() + " " + this.getLocalTranslation();
	}
}