// Fill out your copyright notice in the Description page of Project Settings.

#include "LevelHandler.h"

// Engine headers.
#include "Kismet/GameplayStatics.h"
#include "Sound/SoundCue.h"

// Paper2D plugin headers.
#include "PaperFlipbook.h"
#include "PaperFlipbookComponent.h"

// Custom game headers.
#include "GameJamTemplate.h"
#include "JamGameMode.h"
#include "SoundManager.h"
#include "Spawners/TargetSpawner.h"
#include "Spawners/AmmoSpawner.h"

ALevelHandler::ALevelHandler():
	ObjectName(GetName())
{
	PrimaryActorTick.bCanEverTick = true;

	RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));

	PaperFlipbook = CreateDefaultSubobject<UPaperFlipbookComponent>(TEXT("PaperFlipbook"));
	PaperFlipbook->SetupAttachment(RootComponent);

	bIsRoundPlaying = false;
	bIsGameOver = false;
	bIsShowingTutorial = false;
	bIsCurtainDown = false;
	bIsCurtainClosing = false;
	bIsCurtainOpening = false;
	NextRound = 1;
	ElapsedTime = 0.0f;
	CurrentScore = 0;
	MaxNumberOfPenalties = 0;
	NumberOfPenalties = 0;
}

void ALevelHandler::BeginPlay()
{
	Super::BeginPlay();

	AGameModeBase* GameModeBase = UGameplayStatics::GetGameMode(GetWorld());

	if (GameModeBase != nullptr)
	{
		JamGameMode = Cast<AJamGameMode>(GameModeBase);

		if (JamGameMode == nullptr)
		{
			UE_LOG(LogGameJam, Error,
				TEXT("[%s] Could not cast game mode."),
				*ObjectName);
		}
	}
	else
	{
		UE_LOG(LogGameJam, Error,
			TEXT("[%s] Could not get game mode."),
			*ObjectName);
	}
	
	TargetSpawner = (ATargetSpawner*)UGameplayStatics::GetActorOfClass(GetWorld(), ATargetSpawner::StaticClass());

	if (TargetSpawner == nullptr)
	{
		UE_LOG(LogGameJam, Error,
			TEXT("[%s] Could not find target spawner in level."),
			*ObjectName);
	}

	LevelAmmoSpawner = (AAmmoSpawner*)UGameplayStatics::GetActorOfClass(GetWorld(), AAmmoSpawner::StaticClass());

	if (LevelAmmoSpawner == nullptr)
	{
		UE_LOG(LogGameJam, Error,
			TEXT("[%s] Could not find ammo spawner in level."),
			*ObjectName);
	}
}

void ALevelHandler::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (bIsCurtainClosing && !PaperFlipbook->IsPlaying())
	{
		bIsCurtainDown = true;
		bIsCurtainClosing = false;
	}

	if (bIsCurtainOpening && !PaperFlipbook->IsPlaying())
	{
		bIsCurtainOpening = false;
		StartRound();
	}

	if (bIsRoundPlaying)
	{
		ElapsedTime += DeltaTime;

		if (ElapsedTime >= CurrentRound.Duration)
		{
			StopRound();
		}
	}
}

bool ALevelHandler::IsRoundPlaying()
{
	return bIsRoundPlaying;
}

bool ALevelHandler::IsGameOver()
{
	return bIsGameOver;
}

bool ALevelHandler::IsShowingTutorial()
{
	return bIsShowingTutorial;
}

bool ALevelHandler::IsBlueAmmoEnabled()
{
	if (!bIsRoundPlaying)
	{
		return 0;
	}

	return CurrentRound.bIsBlueAmmoEnabled;
}

bool ALevelHandler::IsBlueSpreadEnabled()
{
	if (!bIsRoundPlaying)
	{
		return 0;
	}

	return CurrentRound.bIsBlueSpreadEnabled;
}

bool ALevelHandler::IsRedSpreadEnabled()
{
	if (!bIsRoundPlaying)
	{
		return 0;
	}

	return CurrentRound.bIsRedSpreadEnabled;
}

bool ALevelHandler::IsCurtainDown()
{
	return bIsCurtainDown;
}

bool ALevelHandler::IsCurtainOpening()
{
	return bIsCurtainOpening;
}

int ALevelHandler::GetRemainingTime()
{
	if (!bIsRoundPlaying)
	{
		return 0;
	}

	return FMath::CeilToInt(CurrentRound.Duration - ElapsedTime);
}

int ALevelHandler::GetCurrentScore()
{
	return CurrentScore;
}

int ALevelHandler::GetNumberOfPenalties()
{
	return NumberOfPenalties;
}

FString ALevelHandler::GetTutorialText()
{
	return CurrentRound.TutorialText;
}

void ALevelHandler::Reset()
{
	bIsRoundPlaying = false;
	bIsGameOver = false;
	bIsShowingTutorial = false;
	bIsCurtainDown = false;
	bIsCurtainClosing = false;
	bIsCurtainOpening = false;
	NextRound = 1;
	ElapsedTime = 0.0f;
	CurrentScore = 0;
	MaxNumberOfPenalties = 0;
	NumberOfPenalties = 0;
}

