#include "precompiled.h"

#include "UnitAnimation.h"

#include "graphics/Model.h"
#include "graphics/SkeletonAnim.h"
#include "graphics/SkeletonAnimDef.h"
#include "graphics/Unit.h"
#include "ps/CStr.h"

namespace
{
    // Randomly modify the speed, so that units won't stay perfectly
    // synchronised if they're playing animations of the same length
    float DesyncSpeed(float speed)
    {
//      const float var = 0.05f; // max fractional variation from default
//      return speed * (1.f - var + 2.f*var*(rand(0, 256)/255.f));
        // TODO: enable this desyncing for cases where we don't care about
        // accurate looping, and just don't do it for e.g. projectile-launchers
        // where we do care
        return speed;
    }
}

CUnitAnimation::CUnitAnimation(CUnit& unit)
: m_Unit(unit), m_State("idle"), m_Looping(true), m_Speed(0.f), m_OriginalSpeed(0.f), m_TimeToNextSync(0.f)
{
}

void CUnitAnimation::SetAnimationState(const CStr& name, bool once, float speed, bool keepSelection)
{
    if (name == m_State)
        return;

    m_State = name;
    m_Looping = !once;
    m_Speed = m_OriginalSpeed = speed;
    m_TimeToNextSync = 0.f;

    if (! keepSelection)
        m_Unit.SetEntitySelection(name);

    m_Unit.SetRandomAnimation(m_State, !m_Looping, DesyncSpeed(m_Speed));
}

void CUnitAnimation::SetAnimationSync(float timeUntilActionPos)
{
    // We need to finish looping our animation at the specified time from now.
    // Assume it's playing at nearly the right speed, and we just need to perhaps
    // shift it a little bit to stay in sync.

    m_TimeToNextSync = timeUntilActionPos;

    CModel* model = m_Unit.GetModel();

    // Calculate the required playback speed so ActionPos coincides with timeUntilActionPos
    float currentPos = model->m_AnimTime / model->m_Anim->m_AnimDef->GetDuration();
    float length = (model->m_Anim->m_ActionPos -currentPos);
    if (length < 0.f)
        length += 1.f;
    float requiredSpeed = length / m_TimeToNextSync;

    // Shift in the right direction
    if (requiredSpeed > m_OriginalSpeed)
        m_Speed = std::min(requiredSpeed, m_OriginalSpeed*1.1f);
    else if (requiredSpeed < m_OriginalSpeed)
        m_Speed = std::max(requiredSpeed, m_OriginalSpeed*0.9f);

    model->m_AnimSpeed = m_Speed * model->m_Anim->m_AnimDef->GetDuration() * model->m_Anim->m_Speed;

    // TODO: this should use the ActionPos2, instead of totally ignoring it
    m_Unit.ShowAmmunition();
}

void CUnitAnimation::Update(float time)
{
    CModel* model = m_Unit.GetModel();

    // Choose a new random animation if we're going to loop
    if (m_Looping && model->NeedsNewAnim(time))
    {
        m_Unit.SetRandomAnimation(m_State, !m_Looping, DesyncSpeed(m_Speed));
        // TODO: this really ought to transition smoothly into the new animation,
        // instead of just cutting off the end of the previous one and jumping
        // straight into the new.
    }

    if (m_TimeToNextSync >= 0.0 && m_TimeToNextSync-time < 0.0)
        m_Unit.HideAmmunition();

    m_TimeToNextSync -= time;

    // TODO: props should get a new random animation once they loop, independent
    // of the object they're propped onto

    model->Update(time);
}