// Project Lab - NHTV Igad

#include "UnrealProject.h"
#include "CharacterCarousel.h"
#include "CharacterBase.h"
#include "DefaultGameInstance.h"



ACharacterCarousel::ACharacterCarousel()
{
	PrimaryActorTick.bCanEverTick = true;

	selectedCharacterIndex = 0;
	m_visualSelected = 0;
	selected = false;

	m_zoom = 0;

	selectedCharacterOffset = FVector(30, -120, 0);
}


void ACharacterCarousel::BeginPlay()
{
	check(IsValid(characterBlueprint));

	Super::BeginPlay();
	
	// Fix the rotation
	FRotator rotation = GetActorRotation();
	SetActorRotation(FRotator(0, rotation.Yaw, 0));

	const FVector position = GetActorLocation();
	const FVector forward = GetActorForwardVector();
	const FVector right = GetActorRightVector();

	UDefaultGameInstance* instance = Cast<UDefaultGameInstance>(GetGameInstance());
	check(instance);
	UCharacterSettings* settings = instance->GetCharacterSettings();
	check(settings);
	
	for (int32 i = 0; i < settings->characterSaves.Num(); i++)
	{
		FActorSpawnParameters spawnParams;
		spawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
		ACharacterBase* actor = GetWorld()->SpawnActor<ACharacterBase>(characterBlueprint, GetActorLocation(), GetActorRotation(), spawnParams);
		m_characters.Add(actor);
	}
}
void ACharacterCarousel::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);

	for (int32 i = 0; i < m_characters.Num(); i++)
	{
		if (IsValid(m_characters[i]))
			m_characters[i]->Destroy();
	}
	m_characters.SetNum(0);
}


inline float MoveTowardsTarget(float current, float target, float delta, float min = 0.0005f)
{
	const int32 moveDir = target > current ? 1 : -1;
	float d = FMath::Abs((target - current) * delta);
	if (d < 0.0005f) d = 0.0005f;

	current += d * moveDir;

	// Clamp
	current = (moveDir == 1) ?
	(current > target ? target : current) :
	(current < target ? target : current);

	return current;
}
void ACharacterCarousel::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (m_characters.Num() == 0)
		return;

	// Zoom towards the target
	const float zoomTarget = selected ? 1 : 0;
	m_zoom = MoveTowardsTarget(m_zoom, zoomTarget, DeltaTime * 10);

	const FVector position = GetActorLocation();
	const FVector forward = GetActorForwardVector();
	const FVector right = GetActorRightVector();

	const float carouselRadius = 1200;
	const float carouselAngle = 10;
	const FVector pivot = position - forward * carouselRadius;

	// Clamp the selected index (may be altered from a blueprint)
	selectedCharacterIndex = selectedCharacterIndex < 0 ? 0 : selectedCharacterIndex >= m_characters.Num() ? m_characters.Num() - 1 : selectedCharacterIndex;

	// Animate the carousel
	const float target = float(selectedCharacterIndex);
	m_visualSelected = MoveTowardsTarget(m_visualSelected, target, DeltaTime * (selected ? 10 : 5));
	
	// Spawn and set the characters
	const FRotator rotation = GetActorRotation();
	const FVector arm = forward * carouselRadius;
	const FVector selectedOffset = rotation.RotateVector(selectedCharacterOffset) * m_zoom;

	for (int32 i = 0; i < m_characters.Num(); i++)
	{
		const int32 distance = i - selectedCharacterIndex;
		if (distance < -2 || distance > 2)
		{
			// Dont render characters off screen
			m_characters[i]->GetRootComponent()->SetVisibility(false, true);
		}
		else
		{
			m_characters[i]->GetRootComponent()->SetVisibility(true, true);

			// Set the position relative from the pivot
			FRotator rotator(0, -carouselAngle * (float(i) - m_visualSelected), 0);
			if (m_zoom > 0) rotator.Yaw *= (m_zoom + 1);
			FVector offset = rotator.RotateVector(arm);
			if (i == selectedCharacterIndex)
				offset += selectedOffset;
			m_characters[i]->SetActorLocation(pivot + offset);
			m_characters[i]->SetActorRotation(rotator + FRotator(0, rotation.Yaw, 0));
		}
	}
}


void ACharacterCarousel::Reset()
{
	selectedCharacterIndex = 0;
	m_visualSelected = 0;
	selected = false;

	m_zoom = 0;
}

void ACharacterCarousel::Next()
{
	if(!selected && m_characters.Num() > 0 && selectedCharacterIndex < m_characters.Num() - 1)
		selectedCharacterIndex++;
}
void ACharacterCarousel::Previous()
{
	if (!selected && selectedCharacterIndex > 0)
		selectedCharacterIndex--;
}

void ACharacterCarousel::ToggleSelect(bool enabled)
{
	selected = enabled;
}

void ACharacterCarousel::CreateNewCharacter(const FString& name)
{
	UDefaultGameInstance* instance = Cast<UDefaultGameInstance>(GetGameInstance());
	check(instance);
	UCharacterSettings* settings = instance->GetCharacterSettings();
	check(settings);

	// Create a scene actor
	ACharacterBase* actor = GetWorld()->SpawnActor<ACharacterBase>(characterBlueprint, GetActorLocation(), GetActorRotation());
	m_characters.Add(actor);

	// Select this character
	selected = false;
	selectedCharacterIndex = m_characters.Num() - 1;
	m_zoom = 0;
	
	// Add to save
	FCharacterSave save;
	save.name = name;
	settings->characterSaves.Add(save);
	instance->SaveSettings();
}
void ACharacterCarousel::DeleteCharacter(int32 index)
{
	UDefaultGameInstance* instance = Cast<UDefaultGameInstance>(GetGameInstance());
	check(instance);
	UCharacterSettings* settings = instance->GetCharacterSettings();
	check(settings);

	if (index < 0 || index >= settings->characterSaves.Num())
	{
		JERROR("Invalid character index");
		return;
	}

	// Destroy scene actor
	m_characters[index]->Destroy();
	m_characters.RemoveAt(index);
	if (index == selectedCharacterIndex)
		selectedCharacterIndex--;

	// Unselect
	selected = false;
	m_zoom = 0;

	// Remove from save
	settings->characterSaves.RemoveAt(index);
	instance->SaveSettings();
}

ACharacterBase* ACharacterCarousel::GetCharacterModel(int32 index)
{
	if (index < 0 || index >= m_characters.Num())
	{
		JERROR("Invalid character index");
		return nullptr;
	}

	return m_characters[index];
}
int32 ACharacterCarousel::GetCharacterNum()
{
	return m_characters.Num();
}


void ACharacterCarousel::SaveCharacterName(const FString& name, int32 index)
{
	UDefaultGameInstance* instance = Cast<UDefaultGameInstance>(GetGameInstance());
	check(instance);
	UCharacterSettings* settings = instance->GetCharacterSettings();
	check(settings);

	if (index < 0 || index >= settings->characterSaves.Num())
	{
		JERROR("Invalid character index");
		return;
	}

	settings->characterSaves[index].name = name;
	instance->SaveSettings();
}
FString ACharacterCarousel::GetCharacterName(int32 index)
{
	UDefaultGameInstance* instance = Cast<UDefaultGameInstance>(GetGameInstance());
	check(instance);
	UCharacterSettings* settings = instance->GetCharacterSettings();
	check(settings);

	if (index < 0 || index >= settings->characterSaves.Num())
	{
		JERROR("Invalid character index");
		return FString();
	}

	return settings->characterSaves[index].name;
}