// Oleg Petruny proprietary.

#include "QuickTimeEvent.h"

#include "Kismet/GameplayStatics.h"

#include "CommonFunctions.h"
#include "CustomPlayerController.h"
#include "MainGameModeBase.h"
#include "PlayerBase.h"
#include "Widgets/WidgetsManager.h"

int32 Event::counter = 0;

Event::Event()
{
    id = counter++;
}

Event::Event(EQuickTimeEventType type, FKey& key, FQuickTimeEventEndCallback& callback, bool sequence)
    : Event()
{
    this->type = type;
    this->key = key;
    this->callback = callback;
    this->sequence = sequence;
}

inline int32 Event::GetId()
{
    return id;
}

void UQuickTimeEventManager::ShowQuickTimeEvent(FQuickTimeEventEnqueProperties properties)
{
    UE_LOG(LogTemp, Log, TEXT("ShowQuickTimeEvent Start"));
    OnFirstEventInit();
    FScopeLock lock1(&lock);
    CreateEvent(properties, false);
    UE_LOG(LogTemp, Log, TEXT("ShowQuickTimeEvent End"));
}

void UQuickTimeEventManager::EnqueQuickTimeEvent(FQuickTimeEventEnqueProperties properties)
{
    UE_LOG(LogTemp, Log, TEXT("EnqueQuickTimeEvent Start"));
    OnFirstEventInit();
    {
        FScopeLock lock1(&lock);
        nextEvents.Enqueue(properties);
    }
    ShowNextEvent();
    UE_LOG(LogTemp, Log, TEXT("EnqueQuickTimeEvent End"));
}

void UQuickTimeEventManager::ShowNextEvent()
{
    UE_LOG(LogTemp, Log, TEXT("ShowNextEvent Start"));
    FScopeLock lock1(&lock);
    FQuickTimeEventEnqueProperties properties;
    nextEvents.Dequeue(properties);
    CreateEvent(properties, true);
    UE_LOG(LogTemp, Log, TEXT("ShowNextEvent End"));
}

void UQuickTimeEventManager::OnEventEnd(int32 id, EQuickTimeEventResult result)
{
    UE_LOG(LogTemp, Log, TEXT("OnEventEnd Start"));
    FScopeLock lock1(&lock);
    Event event;
    if(!events.RemoveAndCopyValue(id, event))
        return;

    GetWorld()->GetTimerManager().ClearTimer(event.timer);

    if(event.callback.IsBound())
        event.callback.Execute(result);

    if(event.sequence && !nextEvents.IsEmpty())
    {
        ShowNextEvent();
        UE_LOG(LogTemp, Log, TEXT("OnEventEnd End"));
        return;
    }

    if(events.IsEmpty())
    {
        if(auto PC = ACustomPlayerController::Get())
        {
            PC->onAnyKeyPressed.RemoveDynamic(this, &UQuickTimeEventManager::OnInputPressed);
            PC->onAnyKeyReleased.RemoveDynamic(this, &UQuickTimeEventManager::OnInputReleased);
        }
        UCommonFunctions::ExitSlowMotion();
    }
    UE_LOG(LogTemp, Log, TEXT("OnEventEnd End"));
}

void UQuickTimeEventManager::OnInput(const FKey& key, bool released)
{
    for(auto eventMapping : events)
    {
        if(eventMapping.Value.key != key)
            continue;

        EQuickTimeEventResult result = EQuickTimeEventResult::Success;

        if(auto WM = AMainGameModeBase::GetWidgetsManager())
        {
            result = WM->ProcessQuickTimeEvent(eventMapping.Key, released);

            switch(result)
            {
                case EQuickTimeEventResult::FailedKey: // its not a failed key, but special state to do nothing (eg. holding event)
                    return;
                default:
                    // the result is success or failed time and widget is self removed
                    break;
            }
        }

        OnEventEnd(eventMapping.Key, result);
        return;
    }

    if(released)
        return;

    if(auto WM = AMainGameModeBase::GetWidgetsManager())
    {
        while(events.Num() > 0)
        {
            WM->RemoveQuickTimeEvent(events.begin()->Key);
            OnEventEnd(events.begin()->Key, EQuickTimeEventResult::FailedKey);
        }
    }
    else
    {
        while(events.Num() > 0)
            OnEventEnd(events.begin()->Key, EQuickTimeEventResult::FailedKey);
    }
}

void UQuickTimeEventManager::OnInputPressed(FKey key)
{
    OnInput(key, false);
}

void UQuickTimeEventManager::OnInputReleased(FKey key)
{
    OnInput(key, true);
}

void UQuickTimeEventManager::OnFirstEventInit()
{
    if(!events.IsEmpty())
        return;

    if(auto PC = ACustomPlayerController::Get())
    {
        GetWorld()->GetTimerManager().SetTimerForNextTick([=, this]()
            {
                PC->onAnyKeyPressed.AddDynamic(this, &UQuickTimeEventManager::OnInputPressed);
                PC->onAnyKeyReleased.AddDynamic(this, &UQuickTimeEventManager::OnInputReleased);
            });
    }
    UCommonFunctions::EnterSlowMotion();
}

void UQuickTimeEventManager::CreateEvent(FQuickTimeEventEnqueProperties& properties, bool sequence)
{
    Event event{ properties.type, properties.key, properties.callback, sequence };
    GetWorld()->GetTimerManager().SetTimer(event.timer, [id = event.GetId(), this]()
        {
            EQuickTimeEventResult result = EQuickTimeEventResult::FailedTime;
            if(auto WM = AMainGameModeBase::GetWidgetsManager())
                result = WM->RemoveQuickTimeEvent(id);
            this->OnEventEnd(id, result);
        }, properties.duration, false);
    events.Add(event.GetId(), event);

    if(auto WM = AMainGameModeBase::GetWidgetsManager())
        WM->ShowQuickTimeEvent(event.GetId(), properties);
}
