// Project Lab - NHTV Igad
#include "UnrealProject.h"
#include "NetworkPlayer.h"
#include "DefaultPlayerController.h"
#include "ItemBase.h"
#include "BaseSkillObject.h"
#include "AbilityInfo.h"
#include "NetworkSwitch.h"
#include "DefaultGameMode.h"
#include "DefaultPlayerState.h"
#include "IngameSkillTree.h"

#include "Modifier.h"
#include "SpawnerBase.h"
#include "TeamData.h"

static UTeamData* teamData;

ANetworkPlayer::ANetworkPlayer()
{
	bReplicates = true;

	m_SetupCamera();

	m_initialised = false;
	m_keyState = 0;

	healthCurve = ConstructorHelpers::FObjectFinder<UCurveFloat>(TEXT("/Game/ScalingCurves/CF_PlayerHealth")).Object;
	healthRegenCurve = ConstructorHelpers::FObjectFinder<UCurveFloat>(TEXT("/Game/ScalingCurves/CF_PlayerHealthRegen")).Object;
	manaCurve = ConstructorHelpers::FObjectFinder<UCurveFloat>(TEXT("/Game/ScalingCurves/CF_PlayerMana")).Object;
	manaRegenCurve = ConstructorHelpers::FObjectFinder<UCurveFloat>(TEXT("/Game/ScalingCurves/CF_PlayerManaRegen")).Object;
	armorCurve = ConstructorHelpers::FObjectFinder<UCurveFloat>(TEXT("/Game/ScalingCurves/CF_PlayerArmor")).Object;
	attackSpeedCurve = ConstructorHelpers::FObjectFinder<UCurveFloat>(TEXT("/Game/ScalingCurves/CF_PlayerAttackSpeed")).Object;

	creatureDamageTakenCurve = ConstructorHelpers::FObjectFinder<UCurveFloat>(TEXT("/Game/ScalingCurves/CF_CreatureDamageTaken")).Object;
	creatureDamageDealtCurve = ConstructorHelpers::FObjectFinder<UCurveFloat>(TEXT("/Game/ScalingCurves/CF_CreatureDamageDealt")).Object;
	teamData = ConstructorHelpers::FObjectFinder<UTeamData>(TEXT("/Game/Assets/TeamData")).Object;

	playerCircle = CreateDefaultSubobject<UStaticMeshComponent>("Player Circle");
	playerCircle->AttachTo(RootComponent);

	visionRadius = 2500;
}
void ANetworkPlayer::BeginPlay()
{
	m_levelStatsInitialized = false;
	Super::BeginPlay();
	m_initialised = false;
}
void ANetworkPlayer::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);

	if (Role != ROLE_Authority)
		return;

	// Return the keys
	ANetworkPlayer* const killedBy = Cast<ANetworkPlayer>(GetLastPlayerDamage());
	if (m_health <= 0 && IsValid(killedBy) && killedBy->GetTeam() != GetTeam())
	{
		FText Source = FText::FromString(killedBy->playerName);
		FText Target = FText::FromString(playerName);
		onPlayerKilled.Broadcast(Source, Target, killedBy->GetTeam(), GetTeam());
		// Assign keys to the player that killed us
		for (auto iter = m_keyOrigins.CreateIterator(); iter; ++iter)
		{
			ASpawnerBase** find = killedBy->m_keyOrigins.Find(iter->Key);
			if (!find)
				killedBy->m_keyOrigins.Add(iter->Key, iter->Value);
			else
				iter->Value->hasDroppedKey = false;
		}
		killedBy->m_keyState |= m_keyState;
	}
	else
	{
		// Return the keys we have
		for (auto iter = m_keyOrigins.CreateIterator(); iter; ++iter)
		{
			if (IsValid(iter->Value))
				iter->Value->hasDroppedKey = false;
		}
	}
	m_keyState = 0;
	m_keyOrigins.Reset();

	// Clean up screen effects and delegates.
	m_filterTimelines.Empty();
	for (int32 i = 0; i < m_delegates.Num(); i++)
	{
		GetWorldTimerManager().ClearTimer(m_delegates[i]);
	}
	m_delegates.Empty();
}
void ANetworkPlayer::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);

	for (auto& timeline : m_filterTimelines)
	{
		timeline.Value.TickTimeline(DeltaSeconds);
	}

	// Server only
	if(Role == ROLE_Authority)
	{
		ADefaultPlayerState* state = Cast<ADefaultPlayerState>(PlayerState);
		if(!m_levelStatsInitialized)
		{
			m_modifierManager->RecalculateCharacter();
			CheckStatsOverflow();
			if(!m_initialised)
			{
				m_health = m_maxHealth;
				m_mana = m_maxMana;
				m_initialised = true;
			}
			m_levelStatsInitialized = true;
		}
	}

}

