// Project Sword & Gun Copyright © 2021 Kevin Poretti


#include "Components/SNGStateMachineComponent.h"

// Sets default values for this component's properties
USNGStateMachineComponent::USNGStateMachineComponent()
{
    // Set this component to be initialized when the game starts, and to be ticked every frame.  You can turn these features
    // off to improve performance if you don't need them.
    PrimaryComponentTick.bCanEverTick = true;

    // ...
}

bool USNGStateMachineComponent::PushEvent(FSNGEvent Event)
{
    EventQueue.Enqueue(Event);
    return true;
}

USNGState* USNGStateMachineComponent::CreateAndInitializeState(TSubclassOf<USNGState> StateClass)
{
    USNGState* State = NewObject<USNGState>(this, StateClass); //, NAME_None, RF_MarkAsRootSet);
    State->SetOwner(GetOwner());
    State->CreateState();
    AddStateToMap(State);
    UE_LOG(LogTemp, Log, TEXT("Adding state: %s"), *State->GetName().ToString());
    for (auto SubStateClass : State->GetSubStateClasses())
    {
        USNGState* CurrSubState = CreateAndInitializeState(SubStateClass);
        State->AddSubState(CurrSubState);
        UE_LOG(LogTemp, Log, TEXT("Adding substate %s of state %s"), *CurrSubState->GetName().ToString(), *State->GetName().ToString());
    }
    return State;
}

void USNGStateMachineComponent::AddStateToMap(USNGState* State)
{
    if(!States.Contains(State->GetName()))
    {
        States.Add(State->GetName(), State);
    }
    else
    {
        UE_LOG(LogTemp, Warning, TEXT("SNGStateMachineComponent :: Trying to add a state to states map that already exists"));
    }
}

void USNGStateMachineComponent::TransitionToState(FName NextStateName)
{
    if(States.Contains(NextStateName))
    {
        UE_LOG(LogTemp, Log, TEXT("Transitioning to state %s"), *NextStateName.ToString());
        USNGState* NextState = States[NextStateName];
        USNGState* PreviousState = CurrentState;
        //CurrentState = NextState;
        PreviousState->ExitState();
        CurrentState = NextState->EnterState();
    }
    else
    {
        UE_LOG(LogTemp, Log,
            TEXT("Tried to transition to state %s from state %s but the state does not exist in the map"),
            *NextStateName.ToString(), *CurrentState->GetName().ToString());
    }
}

// Called when the game starts
void USNGStateMachineComponent::BeginPlay()
{
    Super::BeginPlay();

    if(RootStateClass)
    {
        USNGState* RootState = CreateAndInitializeState(RootStateClass);
        CurrentState = RootState->EnterState();
    }

#ifdef UE_BUILD_DEBUG
    UE_LOG(LogTemp, Log, TEXT("List of states in state machine:"));
    for (auto Pair : States)
    {
        UE_LOG(LogTemp, Log, TEXT("%s"), *Pair.Key.ToString());
    }
#endif
}

// Called every frame
void USNGStateMachineComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
    
    if(CurrentState)
    {
        FString DebugMsg = FString::Printf(TEXT("Current State: %s"), *CurrentState->GetName().ToString());
        GEngine->AddOnScreenDebugMessage(-1, 0.0f, FColor::Cyan, DebugMsg);
        DebugMsg = FString::Printf(TEXT("Time In State: %s"), *FString::SanitizeFloat(CurrentState->GetTimeInState()));
        GEngine->AddOnScreenDebugMessage(-1, 0.0f, FColor::Magenta, DebugMsg);

        CurrentState->UpdateState(DeltaTime);

        FName NextStateName = NAME_None;
        FSNGEvent CurrEvent;
        while(EventQueue.Dequeue(CurrEvent))
        {
            NextStateName = CurrentState->GetNextStateFromEvent(CurrEvent);
        }

        if(NextStateName != NAME_None &&
            (NextStateName != CurrentState->GetName() || CurrentState->CanTransitionToItself()))
        {
            TransitionToState(NextStateName);
            return;
        }
        
        NextStateName = CurrentState->GetNextState();
        if(NextStateName != NAME_None &&
            (NextStateName != CurrentState->GetName() || CurrentState->CanTransitionToItself()))
        {
            TransitionToState(NextStateName);
        }
    }
}