void ALevelHandler::StartRound()
{
	if (bIsGameOver)
	{
		UE_LOG(LogGameJam, Warning,
			TEXT("[%s] Cannot start the next round since the game is over."),
			*ObjectName);
		return;
	}

	if (bIsCurtainDown)
	{
		UE_LOG(LogGameJam, Warning,
			TEXT("[%s] Cannot start the next round while the curtains are down."),
			*ObjectName);
		return;
	}

	if (bIsRoundPlaying)
	{
		UE_LOG(LogGameJam, Warning,
			TEXT("[%s] A round is already being played."),
			*ObjectName);
		return;
	}

	CurrentRound = GetRound(NextRound++);

	if (CurrentRound.bHasTutorial)
	{
		ShowTutorial();
	}
	else
	{
		PlayRound();
	}
}

void ALevelHandler::PlayRound()
{
	if (bIsGameOver)
	{
		UE_LOG(LogGameJam, Warning,
			TEXT("[%s] Cannot play the next round since the game is over."),
			*ObjectName);
		return;
	}

	if (bIsCurtainDown)
	{
		UE_LOG(LogGameJam, Warning,
			TEXT("[%s] Cannot start the next round while the curtains are down."),
			*ObjectName);
		return;
	}

	if (bIsRoundPlaying)
	{
		UE_LOG(LogGameJam, Warning,
			TEXT("[%s] A round is already being played."),
			*ObjectName);
		return;
	}

	HideTutorial();

	bIsRoundPlaying = true;
	ElapsedTime = 0.0f;
	NumberOfPenalties = 0;

	LevelAmmoSpawner->StartingRoundByIndex(NextRound - 1);

	if (TargetSpawner != nullptr)
	{
		TargetSpawner->SetNumberOfLanes(CurrentRound.NumberOfLanes);
		TargetSpawner->SetSpawningParameters(CurrentRound.SpawningInterval, CurrentRound.DeltaInterval);
		TargetSpawner->SetTargetMovementSpeed(CurrentRound.TargetMovementSpeed);
		TargetSpawner->Start();
		UE_LOG(LogGameJam, Log,
			TEXT("[%s] Starting round: '%d'."),
			*ObjectName, NextRound - 1);
	}
}

void ALevelHandler::StopRound()
{
	bIsRoundPlaying = false;

	if (NextRound > Rounds.Num())
	{
		// End the game if the player completed all of the levels.
		bIsGameOver = true;
	}

	if (TargetSpawner != nullptr)
	{
		TargetSpawner->Stop();
		UE_LOG(LogGameJam, Log,
			TEXT("[%s] Stopping round."),
			*ObjectName);
	}

	LevelAmmoSpawner->EndingRound();

	CloseCurtain();
}

void ALevelHandler::AddPoint(int NumberOfDevils, bool bApplyCombo)
{
	CurrentScore += bApplyCombo ? NumberOfDevils * NumberOfDevils : NumberOfDevils;
}

void ALevelHandler::AddPenalty(int NumberOfAngels)
{
	NumberOfPenalties += NumberOfAngels;

	// Check if the player has lost the game.
	if (NumberOfPenalties > MaxNumberOfPenalties)
	{
		UE_LOG(LogGameJam, Log,
			TEXT("[%s] Maximum number of penalties reached."),
			*ObjectName);
		bIsGameOver = true;
		StopRound();
	}
}

FGameRound ALevelHandler::GetRound(int RoundNumber)
{
	if (RoundNumber <= 0 || RoundNumber > Rounds.Num())
	{
		UE_LOG(LogGameJam, Error,
			TEXT("[%s] Round '%d' does not exist."),
			*ObjectName, RoundNumber);
		return FGameRound();
	}

	return Rounds[RoundNumber - 1];
}

void ALevelHandler::ShowTutorial()
{
	bIsShowingTutorial = true;
}

void ALevelHandler::HideTutorial()
{
	bIsShowingTutorial = false;
}

void ALevelHandler::CloseCurtain()
{
	bIsCurtainClosing = true;

	PaperFlipbook->SetFlipbook(CurtainCloseAnimation);
	PaperFlipbook->SetLooping(false);
	PaperFlipbook->PlayFromStart();

	if (JamGameMode != nullptr)
	{
		USoundManager* SoundManager = JamGameMode->GetSoundManager();

		if (SoundManager != nullptr)
		{
			SoundManager->PlaySound(EGameSoundType::Effect, CurtainCloseSound, PaperFlipbook->GetComponentLocation());
		}
	}
}

void ALevelHandler::OpenCurtain()
{
	bIsCurtainOpening = true;
	bIsCurtainDown = false;

	PaperFlipbook->SetFlipbook(CurtainOpenAnimation);
	PaperFlipbook->SetLooping(false);
	PaperFlipbook->PlayFromStart();

	if (JamGameMode != nullptr)
	{
		USoundManager* SoundManager = JamGameMode->GetSoundManager();

		if (SoundManager != nullptr)
		{
			SoundManager->PlaySound(EGameSoundType::Effect, CurtainOpenSound, PaperFlipbook->GetComponentLocation());
		}
	}
}