void ANetworkPlayer::PossessedBy(AController* NewController)
{
	Super::PossessedBy(NewController);
	OnRep_PlayerState();
}
void ANetworkPlayer::OnRep_PlayerState()
{
	Super::OnRep_PlayerState();

	// Initialize player circle
	UMaterialInstanceDynamic* mi = playerCircle->CreateDynamicMaterialInstance(0);
	FLinearColor teamColor = teamData->GetTeamColor(Cast<ADefaultPlayerState>(PlayerState)->GetTeam());
	mi->SetVectorParameterValue("Color", teamColor);
}

void ANetworkPlayer::ResetModifiers()
{
	// Learn abilities
	m_SetLevelStats();
	Super::ResetModifiers();
}

void ANetworkPlayer::OnLevelUp_Server(int32 newLevel)
{
	check(Role == ROLE_Authority);

	// Learn abilities
	//m_SetLevelStats();

	// Recalculate modifiers for new level
	if(m_modifierManager)
		m_modifierManager->RecalculateCharacter();
	CheckStatsOverflow();

	// Call blueprint level up event (server-side)
	OnLevelUp(newLevel);
}

class ANetworkPlayer* ANetworkPlayer::GetTeamMate() const
{
	ADefaultPlayerState* state = Cast<ADefaultPlayerState>(PlayerState);
	if (state && state->teamMate)
		return state->teamMate->character;
	return nullptr;
}

void ANetworkPlayer::m_SetLevelStats()
{
	Super::m_SetLevelStats();
	// Update stats
	ADefaultPlayerState* state = Cast<ADefaultPlayerState>(PlayerState);

	float level = 1;
	if(IsValid(state))
		level = (float)state->GetLevel();

	float newHealth = SampleHealthCurve(level);
	float newMana = SampleManaCurve(level);
	float newArmor = SampleArmorCurve(level);
	float newAttackSpeed = SampleAttackSpeedCurve(level);

	//GWPRINT(L"Setting level bases stats for level " + level + L"(" + levelScale + L")");
	//GWPRINT(L"Health = " + m_maxHealth + L"->" + newHealth);
	//GWPRINT(L"Mana = " + m_maxMana + L"->" + newMana);
	//GWPRINT(L"Armor = " + m_armor + L"->" + newArmor);
	//GWPRINT(L"Attack Speed = " + m_attackSpeed + L"->" + newAs);

	if(!m_levelStatsInitialized)
	{
		m_health = newHealth;
		m_mana = newMana;
	}

	m_maxHealth = newHealth;
	m_maxMana = newMana;
	baseArmor = newArmor;
	m_attackSpeed = newAttackSpeed;

	if(Role == ROLE_Authority)
	{
		// Send level-up/learn skill command
		ADefaultPlayerController* controller = Cast<ADefaultPlayerController>(GetController());
		if(controller)
		{
			controller->LearnSkillsForLevel();
		}
	}
}

void ANetworkPlayer::OnLevelUp_Implementation(int32 newLevel)
{
	m_levelStatsInitialized = false;
}

bool ANetworkPlayer::ToggleSwitch_Validate(ANetworkSwitch* networkSwitch)
{
	return networkSwitch && FVector::Dist(GetActorLocation(), networkSwitch->GetActorLocation()) < 600;
}
void ANetworkPlayer::ToggleSwitch_Implementation(ANetworkSwitch* networkSwitch)
{
	if(FVector::Dist(GetActorLocation(), networkSwitch->GetActorLocation()) < 300)
		networkSwitch->Toggle();
}

