#include "Animation3D.h"

#include "Model3D.h"

using namespace Framework;

// create an animation for a specific bone
// \param boneId the id of the bone
// \param originPos the position of the bone at the beginning of the animation
// \param originRot the rotation of the bone at the beginning of the animation
BoneAnimation::BoneAnimation(
    int boneId, Vec3<float> originPos, Vec3<float> originRot)
    : ReferenceCounter(),
      boneId(boneId),
      frameCount(1),
      maxTime(0.0),
      loop(0)
{
    frames = new KeyFrame[1];
    frames[0].time = 0.0;
    frames[0].pos = originPos;
    frames[0].rot = originRot;
    current = frames[0];
}

// destructor
BoneAnimation::~BoneAnimation()
{
    delete[] frames;
}

// make the animation to a loop that starts again if the end is reached
void BoneAnimation::setLoop(bool loop)
{
    this->loop = loop;
}

//! adds a keyframe
//! \param time the time in seconds since the start of the animation at
//! which the bone should have the given position and rotation relative
//! to its parent bone \param pos the position of the bone \param rot
//! the rotation of the bone
void BoneAnimation::addKeyFrame(double time, Vec3<float> pos, Vec3<float> rot)
{
    if (time < 0)
        throw "Illegal argument exception: time of a keyframe can not be lower "
              "than 0";
    if (maxTime < time) maxTime = time;
    frameCount++;
    KeyFrame* tmp = new KeyFrame[frameCount];
    bool added = 0;
    for (int i = 0; i < frameCount; i++)
    {
        if (i < frameCount - 1 && frames[i - (int)added].time <= time)
        {
            tmp[i] = frames[i];
        }
        else
        {
            if (!added)
            {
                tmp[i].time = time;
                tmp[i].pos = pos;
                tmp[i].rot = rot;
                added = 1;
            }
            else
            {
                tmp[i] = frames[i - 1];
            }
        }
    }
    delete[] frames;
    frames = tmp;
}

// adds a keyframe that is the same as the last keyframe with the time time
// \returns true if the keyframe was added successfully, false if time is maller
// than the current length of the animation
bool BoneAnimation::doNothingUntil(double time)
{
    if (time > maxTime)
    {
        KeyFrame& last = frames[frameCount - 1];
        addKeyFrame(time, last.pos, last.rot);
    }
    return 0;
}

// calculates the positions and rotation at the next time
// \param time the passed time in seconds since the last call
void BoneAnimation::tick(double time)
{
    if (frameCount <= 1) return;
    current.time += time;
    if (loop)
    {
        while (current.time >= maxTime)
        {
            current.time -= maxTime;
        }
    }
    else if (current.time > maxTime)
    {
        current.time = maxTime;
    }
    int last = -1;
    int next = -1;
    for (int i = 0; i < frameCount; i++)
    {
        last = next;
        next = i;
        if (frames[i].time > current.time)
        {
            break;
        }
    }
    float timePart = (float)(current.time - frames[last].time);
    current.pos
        = frames[last].pos + (frames[next].pos - frames[last].pos) * timePart;
    current.rot
        = frames[last].rot + (frames[next].rot - frames[last].rot) * timePart;
}

//! applys the animation on a given skeleton
//! \param zS: the sceleton
void BoneAnimation::apply(Skeleton* zSkelett) const
{
    Bone* zK = zSkelett->zBone(boneId);
    if (zK)
    {
        zK->setPosition(current.pos);
        zK->setRotation(current.rot);
    }
}

// returns true if the animation has reached the last keyframe and does
// not loop
bool BoneAnimation::isFinished() const
{
    return current.time >= maxTime;
}

// returns the bone id this animation is for
int BoneAnimation::getBoneId() const
{
    return boneId;
}

// returns the maximum time of the animation
double BoneAnimation::getMaxTime() const
{
    return maxTime;
}

//! Constructor
SkeletonAnimation::SkeletonAnimation()
    : ReferenceCounter(),
      loop(0)
{}

// make the animation to a loop that starts again if the end is reached
void SkeletonAnimation::setLoop(bool loop)
{
    this->loop = loop;
    for (BoneAnimation* animation : subAnimations)
    {
        animation->setLoop(loop);
    }
}

// adds an animation for a specific bone
// \param boneId the bone id
// \param originPos the position of the bone at the beginning of the animation
// \param originRot the rotation of the bone at the beginning of the animation
bool SkeletonAnimation::addAnimation(
    int boneId, Vec3<float> originPos, Vec3<float> originRot)
{
    for (BoneAnimation* animation : subAnimations)
    {
        if (animation->getBoneId() == boneId)
        {
            return 0;
        }
    }
    subAnimations.add(new BoneAnimation(boneId, originPos, originRot));
    return 1;
}

//! adds a keyframe for a specific bone of the sceleton
//! \param kId id of the bone
//! \param time the time in seconds since the start of the animation at
//! which the bone should have the given position and rotation relative
//! to its parent bone \param pos the position of the bone \param rot
//! the rotation of the bone
bool SkeletonAnimation::addKeyFrame(
    int boneId, double time, Vec3<float> pos, Vec3<float> rot)
{
    for (BoneAnimation* animation : subAnimations)
    {
        if (animation->getBoneId() == boneId)
        {
            animation->addKeyFrame(time, pos, rot);
            return 1;
        }
    }
    return 0;
}

//! applys the animation on a given skeleton
//! \param zS: the sceleton
void SkeletonAnimation::apply(Skeleton* zS) const
{
    for (BoneAnimation* animation : subAnimations)
    {
        animation->apply(zS);
    }
}

// calculates the positions and rotation at the next time
// \param time the passed time in seconds since the last call
void SkeletonAnimation::tick(double time)
{
    for (BoneAnimation* animation : subAnimations)
    {
        animation->tick(time);
    }
}

// returns true if the animation has reached the last keyframe and does
// not loop
bool SkeletonAnimation::isFinished() const
{
    for (BoneAnimation* animation : subAnimations)
    {
        if (!animation->isFinished()) return 0;
    }
    return 1;
}

// returns the maximum time of the animation
double SkeletonAnimation::getMaxTime() const
{
    double max = 0;
    for (BoneAnimation* animation : subAnimations)
    {
        max = MAX(animation->getMaxTime(), max);
    }
    return max;
}

// returns the animation for a specific bone or 0 if it does not exist
BoneAnimation* SkeletonAnimation::zAnimation(int boneId) const
{
    for (BoneAnimation* animation : subAnimations)
    {
        if (animation->getBoneId() == boneId) return animation;
    }
    return 0;
}