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

#include "TargetSpawner.h"

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

// Custom game headers.
#include "../Actors/Lane.h"
#include "../Actors/Target.h"
#include "../GameJamTemplate.h"

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

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

	bIsSpawning = false;

	NumberOfLanes = 0;

	SpawningInterval = 0.0f;
	DeltaInterval = 0.0f;
	ElapsedTime = 0.0f;
	TimeTillNextSpawn = 0.0f;

	TargetMovementSpeed = 0.0f;
}

void ATargetSpawner::BeginPlay()
{
	Super::BeginPlay();
	
	FindLanes();
}

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

	if (bIsSpawning)
	{
		Spawn(DeltaTime);
	}
}

void ATargetSpawner::SetNumberOfLanes(int NewNumberOfLanes)
{
	if (NewNumberOfLanes <= 0.0f)
	{
		UE_LOG(LogGameJam, Error,
			TEXT("[%s] The number of lanes must be larger than zero.'"),
			*ObjectName);
		return;
	}

	NumberOfLanes = NewNumberOfLanes;
}

void ATargetSpawner::SetSpawningParameters(float NewSpawningInterval, float NewDeltaInterval)
{
	if (NewSpawningInterval < 0.0f || NewDeltaInterval < 0.0f)
	{
		UE_LOG(LogGameJam, Error,
			TEXT("[%s] The spawning intervals must be larger than or equal to zero.'"),
			*ObjectName);
		return;
	}

	SpawningInterval = NewSpawningInterval;
	DeltaInterval = NewDeltaInterval;

	if (DeltaInterval >= SpawningInterval)
	{
		UE_LOG(LogGameJam, Error,
			TEXT("[%s] The delta interval (%f) cannot be larger than the spawning interval (%f).'"),
			*ObjectName, DeltaInterval, SpawningInterval);
		DeltaInterval = 0.0f;
	}
}

void ATargetSpawner::SetTargetMovementSpeed(float NewTargetMovementSpeed)
{
	if (NewTargetMovementSpeed < 0.0f)
	{
		UE_LOG(LogGameJam, Error,
			TEXT("[%s] The target movement speed must be larger than or equal to zero.'"),
			*ObjectName);
		return;
	}

	TargetMovementSpeed = NewTargetMovementSpeed;
}

void ATargetSpawner::Start()
{
	if (bIsSpawning)
	{
		UE_LOG(LogGameJam, Warning,
			TEXT("[%s] The spawner is already spawning targets."),
			*ObjectName);
	}

	bIsSpawning = true;
	ElapsedTime = 0.0f;
	TimeTillNextSpawn = 0.0f;

	// Randomly decides the direction from which the lanes will be spawning targets
	// at the beginning or each round. Switch direction for every other lane.
	bool bLeftToRight = FMath::RandRange(0, 1) ? true : false;

	for (auto& Lane : Lanes)
	{
		int LaneId = Lane.Value->GetLaneID();
		Lane.Value->SetSpawningDirection(LaneId % 2 == 0 ? bLeftToRight : !bLeftToRight);
	}
}

void ATargetSpawner::Stop()
{
	bIsSpawning = false;

	for (auto& Lane : Lanes)
	{
		Lane.Value->Empty();
	}
}

void ATargetSpawner::FindTargets(TArray<ATarget*>* TargetsInRange, FVector CenterOfBlast, float BlastRadius)
{
	if (TargetsInRange == nullptr)
	{
		return;
	}

	for (auto& Lane : Lanes)
	{
		TArray<ATarget*> LaneTargets;
		Lane.Value->FindTargets(&LaneTargets, CenterOfBlast, BlastRadius);
		TargetsInRange->Append(LaneTargets);
	}
}

void ATargetSpawner::FindLanes()
{
	TArray<AActor*> ActorsToFind;
	UGameplayStatics::GetAllActorsOfClass(GetWorld(), ALane::StaticClass(), ActorsToFind);

	for (AActor* Actor : ActorsToFind)
	{
		ALane* Lane = Cast<ALane>(Actor);

		if (Lane)
		{
			int LaneID = Lane->GetLaneID();

			if (!Lanes.Contains(LaneID))
			{
				Lanes.Add(LaneID, Lane);
				Lane->SetTargetSpawner(this);
			}
			else
			{
				UE_LOG(LogGameJam, Error,
					TEXT("[%s] A lane with the ID '%d' already exists.'"),
					*ObjectName, LaneID);
			}
		}
	}
}

void ATargetSpawner::Spawn(float DeltaTime)
{
	ElapsedTime += DeltaTime;

	if (ElapsedTime >= TimeTillNextSpawn)
	{
		ElapsedTime -= TimeTillNextSpawn;

		int LaneID = FMath::RandRange(1, NumberOfLanes);
		ALane** Lane = Lanes.Find(LaneID);

		if (Lane != nullptr)
		{
			uint8 Heads = FMath::RandRange(0, 1);
			uint8 Tails = !Heads;
			(*Lane)->Spawn(Heads, Tails, TargetMovementSpeed);
		}
		else
		{
			UE_LOG(LogGameJam, Error,
				TEXT("[%s] Cannot find lane with ID: '%d'.'"),
				*ObjectName, LaneID);
		}

		TimeTillNextSpawn = FMath::RandRange(SpawningInterval - DeltaInterval, SpawningInterval + DeltaInterval);
	}
}
