// Oleg Petruny proprietary.

#include "CrossyRoadManager.h"

#include "Camera/CameraComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/SplineComponent.h"
#include "EnhancedInputComponent.h"
#include "InputMappingContext.h"

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

ACrossyRoadManager::ACrossyRoadManager()
    : AMinigame()
{
    static ConstructorHelpers::FObjectFinder<UInputMappingContext> asset{ TEXT("/Script/EnhancedInput.InputMappingContext'/Game/Input/Minigame/IMC_Minigame_CrossyRoad.IMC_Minigame_CrossyRoad'") };
    context = asset.Object;

    auto root = CreateDefaultSubobject<USceneComponent>(TEXT("Scene"));

    playerPos = CreateDefaultSubobject<USceneComponent>(TEXT("PlayerPos"));
    playerPos->SetupAttachment(root);

    camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
    camera->SetupAttachment(root);

    mannequin = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Mannequin"));
    mannequin->SetupAttachment(root);
}

void ACrossyRoadManager::Start(APlayerBase* playerPawn, FMinigameEndCallback delegate)
{
    if(!playerPawn)
        return;

    AMinigame::Start(playerPawn, delegate);

    player->LockPlayer(FPlayerLock::All());
    player->SwitchToView(this);

    auto input = ACustomPlayerController::GetInput();
    for(auto& mapping : context.LoadSynchronous()->GetMappings())
    {
        if(mapping.Action->ActionDescription.EqualTo(FText::AsCultureInvariant(TEXT("Up"))))
            inputHandlers.Add(input->BindAction(mapping.Action, ETriggerEvent::Triggered, this, &ACrossyRoadManager::Up).GetHandle());
        else if(mapping.Action->ActionDescription.EqualTo(FText::AsCultureInvariant(TEXT("Down"))))
            inputHandlers.Add(input->BindAction(mapping.Action, ETriggerEvent::Triggered, this, &ACrossyRoadManager::Down).GetHandle());
        else if(mapping.Action->ActionDescription.EqualTo(FText::AsCultureInvariant(TEXT("Left"))))
            inputHandlers.Add(input->BindAction(mapping.Action, ETriggerEvent::Triggered, this, &ACrossyRoadManager::Left).GetHandle());
        else if(mapping.Action->ActionDescription.EqualTo(FText::AsCultureInvariant(TEXT("Right"))))
            inputHandlers.Add(input->BindAction(mapping.Action, ETriggerEvent::Triggered, this, &ACrossyRoadManager::Right).GetHandle());
    }

    playerPos->SetWorldLocation(lines[0]->GetLocationAtTime(lines[0]->Duration * playerLineTime, ESplineCoordinateSpace::World));
    mannequin->SetWorldLocation(playerPos->GetComponentLocation());

    for(auto line : lines)
        if(FMath::RandRange(0, 1))
            RotateLine(line);

    PrimaryActorTick.SetTickFunctionEnable(true);
}

void ACrossyRoadManager::End()
{
    PrimaryActorTick.SetTickFunctionEnable(false);

    for(int32 handler : inputHandlers)
        ACustomPlayerController::GetInput()->RemoveBindingByHandle(handler);

    player->UnlockPlayer(FPlayerLock::All());
    player->ReturnPlayerView();

    AMinigame::End();
}

void ACrossyRoadManager::Hit()
{
    if(ended)
        return;

    ended = true;

    if(score > 1000)
    {
        Finish();
        return;
    }

    result = EMinigameResult::Loss;
    End();
}

void ACrossyRoadManager::Finish()
{
    result = EMinigameResult::Win;
    End();
}

void ACrossyRoadManager::PostInitializeComponents()
{
    AMinigame::PostInitializeComponents();

    GetComponents<USplineComponent>(lines);
    for(int32 i = 0; i < lines.Num(); ++i)
    {
        int32 id = FCString::Atoi(*lines[i]->GetName().Mid(6));
        if(i != id - 1)
        {
            auto l = lines[i];
            lines[i] = lines[id - 1];
            lines[id - 1] = l;
        }

        linesRotated.Add(lines[i], false);
    }
}

