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

#include "Target.h"

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

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

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

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

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

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

	HitComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("HitComponent"));
	HitComponent->SetupAttachment(RootComponent);

	JamGameMode = nullptr;
	SoundManager = nullptr;
	TargetSpawner = nullptr;
	Lane = nullptr;

	PreviousState = ETargetState::Moving;
	CurrentState = ETargetState::Moving;

	Heads = EFaceType::Angel;
	Tails = EFaceType::Devil;
	bIsFacingHeads = true;

	MovementSpeed = 0.0f;
	MovementDirection = FVector::ZeroVector;
}

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

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

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

		if (JamGameMode != nullptr)
		{
			SoundManager = JamGameMode->GetSoundManager();
		}
	}
	
	if (JamGameMode == nullptr)
	{
		UE_LOG(LogGameJam, Error,
			TEXT("[%s] Could not get game mode."),
			*ObjectName);
	}

	LevelHandler = (ALevelHandler*)UGameplayStatics::GetActorOfClass(GetWorld(), ALevelHandler::StaticClass());

	if (LevelHandler == nullptr)
	{
		UE_LOG(LogGameJam, Error,
			TEXT("[%s] Could not find the level handler."),
			*ObjectName);
	}

	ChangeState(ETargetState::Moving);
	PlayAnimation(GetMovingAnimation(), true);
}

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

	switch (CurrentState)
	{
	case ETargetState::Flipping:
		if (!PaperFlipbook->IsPlaying())
		{
			ChangeState(ETargetState::Moving);
			PlayAnimation(GetMovingAnimation(), true);
		}
		Move(DeltaTime);
		break;
	case ETargetState::BeingShot:
		if (!PaperFlipbook->IsPlaying())
		{
			if (Lane)
			{
				Lane->Remove(this);
			}

			Destroy();
		}
		break;
	default:
	case ETargetState::Moving:
		Move(DeltaTime);
		break;
	}

	PreviousState = CurrentState;
}

EFaceType ATarget::GetCurrentFace()
{
	return bIsFacingHeads ? Heads : Tails;
}

void ATarget::SetTargetSpawner(ATargetSpawner* NewTargetSpawner)
{
	TargetSpawner = NewTargetSpawner;
}

void ATarget::SetLane(class ALane* NewLane)
{
	Lane = NewLane;
}

void ATarget::SetFaces(EFaceType NewHeads, EFaceType NewTails)
{
	Heads = NewHeads;
	Tails = NewTails;
}

void ATarget::SetMovementParameters(float NewMovementSpeed, FVector NewMovementDirection)
{
	MovementSpeed = NewMovementSpeed;
	MovementDirection = NewMovementDirection;
}

void ATarget::Shoot(float BlastRadius, bool bCountPoints)
{
	if (CurrentState == ETargetState::Flipping || CurrentState == ETargetState::BeingShot)
	{
		return;
	}

	ChangeState(ETargetState::BeingShot);
	PlayAnimation(GetBeingShotAnimation(), false);

	if (SoundManager != nullptr)
	{
		SoundManager->PlaySound(EGameSoundType::Effect, TargetShotSound, GetActorLocation());
	}

	if (BlastRadius > 0.0f && TargetSpawner != nullptr)
	{
		// Find all of the targets within the blast radius.
		TArray<ATarget*> TargetsInRange;
		TargetSpawner->FindTargets(&TargetsInRange, GetActorLocation(), BlastRadius);

		int NumberOfAngels = 0;
		int NumberOfDevils = 0;

		// Go through all of the targets in the blast radius and
		// check which types of targets were shot.
		for (auto& Target : TargetsInRange)
		{
			EFaceType CurrentFace = Target->GetCurrentFace();
			NumberOfAngels += CurrentFace == EFaceType::Angel ? 1 : 0;
			NumberOfDevils += CurrentFace == EFaceType::Devil ? 1 : 0;

			if (Target != this)
			{
				// Shot down the target in the blast radius, but don't propagate further.
				Target->Shoot(0.0f, false);
			}
		}

		UpdateScore(NumberOfAngels, NumberOfDevils);
	}
	else if (bCountPoints)
	{
		// If the shot is only limited to this target,
		// just update the player's total score.
		EFaceType CurrentFace = bIsFacingHeads ? Heads : Tails;
		UpdateScore(CurrentFace == EFaceType::Angel ? 1 : 0, CurrentFace == EFaceType::Devil ? 1 : 0);
	}
}