float ANetworkPlayer::SampleHealthCurve(float in)
{
	if(GetCharacterClassProperties().healthCurve)
	{
		return GetCharacterClassProperties().healthCurve->GetFloatValue(in);
	}
	return healthCurve->GetFloatValue(in);
}
float ANetworkPlayer::SampleManaCurve(float in)
{
	if(GetCharacterClassProperties().manaCurve)
	{
		return GetCharacterClassProperties().manaCurve->GetFloatValue(in);
	}
	return manaCurve->GetFloatValue(in);
}
float ANetworkPlayer::SampleArmorCurve(float in)
{
	if(GetCharacterClassProperties().armorCurve)
	{
		return GetCharacterClassProperties().armorCurve->GetFloatValue(in);
	}
	return armorCurve->GetFloatValue(in);
}
float ANetworkPlayer::SampleAttackSpeedCurve(float in)
{
	if(GetCharacterClassProperties().attackSpeedCurve)
	{
		return GetCharacterClassProperties().attackSpeedCurve->GetFloatValue(in);
	}
	return attackSpeedCurve->GetFloatValue(in);
}

bool ANetworkPlayer::HasKey(PlayerKeyType keyType) const
{
	if (keyType == PlayerKeyType::None)
		return m_keyState == 0;
	return ((1 << (int32)keyType) & m_keyState) != 0;
}
bool ANetworkPlayer::HasAnyKey() const
{
	return m_keyState != 0;
}
bool ANetworkPlayer::AssignKey(PlayerKeyType keyType, ASpawnerBase* origin)
{
	check(Role == ROLE_Authority);

	if (HasKey(keyType))
		return false;

	origin->hasDroppedKey = true;

	// Store the origin for later
	m_keyOrigins.Add((uint8)keyType, origin);
	m_keyState += (1 << (int32)keyType);
	return true;
}
bool ANetworkPlayer::ClearKey(PlayerKeyType keyType)
{
	check(Role == ROLE_Authority);
	
	if (!HasKey(keyType))
		return false;

	// Allow the boss to drop keys again
	ASpawnerBase** find = m_keyOrigins.Find((uint8)keyType);
	if (find)
	{
		if(IsValid(*find)) (*find)->hasDroppedKey = false;
		m_keyOrigins.Remove((uint8)keyType);
	}
	m_keyState -= (1 << (int32)keyType);
	return true;
}

void ANetworkPlayer::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	DOREPLIFETIME(ANetworkPlayer, m_keyState);
}

FCharacterClassProperty ANetworkPlayer::GetCharacterClassProperties() const
{
	static FCharacterClassProperty dummy;
	if(!Controller)
		return dummy;
	return Cast<ADefaultPlayerController>(Controller)->GetCharacterClassProperties();
}

//Screen Filters
void ANetworkPlayer::SetFilterDuration(float duration, UPostProcessComponent* filter)
{
	FTimerHandle uniqueHandle;
	FTimerDelegate stopFilterDelegate = FTimerDelegate::CreateUObject(this, &ANetworkPlayer::StopFilter, filter, uniqueHandle);
	GetWorldTimerManager().SetTimer(uniqueHandle, stopFilterDelegate, duration, false);
	m_delegates.Add(uniqueHandle);
}

void ANetworkPlayer::SetTimelineDuration(float duration, UPostProcessComponent* filter)
{
	FTimerHandle uniqueHandle;
	FTimerDelegate stopFilterDelegate = FTimerDelegate::CreateUObject(this, &ANetworkPlayer::StopTimeline, filter, uniqueHandle);
	GetWorldTimerManager().SetTimer(uniqueHandle, stopFilterDelegate, duration, false);
	m_delegates.Add(uniqueHandle);
}

void ANetworkPlayer::StartFilter(float duration, UPostProcessComponent* filter, UCurveFloat* timelineCurve)
{
	if ( m_filterTimelines.Contains(filter) ) return;
	if (timelineCurve)
	{
		FTimeline timeLine;
		FOnTimelineFloatStatic timelineDelegate;
		timelineDelegate.BindUObject(this, &ANetworkPlayer::FilterUpdate, filter);
		timeLine.AddInterpFloat(timelineCurve, timelineDelegate);
		timeLine.SetPlayRate(1.0f / duration);
		timeLine.PlayFromStart();
		m_filterTimelines.Add(filter, timeLine);
		SetFilterDuration(duration, filter);
	}
	filter->bEnabled = true;
}