void ACrossyRoadManager::Tick(float deltaTime)
{
    AMinigame::Tick(deltaTime);

    time += deltaTime;

    if(time >= spawnDelay / speed)
    {
        time = 0; //FMath::RandRange(0.0f, spawnDelay / 4 * speed); // (time -= spawnDelay) is not good on lags
        CreateObstacle();
    }

    for(int32 i = 0; i < obstacles.Num(); ++i)
    {
        if(obstacles[i].time > obstacles[i].line->Duration)
        {
            obstacles[i].actor->Destroy();
            obstacles.RemoveAt(i);
            //CreateObstacle();
        }
        else
        {
            obstacles[i].time += deltaTime * speed * obstacles[i].speed;
            obstacles[i].actor->SetActorLocation(obstacles[i].line->GetWorldLocationAtTime(obstacles[i].time));
        }
    }
}

void ACrossyRoadManager::CreateObstacle(int32 index)
{
    auto obstacleClass = obstacleClasses.CreateIterator();
    for(int32 i = 0; i < FMath::RandRange(0, obstacleClasses.Num() - 1); ++i) // std::advance not works :(
        ++obstacleClass;

    auto spline = lines[FMath::RandRange(0, lines.Num() - 1)];

    FActorSpawnParameters spawnParams{};
    spawnParams.Owner = this;
    auto obstacle = GetWorld()->SpawnActor<ACrossyRoadObstacle>(*obstacleClass, spline->GetComponentLocation(), GetActorRotation(), spawnParams);
    obstacle->manager = this;

    if(index < 0)
    {
        obstacles.Add({ .actor = obstacle, .line = spline, .time = 0, .speed = FMath::RandRange(0.2f, 1.0f) * speed });
    }
    else
    {
        obstacles[index].actor->Destroy();
        obstacles[index].actor = obstacle;
        obstacles[index].line = spline;
        obstacles[index].time = 0;
        obstacles[index].speed = FMath::RandRange(1.0f, 2.0f) * speed;
    }
}

void ACrossyRoadManager::RotateLine(USplineComponent* line)
{
    auto pos = line->GetComponentLocation();
    auto rot = line->GetComponentRotation();
    auto dist = line->GetWorldLocationAtSplinePoint(1) - line->GetWorldLocationAtSplinePoint(0);

    if(linesRotated[line])
    {
        line->SetWorldLocationAndRotation(pos - dist, FRotator{ rot.Pitch, rot.Yaw - 180, rot.Roll });
        linesRotated[line] = false;
    }
    else
    {
        line->SetWorldLocationAndRotation(pos + dist, FRotator{ rot.Pitch, rot.Yaw + 180, rot.Roll });
        linesRotated[line] = true;
    }

}

void ACrossyRoadManager::Up()
{
    auto line = lines[0];
    auto diffY = lines[0]->GetComponentLocation().Y - lines[1]->GetComponentLocation().Y;
    for(int32 i = 1; i < lines.Num(); ++i)
    {
        lines[i]->AddRelativeLocation(FVector{ 0, diffY, 0 });
        lines[i - 1] = lines[i];
    }
    line->AddRelativeLocation(FVector{ 0, (lines.Num() - 1) * diffY * -1, 0 });
    lines[lines.Num() - 1] = line;
    //if(FMath::RandRange(0, 1))
    //    RotateLine(line);

    speed += 0.15;
    score += 100 * speed;
}

void ACrossyRoadManager::Down()
{
    //
}

void ACrossyRoadManager::Left()
{
    if(playerLineTime >= 1.0f)
        return;

    playerLineTime += 0.1f;

    float lineTime = playerLineTime;
    if(linesRotated[lines[0]])
        lineTime = 1 - lineTime;

    playerPos->SetWorldLocation(lines[0]->GetLocationAtTime(lines[0]->Duration * lineTime, ESplineCoordinateSpace::World));
    mannequin->SetWorldLocation(playerPos->GetComponentLocation());
}

void ACrossyRoadManager::Right()
{
    if(playerLineTime <= 0.0f)
        return;

    playerLineTime -= 0.1f;

    float lineTime = playerLineTime;
    if(linesRotated[lines[0]])
        lineTime = 1 - lineTime;

    playerPos->SetWorldLocation(lines[0]->GetLocationAtTime(lines[0]->Duration * lineTime, ESplineCoordinateSpace::World));
    mannequin->SetWorldLocation(playerPos->GetComponentLocation());
}
