#include "ScriptedAnimation.h"
#include "Engine/Engine.h"
#include "Animation/AnimSequenceBase.h"

void FlxScriptedAnimation::InitiateFadeOut(double CurrentTime, double AllowedFade)
{
    // We only need to truncate if we aren't already fading out,
    // or if the in-progress fadeout isn't sufficiently fast.
    //
    double FadeOutAlpha = CalcFadeOutAlpha(CurrentTime);
    if ((FadeOutAlpha >= 1.0) || (FadeOutDuration > AllowedFade))
    {
        double CurrentAlpha = FMath::Min(CalcFadeInAlpha(CurrentTime), FadeOutAlpha);
        double NewFadeOutDuration = FMath::Min(AllowedFade, FadeOutDuration);
        double NewTimeLeft = NewFadeOutDuration * CurrentAlpha;
        EndTime = CurrentTime + NewTimeLeft;
        FadeOutDuration = NewFadeOutDuration;
    }
}

void UlxScriptedAnimations::Keep(int n)
{
	if (n < 0) n = 0;
	if (Animations.Num() > n)
	{
		Animations.SetNum(n);
	}
}

void UlxScriptedAnimations::AddAnimation(
    UObject* WorldContextObject, UAnimSequenceBase* Sequence, double FadeInTime, double FadeOutTime,
	int64 AnimationStepID, bool ContinueWhenPaused)
{
	check(KeepCount >= 1);
	FlxScriptedAnimation Result;

	// Get World Time
	FlxWorldClocks Clocks = UlxScriptedAnimationLibrary::GetAllWorldClocks(WorldContextObject);
	double CurrentTime = ContinueWhenPaused ? Clocks.RealTime : Clocks.WorldTime;

    // Get the animation Length.
    double Length = (Sequence ? static_cast<double>(Sequence->GetPlayLength()) : 0.0);

	// Fill the static setup fields
	Result.Sequence           = Sequence;
	Result.ContinueWhenPaused = ContinueWhenPaused;
	Result.AnimationStepID    = AnimationStepID;
	Result.FadeInDuration     = FadeInTime;
	Result.FadeOutDuration    = FadeOutTime;
	Result.StartTime          = CurrentTime;
	Result.EndTime            = CurrentTime + Length;

	Keep(KeepCount - 1);
	Animations.Insert(Result, 0);
}

double UlxScriptedAnimations::CalculateTimeLeft(UObject *WorldContextObject, int64 AnimationID)
{
	double max = 0.0;
	FlxWorldClocks Clocks = UlxScriptedAnimationLibrary::GetAllWorldClocks(WorldContextObject);
    for (int i = 0; i < Animations.Num(); i++)
	{
        const FlxScriptedAnimation &Anim = Animations[i];
		if (Anim.AnimationStepID == AnimationID)
		{
			double Current = Anim.ChooseCorrectClock(Clocks);
			double TimeLeft = Anim.ClampedTimeLeft(Current);
			if (TimeLeft > max) max = TimeLeft;
		}
	}
	return max;
}

void UlxScriptedAnimations::FadeGarbage(const UObject *WorldContextObject, TArray<int64> KeepIDs)
{
	FlxWorldClocks Clocks = UlxScriptedAnimationLibrary::GetAllWorldClocks(WorldContextObject);
    for (int i = 0; i < Animations.Num(); i++)
    {
        FlxScriptedAnimation &Anim = Animations[i];
        if ((Anim.AnimationStepID > 0) && (!KeepIDs.Contains(Anim.AnimationStepID)))
        {
            Anim.InitiateFadeOut(Anim.ChooseCorrectClock(Clocks), 0.2);
        }
    }
}

void UlxScriptedAnimationLibrary::ScriptedAnimationEvaluatorData(
		const UlxScriptedAnimations *Animations,
		const FlxWorldClocks &WorldClocks, 
		UAnimSequenceBase *&Sequence0, float &ExplicitTime0,
		UAnimSequenceBase *&Sequence1, float &ExplicitTime1,
		UAnimSequenceBase *&Sequence2, float &ExplicitTime2,
		float &BaseAlpha, float &Sequence0Alpha, float &Sequence1Alpha, float &Sequence2Alpha) 
{
	Sequence0 = nullptr;
	Sequence1 = nullptr;
	Sequence2 = nullptr;
	ExplicitTime0 = 0.0;
	ExplicitTime1 = 0.0;
	ExplicitTime2 = 0.0;
	BaseAlpha = 0.0;
	Sequence0Alpha = 0.0;
	Sequence1Alpha = 0.0;
	Sequence2Alpha = 0.0;
	
	if (Animations != nullptr)
	{
		const TArray<FlxScriptedAnimation> &Anims = Animations->GetAnimations();
		if (Anims.Num() > 0)
		{
            const FlxScriptedAnimation &Anim = Anims[0];
			double CurrentTime = Anim.ChooseCorrectClock(WorldClocks);
			Sequence0 = Anim.Sequence;
			ExplicitTime0 = Anim.ClampedElapsedTime(CurrentTime);
			Sequence0Alpha = Anim.CalcFadeInOutAlpha(CurrentTime);
		}
		if (Anims.Num() > 1)
		{
            const FlxScriptedAnimation &Anim = Anims[1];
			double CurrentTime = Anim.ChooseCorrectClock(WorldClocks);
			Sequence1 = Anim.Sequence;
			ExplicitTime1 = Anim.ClampedElapsedTime(CurrentTime);
			Sequence1Alpha = Anim.CalcFadeInOutAlpha(CurrentTime);
		}
		if (Anims.Num() > 2)
		{
            const FlxScriptedAnimation &Anim = Anims[2];
			double CurrentTime = Anim.ChooseCorrectClock(WorldClocks);
			Sequence2 = Anim.Sequence;
			ExplicitTime2 = Anim.ClampedElapsedTime(CurrentTime);
			Sequence2Alpha = Anim.CalcFadeInOutAlpha(CurrentTime);
		}
	}

	double AlphaTotal = Sequence0Alpha + Sequence1Alpha + Sequence2Alpha;
	if (AlphaTotal > 1.0)
	{
		double Scale = 1.0 / AlphaTotal;
		Sequence0Alpha *= Scale;
		Sequence1Alpha *= Scale;
		Sequence2Alpha *= Scale;
		BaseAlpha = 0.0;
	}
	else
	{
		BaseAlpha = 1.0 - AlphaTotal;
	}
}

FlxWorldClocks UlxScriptedAnimationLibrary::GetAllWorldClocks(const UObject *WorldContextObject)
{
	UWorld* World = GEngine->GetWorldFromContextObjectChecked(WorldContextObject);
	FlxWorldClocks Result;
	if (World != nullptr)
	{
		Result.WorldTime = World->GetTimeSeconds();
		Result.RealTime = World->GetRealTimeSeconds();
	}
	return Result;
}