void ANetworkPlayer::StartFilterWithDelegate(float duration, UPostProcessComponent* filter, UCurveFloat* timelineCurve, FPostAction action)
{
	if ( m_filterTimelines.Contains(filter) ) return;
	if (timelineCurve)
	{
		FTimeline timeLine;
		FOnTimelineFloatStatic timelineDelegate;
		timelineDelegate.BindUObject(this, &ANetworkPlayer::FilterUpdate, filter, action);
		timeLine.AddInterpFloat(timelineCurve, timelineDelegate);
		timeLine.SetPlayRate(1.0f / duration);
		timeLine.PlayFromStart();
		m_filterTimelines.Add(filter, timeLine);
		SetFilterDuration(duration, filter);
	}
	filter->bEnabled = true;
}

void ANetworkPlayer::EnableFilter(float duration, UPostProcessComponent* filter, UCurveFloat* timelineCurve)
{
	if (m_filterTimelines.Contains(filter)) return;
	if (!timelineCurve)
	{
		YERROR("No timelineCurve given.");
		return;
	}

	FTimeline timeLine;
	FOnTimelineFloatStatic timelineDelegate;
	timelineDelegate.BindUObject(this, &ANetworkPlayer::FilterUpdate, filter);
	timeLine.AddInterpFloat(timelineCurve, timelineDelegate);
	timeLine.SetPlayRate(1.0f / duration);
	timeLine.PlayFromStart();
	m_filterTimelines.Add(filter, timeLine);
	SetTimelineDuration(duration, filter);
	filter->bEnabled = true;
}

void ANetworkPlayer::DisableFilter(float duration, UPostProcessComponent* filter, UCurveFloat* timeline)
{
	StartFilter(duration, filter, timeline);
}

void ANetworkPlayer::EnableFilterWithDelegate(float duration, UPostProcessComponent* filter, UCurveFloat* timelineCurve, FPostAction action)
{
	if (m_filterTimelines.Contains(filter)) return;
	if (!timelineCurve)
	{
		YERROR("No timelineCurve given.");
		return;
	}

	FTimeline timeLine;
	FOnTimelineFloatStatic timelineDelegate;
	timelineDelegate.BindUObject(this, &ANetworkPlayer::FilterUpdate, filter, action);
	timeLine.AddInterpFloat(timelineCurve, timelineDelegate);
	timeLine.SetPlayRate(1.0f / duration);
	timeLine.PlayFromStart();
	m_filterTimelines.Add(filter, timeLine);
	SetTimelineDuration(duration, filter);
	filter->bEnabled = true;
}

void ANetworkPlayer::DisableFilterWithDelegate(float duration, UPostProcessComponent* filter, UCurveFloat* timeline, FPostAction action)
{
	StartFilterWithDelegate(duration, filter, timeline, action);
}

void ANetworkPlayer::StopFilter(UPostProcessComponent* filter, FTimerHandle handle)
{
	filter->bEnabled = false;
	m_delegates.Remove(handle);
	if (!m_filterTimelines.Contains(filter))
	{
		YERROR("Can't find filter in timeline.");
		return;
	}
	m_filterTimelines.Remove(filter);
}

void ANetworkPlayer::StopTimeline( UPostProcessComponent* filter, FTimerHandle handle )
{
	m_delegates.Remove(handle);
	if (!m_filterTimelines.Contains(filter))
	{
		YERROR("Can't find filter in timeline.");
		return;
	}
	m_filterTimelines.Remove(filter);
}

void ANetworkPlayer::FilterUpdate(float value, UPostProcessComponent* filter)
{
	filter->BlendWeight = value;
}

void ANetworkPlayer::FilterUpdate(float value, UPostProcessComponent* filter, FPostAction action)
{
	action.ExecuteIfBound(value, filter);
}

void ANetworkPlayer::AddThreatLevel_Client_Implementation(const float a_NewThreat)
{
	threats.Add(a_NewThreat);

	threatLevel = a_NewThreat > threatLevel ? a_NewThreat : threatLevel;
}

void ANetworkPlayer::RemoveThreatLevel_Client_Implementation(const float a_NewThreat)
{
	threats.RemoveSingle(a_NewThreat);

	if (a_NewThreat == threatLevel)
	{
		threatLevel = 0;
		for (int32 i = 0; i < threats.Num(); i++)
		{
			if (threats[i] > threatLevel)
			{
				threatLevel = threats[i];
			}
		}
	}
}