void ATarget::Flip(bool bFlipRight, float BlastRadius)
{
	if (CurrentState == ETargetState::Flipping || CurrentState == ETargetState::BeingShot)
	{
		return;
	}

	ChangeState(ETargetState::Flipping);
	PlayAnimation(GetFlippingAnimation(bFlipRight), false);
	bIsFacingHeads = !bIsFacingHeads;

	if (SoundManager != nullptr)
	{
		SoundManager->PlaySound(EGameSoundType::Effect, TargetFlippedSound, GetActorLocation());
	}

	if (BlastRadius > 0.0f && TargetSpawner != nullptr)
	{
		// Find all of the targets within the blast radius.
		TArray<ATarget*> TargetsInRange;
		TargetSpawner->FindTargets(&TargetsInRange, GetActorLocation(), BlastRadius);

		for (auto& Target : TargetsInRange)
		{
			if (Target != this)
			{
				// Flip the target in the blast radius, but don't propagate further.
				Target->Flip(bFlipRight, 0.0f);
			}
		}
	}
}

void ATarget::ChangeState(ETargetState NewState)
{
	CurrentState = NewState;
	UE_LOG(LogGameJam, Verbose,
		TEXT("[%s] Changing target state from '%s' to '%s.'"),
		*ObjectName,
		*(UEnum::GetValueAsString<ETargetState>(PreviousState)),
		*(UEnum::GetValueAsString<ETargetState>(CurrentState)));
}

void ATarget::PlayAnimation(UPaperFlipbook* Animation, bool bLoop)
{
	PaperFlipbook->SetFlipbook(Animation);
	PaperFlipbook->SetLooping(bLoop);
	PaperFlipbook->PlayFromStart();
}

UPaperFlipbook* ATarget::GetAnimation(const TMap<EFaceType, UPaperFlipbook*>& Animations)
{
	EFaceType CurrentFace = bIsFacingHeads ? Heads : Tails;
	UPaperFlipbook** Animation = (UPaperFlipbook**)Animations.Find(CurrentFace);

	if (Animation != nullptr)
	{
		return *Animation;
	}
	else
	{
		UE_LOG(LogGameJam, Error,
			TEXT("[%s] Could not find animation for face type: '%s'."),
			*ObjectName,
			*(UEnum::GetValueAsString<EFaceType>(CurrentFace)));
		return nullptr;
	}
}

UPaperFlipbook* ATarget::GetMovingAnimation()
{
	return GetAnimation(MovingAnimations);
}

UPaperFlipbook* ATarget::GetFlippingAnimation(bool bFlipRight)
{
	return GetAnimation(bFlipRight ? FlippingRightAnimations : FlippingLeftAnimations);
}

UPaperFlipbook* ATarget::GetBeingShotAnimation()
{
	return GetAnimation(BeingShotAnimations);
}

void ATarget::Move(float DeltaTime)
{
	FVector NewLocation = GetActorLocation();
	NewLocation += MovementDirection * MovementSpeed * DeltaTime;
	SetActorLocation(NewLocation);
}

void ATarget::UpdateScore(int NumberOfAngels, int NumberOfDevils)
{
	if (LevelHandler != nullptr)
	{
		LevelHandler->AddPoint(NumberOfDevils, NumberOfAngels <= 0);
		LevelHandler->AddPenalty(NumberOfAngels);
	}
}
