// 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)
        {
            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())
    {
        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);
}
