/*
 * Copyright (c) 2009-2015 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.jme3.scene.plugins.fbx.anim;

import com.jme3.animation.BoneTrack;
import com.jme3.animation.SpatialTrack;
import com.jme3.animation.Track;
import com.jme3.math.Quaternion;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.fbx.node.FbxNode;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Maps animation stacks to influenced nodes. 
 * Will be used later to create jME3 tracks.
 */
public final class FbxToJmeTrack {

    public FbxAnimStack animStack;
    public FbxAnimLayer animLayer;
    public FbxNode node;

    // These are not used in map lookups.
    public transient final Map<String, FbxAnimCurveNode> animCurves = new HashMap<String, FbxAnimCurveNode>();

    public long[] getKeyTimes() {
        Set<Long> keyFrameTimesSet = new HashSet<Long>();
        for (FbxAnimCurveNode curveNode : animCurves.values()) {
            for (FbxAnimCurve curve : curveNode.getCurves()) {
                for (long keyTime : curve.getKeyTimes()) {
                    keyFrameTimesSet.add(keyTime);
                }
            }
        }
        long[] keyFrameTimes = new long[keyFrameTimesSet.size()];
        int i = 0;
        for (Long keyFrameTime : keyFrameTimesSet) {
            keyFrameTimes[i++] = keyFrameTime;
        }
        Arrays.sort(keyFrameTimes);
        return keyFrameTimes;
    }
    
    /**
     * Generate a {@link BoneTrack} from the animation data, for the given
     * boneIndex.
     * 
     * @param boneIndex The bone index for which track data is generated for.
     * @param inverseBindPose Inverse bind pose of the bone (in world space).
     * @return A BoneTrack containing the animation data, for the specified
     * boneIndex.
     */
    public BoneTrack toJmeBoneTrack(int boneIndex, Transform inverseBindPose) {
        return (BoneTrack) toJmeTrackInternal(boneIndex, inverseBindPose);
    }
    
    public SpatialTrack toJmeSpatialTrack() {
        return (SpatialTrack) toJmeTrackInternal(-1, null);
    }
    
    public float getDuration() {
        long[] keyframes = getKeyTimes();
        return (float) (keyframes[keyframes.length - 1] * FbxAnimUtil.SECONDS_PER_UNIT);
    }
    
    private static void applyInverse(Vector3f translation, Quaternion rotation, Vector3f scale, Transform inverseBindPose) {
        Transform t = new Transform();
        t.setTranslation(translation);
        t.setRotation(rotation);
        if (scale != null) {
            t.setScale(scale);
        }
        t.combineWithParent(inverseBindPose);
        
        t.getTranslation(translation);
        t.getRotation(rotation);
        if (scale != null) {
            t.getScale(scale);
        }
    }
    
    private Track toJmeTrackInternal(int boneIndex, Transform inverseBindPose) {
        float duration = animStack.getDuration();
        
        FbxAnimCurveNode translationCurve = animCurves.get("Lcl Translation");
        FbxAnimCurveNode rotationCurve    = animCurves.get("Lcl Rotation");
        FbxAnimCurveNode scalingCurve     = animCurves.get("Lcl Scaling");

        long[] fbxTimes = getKeyTimes();
        float[] times = new float[fbxTimes.length];
        
        // Translations / Rotations must be set on all tracks.
        // (Required for jME3)
        Vector3f[]   translations = new Vector3f[fbxTimes.length];
        Quaternion[] rotations = new Quaternion[fbxTimes.length];
        
        Vector3f[] scales = null;
        if (scalingCurve != null) {
            scales = new Vector3f[fbxTimes.length];
        }
         
         for (int i = 0; i < fbxTimes.length; i++) {
            long fbxTime = fbxTimes[i];
            float time = (float) (fbxTime * FbxAnimUtil.SECONDS_PER_UNIT);

            if (time > duration) {
                // Expand animation duration to fit the curve.
                duration = time;
                System.out.println("actual duration: " + duration);
            }

            times[i] = time;
            if (translationCurve != null) {
                translations[i] = translationCurve.getVector3Value(fbxTime);
            } else {
                translations[i] = new Vector3f();
            }
            if (rotationCurve != null) {
                rotations[i] = rotationCurve.getQuaternionValue(fbxTime);
                if (i > 0) {
                    if (rotations[i - 1].dot(rotations[i]) < 0) {
                        System.out.println("rotation will go the long way, oh noes");
                        rotations[i - 1].negate();
                    }
                }
            } else {
                rotations[i] = new Quaternion();
            }
            if (scalingCurve != null) {
                scales[i] = scalingCurve.getVector3Value(fbxTime);
            }
            
            if (inverseBindPose != null) {
                applyInverse(translations[i], rotations[i], scales != null ? scales[i] : null, inverseBindPose);
            }
        }
        
        if (boneIndex == -1) {
            return new SpatialTrack(times, translations, rotations, scales);
        } else {
            if (scales != null) {
                return new BoneTrack(boneIndex, times, translations, rotations, scales);
            } else {
                return new BoneTrack(boneIndex, times, translations, rotations);
            }
        }
    }
    
    @Override
    public int hashCode() {
        int hash = 3;
        hash = 79 * hash + this.animStack.hashCode();
        hash = 79 * hash + this.animLayer.hashCode();
        hash = 79 * hash + this.node.hashCode();
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        final FbxToJmeTrack other = (FbxToJmeTrack) obj;
        return this.node == other.node
                && this.animStack == other.animStack
                && this.animLayer == other.animLayer;
    }
}
