// Project Lab - NHTV Igad
#include "UnrealProject.h"
#if PLATFORM_SPECIFIC_WIN == 0
#include "InputManager.hpp"
using namespace Input;
#endif

#include "NetworkCharacter.h"

#include "AbilityInfo.h"
#include "ItemBase.h"
#include "BaseSkillObject.h"
#include "IngameSkillTree.h"
#include "DefaultPlayerController.h"
#include "DefaultGameMode.h"
#include "NetworkPlayer.h"
#include "Modifier.h"
#include "AbilityEventGroup.h"
#include "PreCastAbilityEventGroup.h"
#include "NativeModifiers.h"
#include "DefaultGameState.h"
#include "DefaultPlayerState.h"
#include "Effect.h"

TSubclassOf<ATargetedEffect> expGainEffect;

ANetworkCharacter::ANetworkCharacter()
{
	expGainEffect = ConstructorHelpers::FClassFinder<ATargetedEffect>(L"/Game/Assets/Art/Effects/FX_ExpParticle").Class;

	// Set size for collision capsule
	GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);

	// Network init
	bReplicates = true;
	bAlwaysRelevant = true;
	bReplicateMovement = true;

	// Configure character movement
	GetCharacterMovement()->bOrientRotationToMovement = true; // Rotate character to moving direction
	GetCharacterMovement()->RotationRate = FRotator(0.f, 640.f, 0.f);
	GetCharacterMovement()->bConstrainToPlane = true;
	GetCharacterMovement()->bSnapToPlaneAtStart = true;
	baseMovementSpeed = GetCharacterMovement()->MaxWalkSpeed;

	experienceGainOnKill = 50;
	m_maxHealth = 1000;
	m_maxMana = 100;
	canBeStunned = true;
	m_visible = true;
	m_usesMana = true;

	// Default to neutral
	GetMesh()->bRenderCustomDepth = true;
	GetMesh()->CustomDepthStencilValue = 0;

	PrimaryActorTick.bCanEverTick = true;
	m_lastPlayerDamage = nullptr;


	healthAccumTimer = 1.0f;
	damageAccumTimer = 0.1f;

	teamDamageTimer = 5.0f;
	
	m_healAccumValue = 0;
	m_healAccumTimer = 0;
}

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

	m_cooldownReduction = 0.0f;
	m_channelStun = false;
	m_channelGroup = nullptr;
	m_castingAbility = nullptr;
	m_initialised = true;
	m_hitable = true;

	m_silenceCount = 0;
	m_silenced = false;
	swingAnimationSequence = 0;

	// Filter out NULL abilities
	TArray<class UAbilityInfo*> actualAbilities;
	for (int32 i = 0; i < abilities.Num(); i++)
	{
		if (!IsValid(abilities[i]))
			continue;
		actualAbilities.Add(abilities[i]);
	}
	abilities = actualAbilities;


	for (int32 i = 0; i < abilities.Num(); i++)
	{
		GetAbilityState(abilities[i]);
	}

	RegisterHealthBar();

	// Initialize the character modifiers on the server
	if (Role == ROLE_Authority)
	{
		m_modifierManager = new ModifierManager(this);
		m_SpawnModifiers();
		for(UAbilityInfo* info : passives)
		{
			CastAbility(info);
		}
		m_health = m_maxHealth;
		m_mana = m_maxMana;
	}
}

void ANetworkCharacter::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);

	if (m_modifierManager)
	{
		delete m_modifierManager;
		m_modifierManager = nullptr;
	}

#if UE_INCLUDE_METRICS
	// Record trivial player death
	if(metricsHandle && EndPlayReason == EEndPlayReason::Type::Destroyed)
		metricsHandle->OnPlayerDie(0);
#endif
}

void ANetworkCharacter::Destroyed()
{
	UnregisterHealthBar();
	Super::Destroyed();
}

void ANetworkCharacter::RegisterHealthBar()
{
	// Notify UI of character spawn
	ULocalPlayer* localPlayer = GetGameInstance()->GetFirstGamePlayer();
	if(localPlayer)
	{
		ADefaultPlayerController* localPlayerController = Cast<ADefaultPlayerController>(localPlayer->PlayerController);
		if(localPlayerController)
		{
			localPlayerController->OnCharacterCreated(this);

		}
		else
		{
			GWWARNING(L"No local player found to send OnCharacterCreated");
		}
	}
}
void ANetworkCharacter::UnregisterHealthBar()
{
	// Notify UI of character destroy
	UGameInstance* inst = GetGameInstance();
	if(!inst)
		return;
	ULocalPlayer* player = inst->GetFirstGamePlayer();
	if(player)
	{
		APlayerController* controller = player->PlayerController;

		ADefaultPlayerController* localPlayer = Cast<ADefaultPlayerController>(controller);
		if(localPlayer)
		{
			localPlayer->OnCharacterDestroyed(this);
		}
		else
		{
			GWWARNING(L"No local player found to send OnCharacterDestroyed");
		}
	}
}
void ANetworkCharacter::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);

	// Update the damage accumolation table
	if (m_damageAccum.size() > 0)
	{
		for (auto iter = m_damageAccum.begin(); iter != m_damageAccum.end();)
		{
			// Controller no longer exists
			if (!IsValid(iter->first))
			{
				iter = m_damageAccum.erase(iter);
				continue;
			}
			
			DamagePeriod& value = iter->second;
			value.timer -= DeltaSeconds;

			// Time has expired
			if (value.timer <= 0)
			{
				if (value.damage > 0)
					iter->first->SpawnArcingCombatText(GetActorLocation(), FString::FromInt(value.damage), FLinearColor::White);
				m_totalDamage = value.damage;
				iter = m_damageAccum.erase(iter);
				continue;
			}

			iter++;
		}
	}

	// Update the team damage dealt table
	if (m_teamDamageDealt.size() > 0)
	{
		for (auto iter = m_teamDamageDealt.begin(); iter != m_teamDamageDealt.end();)
		{
			DamagePeriod& value = iter->second;
			value.timer -= DeltaSeconds;

			// Time has expired, this team is no longer entitled for the experience
			if (value.timer <= 0)
			{
				iter = m_teamDamageDealt.erase(iter);
				continue;
			}

			iter++;
		}
	}

	ADefaultPlayerController* const controller = Cast<ADefaultPlayerController>(GetController());

	// Update the heal accumolation
	if (IsValid(controller) && m_healAccumValue > 0)
	{
		m_healAccumTimer -= DeltaSeconds;
		if (m_healAccumTimer <= 0)
		{
			m_healAccumTimer = 0;
			controller->SpawnCombatText(GetActorLocation(), FString::FromInt(m_healAccumValue), FLinearColor::Green);
			m_healAccumValue = 0;
		}
	}

	if(GetMesh())
	{
		// Check if ally or player for silhouette material
		// (0 = neutral, 1 = player, 2 = ally, 3 = enemy)
		UWorld* const world = GetWorld();
		const int32 current = GetMesh()->CustomDepthStencilValue;
		int32 target = 0; // Default to neutral

		if(controller == world->GetFirstPlayerController())
			target = 1; // Self
		else
		{
			// Get the local team
			int32 localTeam = -1;
			ADefaultPlayerController* controller = Cast<ADefaultPlayerController>(world->GetFirstPlayerController());
			if(IsValid(controller))
			{
				APlayerStateBase* playerState = Cast<ADefaultPlayerState>(controller->PlayerState);
				if(IsValid(playerState))
					localTeam = playerState->GetTeam();
			}
			if(localTeam != -1)
			{
				if(GetTeam() == localTeam)
					target = 2; // Ally
				else if(GetTeam() != 0)
					target = 3; // Enemy
			}
		}
		if(target != current)
		{
			GetMesh()->CustomDepthStencilValue = target;
			GetMesh()->MarkRenderStateDirty();
		}
	}

	m_TickAbilities(DeltaSeconds);

#if UE_INCLUDE_METRICS
	if (metricsHandle)
	{
		// Record player stats
		metricsHandle->OnPlayerUpdate(GetActorLocation().X, GetActorLocation().Y);
		ADefaultPlayerState* const state = Cast<ADefaultPlayerState>(PlayerState);
		if(state) 
			metricsHandle->OnPlayerLevel(uint8(state->GetLevel()));
		metricsHandle->OnPlayerMaxHealth(m_maxHealth);
		metricsHandle->OnPlayerMaxMana(m_maxMana);
		metricsHandle->OnPlayerHealth(m_health);
		metricsHandle->OnPlayerMana(m_mana);
	}
#endif
	if (m_shouldBeDestroyed)
		Destroy();
}
void ANetworkCharacter::OnDeath(UAbilityInfo* ability)
{
	m_shouldBeDestroyed = true;
}
void ANetworkCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	DOREPLIFETIME(ANetworkCharacter, basicAttack);
	DOREPLIFETIME(ANetworkCharacter, m_health);
	DOREPLIFETIME(ANetworkCharacter, m_maxHealth);
	DOREPLIFETIME(ANetworkCharacter, m_mana);
	DOREPLIFETIME(ANetworkCharacter, m_maxMana);
	DOREPLIFETIME(ANetworkCharacter, m_stunned);
	DOREPLIFETIME(ANetworkCharacter, m_silenced);
	DOREPLIFETIME(ANetworkCharacter, m_channelStun);
	DOREPLIFETIME(ANetworkCharacter, m_allowChannelRotation);
	DOREPLIFETIME(ANetworkCharacter, m_castingAbility);
	DOREPLIFETIME(ANetworkCharacter, m_cooldownReduction);
	DOREPLIFETIME(ANetworkCharacter, m_attackSpeed);
	DOREPLIFETIME(ANetworkCharacter, m_attackDamageMultiplier);
	DOREPLIFETIME(ANetworkCharacter, m_magicDamageMultiplier);
	DOREPLIFETIME(ANetworkCharacter, m_blockedMana);
	DOREPLIFETIME(ANetworkCharacter, m_damageMultiplier);
	DOREPLIFETIME(ANetworkCharacter, m_ignoreArmor);
	DOREPLIFETIME(ANetworkCharacter, m_armor);
	DOREPLIFETIME(ANetworkCharacter, m_manaRegenMultiplier);
	DOREPLIFETIME(ANetworkCharacter, m_visible);
	DOREPLIFETIME(ANetworkCharacter, m_hitable);
	DOREPLIFETIME(ANetworkCharacter, m_manaUsageMultiplier);
	DOREPLIFETIME(ANetworkCharacter, m_positiveEffectMultiplier);
	DOREPLIFETIME(ANetworkCharacter, m_negativeEffectMultiplier);
	DOREPLIFETIME(ANetworkCharacter, m_castingMovementspeedMultiplier);
	DOREPLIFETIME(ANetworkCharacter, swingAnimationSequence);
}

void ANetworkCharacter::TriggerSwingAnimation_Server()
{
	check(Role == ROLE_Authority);
	swingAnimationSequence++;
}

void ANetworkCharacter::LearnSkills_Implementation(const TArray<FIngameSkillTreeSkill>& skills)
{
	ADefaultPlayerController* pc = Cast<ADefaultPlayerController>(GetController());

	for(size_t i = 0; i < skills.Num(); i++)
	{
		const FIngameSkillTreeSkill& obj = skills[i];
		UAbilityInfo* ability = obj.selectedEffect;

		if(ability)
		{
			// Initialize ability power in the ability state
			AAbilityState* aState = GetAbilityState(ability);
			if(aState)
				aState->power = obj.power;

			if(ability->passive)
			{
				if(!passives.Contains(ability))
				{
					// Learn passive
					passives.Add(obj.selectedEffect);

					// Only Cast the actual passive ability on the server
					if(Role == ROLE_Authority)
					{
						CastAbility(ability);
					}
				}
			}
			else
			{
				// Learn ability
				if(!abilities.Contains(ability))
				{
					abilities.Add(ability);
				}
			}
		}
	}
}
void ANetworkCharacter::LearnSkills_Server(const TArray<FIngameSkillTreeSkill>& skills)
{
	// Server side call
	LearnSkills_Implementation(skills);
	// Client side call
	LearnSkills(skills);
}

void ANetworkCharacter::m_SpawnModifiers()
{
	ARegenModifier* regenMod = Cast<ARegenModifier>(m_modifierManager->AddModifier(ARegenModifier::StaticClass(), 0));
	regenMod->regenPerTick = passiveHealthRegen;
	AManaRegenModifier* manaMod = Cast<AManaRegenModifier>(m_modifierManager->AddModifier(AManaRegenModifier::StaticClass(), 0));
	manaMod->regenPerTick = passiveManaRegen;
}

bool ANetworkCharacter::m_AllowedToMove()
{
	return !IsStunned();
}
bool ANetworkCharacter::m_AllowedToRotate()
{
	return CanRotate();
}

void ANetworkCharacter::m_OnAbilityCastConfirm_Implementation(UAbilityInfo* ability, bool success, bool toggleState, float cooldown, float cooldownTime)
{
	if (!ability)
	{
		GWWARNING(L"Out of range value received for OnAbilityCastConfirm");
		return;
	}

	AAbilityState* state = GetAbilityState(ability);

	//GWPRINT(L"Ability toggle " + ability->GetName() + L" toggles from " + state->toggleState + L" to " + toggleState);

	state->toggleState = toggleState;

	state->onCooldownTimer = cooldown;
	state->currentCooldownDuration = cooldownTime;
	state->cooldownRate = cooldown/cooldownTime;
}
bool ANetworkCharacter::m_CastAbility_Server_Validate(UAbilityInfo* ability)
{
	if(ability == nullptr)
		return false;
	if(ability->events.Num() == 0)
	{
		GWERROR(L"No events in ability->events for ability " + ability->GetName());
		return false;
	}
	// Validate AbilityInfo Array
	for(int32 i = 0; i < ability->events.Num(); i++)
	{
		if(ability->events[i] == nullptr)
		{
			GWERROR(L"Null entry in ability->events[" + i + L"] for ability " + ability->GetName());
			return false;
		}
	}
	return true;
}
void ANetworkCharacter::m_CastAbility_Server_Implementation(UAbilityInfo* ability)
{
	AAbilityState* state = GetAbilityState(ability);

	// Check if this is a toggle ability, and is toggled off
	bool toggleOffAbility = ability->IsHoldOrToggle() && state->toggleState == true;

	// Check if we're not channeling (if this is not a toggle off ability)
	if(IsPendingKill() || IsPendingKillPending())
		return;

	// Check if stunned or already casting an ability
	// allow if this is a toggle ability and currently on
	if(!toggleOffAbility && (IsStunned() || IsChanneling() ))
	{
		m_OnAbilityCastConfirm_Server(ability, false);
		return;
	}

	// Check if silenced, block all non-basic attacks in this case
	// also, don't block passives
	if (ability->abilityType != EAbilityType::Basic && IsSilenced() && !ability->passive)
	{
		m_OnAbilityCastConfirm_Server(ability, false);
		return;
	}

	// Check if ability is not on cooldown (if not toggling off)
	if(state->onCooldownTimer > 0.1f && !toggleOffAbility)
	{
		m_OnAbilityCastConfirm_Server(ability, false);
		return;
	}

	// Use mana state for current ability / character mana usage chained together with ability state (toggle off ability?)
	bool currentUsesMana = m_usesMana && !toggleOffAbility;

	UWorld* const world = GetWorld();
	if (ability && world && (ability->mana * m_manaUsageMultiplier <= m_mana || !currentUsesMana))
	{
		if (currentUsesMana)
			RemoveMana(ability->mana * m_manaUsageMultiplier);

		// Handle cooldown setting
		if(ability->IsHoldOrToggle() && state->toggleState == false)
		{
			// No cooldown when casting toggle abilities
			state->onCooldownTimer = 0.0f;
			state->cooldownRate = 0.0f;
		}
		else
		{
			// Go on cooldown if it is a toggle ability, or a regular ability
			m_SetAbilityCooldown(ability, state);
		}

		if(ability->IsHoldOrToggle())
		{
			AAbilityEventGroup** activeAbility = m_activeAbilities.Find(ability);
			bool isAbilityActive = activeAbility && *activeAbility;

			// Check if ability is enabled
			if(state->toggleState == false)
			{
				// Check if ability does not yet exist
				if(!isAbilityActive)
				{
					state->toggleState = true;

					// Activate toggle ability
					m_InitAbilitySequence(ability, state);
				}
				else
				{
					m_OnAbilityCastConfirm_Server(ability, false);
				}
			}
			else
			{
				// Deactivate ability
				if(isAbilityActive)
				{
					state->toggleState = false;
					(*activeAbility)->NextGroup();
					*activeAbility = nullptr;
					m_OnAbilityCastConfirm_Server(ability, true);
				}
				else
				{
					m_OnAbilityCastConfirm_Server(ability, false);
				}
			}
		}
		else
		{
			m_InitAbilitySequence(ability, state);
		}
	}
	else
	{
		m_OnAbilityCastConfirm_Server(ability, false);
	}
}
void ANetworkCharacter::m_InitAbilitySequence(class UAbilityInfo* ability, class AAbilityState* state)
{
	AAbilityEventGroup* group = AAbilityEventGroup::SpawnSequence(this, ability, state, ability->events);
	check(group);
	if(group->channelEvent)
	{
		//GWPRINT(L"Begin channel ability " + group->GetName());
		m_channelGroup = group;
		m_castingAbility = ability;
		m_allowChannelRotation = group->allowRotateWhileChannel;
		if(m_castingMovementspeedMultiplier == 0.0f || ability->abilityType == EAbilityType::Basic)
			m_channelStun = group->stunWhileChannel;
		else
		{
			if (group->stunWhileChannel)
			{
				ASpeedModifier* sModifier = GetWorld()->SpawnActor<ASpeedModifier>();
				sModifier->lifeTime = group->duration;
				sModifier->speedMultiplier = m_castingMovementspeedMultiplier;
				sModifier->target = this;
				GetModifierManager()->AddModifier(sModifier);
			}
		}
	}
	else
	{
		m_allowChannelRotation = false;
		m_channelGroup = nullptr;
	}

	group->onAbilityEventGroupEnded.AddDynamic(this, &ANetworkCharacter::m_OnAbilityEventGroupEnded);
	m_activeAbilities.Add(ability, group);

	m_OnAbilityCastConfirm_Server(ability, true);
}

void ANetworkCharacter::m_PreCast(UAbilityInfo* ability)
{
	if(ability->mana * m_manaUsageMultiplier <= m_mana)
		m_currentPreCast = APreCastAbilityEventGroup::InitPreCast(ability, this);
}
void ANetworkCharacter::m_TickAbilities(float DeltaSeconds)
{
	for(auto it = m_abilityStates.CreateIterator(); it; ++it)
	{
		AAbilityState* state = it.Value();
		if((state->onCooldownTimer -= (DeltaSeconds * (1.0f + m_cooldownReduction))) <= 0.0f)
		{
			state->onCooldownTimer = 0.0f;
		}
		state->cooldownRate = state->onCooldownTimer / it.Key()->cooldown;
	}
	if(m_modifierManager)
		m_modifierManager->Tick(DeltaSeconds);
	m_timeSinceDamage += DeltaSeconds;
}

void ANetworkCharacter::m_SetAbilityCooldown(class UAbilityInfo* ability, class AAbilityState* state)
{
	if(ability->abilityType == EAbilityType::Basic)
	{
		// This is the character's basic attack
		state->onCooldownTimer = state->currentCooldownDuration = 1.0f / m_attackSpeed;
		state->cooldownRate = (state->currentCooldownDuration > 0.0f) ? (state->onCooldownTimer / state->currentCooldownDuration) : 0.0f;
	}
	else
	{
		// This is an ability
		state->NativeSetCooldown();
	}
}

void ANetworkCharacter::m_OnAbilityCastConfirm_Server(UAbilityInfo* ability, bool success)
{
	AAbilityState* state = GetAbilityState(ability);
	m_OnAbilityCastConfirm(ability, success, state->toggleState, state->onCooldownTimer, state->currentCooldownDuration);
	
#if UE_INCLUDE_METRICS
	if (metricsHandle && success && !ability->passive)
	{
		// Record ability cast
		if (!ability->IsHoldOrToggle() || state->toggleState)
		{
			std::wstring abilityName = std::wstring() + ability->name;
			if (abilityName.size() > 512)
				abilityName.resize(512);
			
			Metrics::RegisterAbility(ability->GetStaticHash(), abilityName);
			metricsHandle->OnPlayerCast(ability->GetStaticHash());
		}
	}
#endif

	return;
}
void ANetworkCharacter::m_OnAbilityEventGroupEnded(class UAbilityInfo* ability, class AAbilityEventGroup* current, class AAbilityEventGroup* next)
{
	check(Role == ROLE_Authority);
	check(ability);
	AAbilityEventGroup** group = m_activeAbilities.Find(ability);
	check(group);
	*group = next;

	// Check end of channeling ability
	if(m_channelGroup == current)
	{
		if(!next || !next->channelEvent)
		{
			//GWPRINT(L"End ability channel on ability " + m_castingAbility->GetName());
			m_castingAbility = nullptr;
			m_channelGroup = nullptr;
			m_channelStun = false;
		}
		else
		{
			m_channelGroup = next;
			m_channelStun = m_channelGroup->stunWhileChannel;
		}
	}

	if(!next)
	{
		// Turn of toggle state if it was active
		AAbilityState* state = GetAbilityState(ability);
		if(state->toggleState == true)
		{
			state->toggleState = false;
			m_SetAbilityCooldown(ability, state);
			m_OnAbilityCastConfirm_Server(ability, true);
		}
	}
	else
	{
		// Subscribe to next group ended event
		next->onAbilityEventGroupEnded.AddDynamic(this, &ANetworkCharacter::m_OnAbilityEventGroupEnded);
	}
	//GWPRINT(L"Ability " + ability->GetName() + L" reached next state " + (next ? next->GetName() : FString(L"end")));
}

void ANetworkCharacter::CastAbility(int32 abilityIndex)
{
	if (abilityIndex < 0 || abilityIndex >= abilities.Num())
	{
		GWERROR(L"Ability index out of range");
		return;
	}
	UAbilityInfo* ability = abilities[abilityIndex];
	if (!ability)
	{
		GWERROR(L"Null ability assigned and casted on character character[" + GetName() + L"] slot[" + abilityIndex + L"]");
		return;
	}
	CastAbility(ability);
}
void ANetworkCharacter::CastAbility(UAbilityInfo* ability)
{
	if (!ability)
	{
		GWERROR(L"Ability pointer invalid");
		return;
	}

	if(IsPendingKill() || IsPendingKillPending())
		return;

	AAbilityState* state = GetAbilityState(ability);
	if(state->onCooldownTimer <= 0.0f && ability->precastEvent == nullptr)
	{
		m_CastAbility_Server(ability);
	}
	else if(ability->precastEvent != nullptr && state->onCooldownTimer <= 0.0f)
	{
		m_PreCast(ability);
	}
}

class UAbilityInfo* ANetworkCharacter::GetCastingAbility()
{
	return m_castingAbility;
}

AAbilityState* ANetworkCharacter::GetAbilityState(UAbilityInfo* abilityInfo)
{
	AAbilityState** state = m_abilityStates.Find(abilityInfo);
	if (!state)
	{
		// Don't check anymore if this character is allowed to cast an ability
		// TODO: check this only for players in CastAbility_Validate when the new character class hierarchy is implemented
		//check(abilities.Contains(abilityInfo) || passives.Contains(abilityInfo));
		if (abilityInfo == nullptr)
		{
			FERROR("abilityInfo == nullptr");
				return nullptr;
		}
		if(!abilityInfo->abilityState)
		{
			GWERROR(L"Ability " + abilityInfo->GetName() + L" does not have an ability state class assigned");
			abilityInfo->abilityState = AAbilityState::StaticClass();
		}
		AAbilityState* abs = GetWorld()->SpawnActor<AAbilityState>(abilityInfo->abilityState);
		abs->onCooldownTimer = 0.0f;
		abs->cooldownRate = 0.0f;
		abs->power = 0.0f;
		abs->info = abilityInfo;
		return m_abilityStates.Add(abilityInfo, abs);
	}
	return *state;
}

bool ANetworkCharacter::GetAbilityToggleState(class UAbilityInfo* abilityInfo)
{
	AAbilityState* state = GetAbilityState(abilityInfo);
	if(state)
	{
		return state->toggleState;
	}
	return false;
}

void ANetworkCharacter::m_InteruptSpellcasting()
{
	if (m_channelGroup)
	{
		m_channelGroup->Interrupt();
		m_channelGroup = nullptr;
		m_castingAbility = nullptr;
		m_channelStun = false;
	}
}

ModifierManager* ANetworkCharacter::GetModifierManager()
{
	return m_modifierManager;
}

TArray<AModifier*> ANetworkCharacter::GetModifiersOfClass(TSubclassOf<class AModifier> modifierClass)
{
	if (m_modifierManager)
		return m_modifierManager->GetModifiersOfClass(modifierClass);
	return TArray<AModifier*>();
}
void ANetworkCharacter::ResetModifiers()
{
	m_blockedMana = 0;
	m_attackDamageMultiplier = 1.0f;
	m_magicDamageMultiplier = 1.0f;
	m_damageMultiplier = 1.0f;
	m_manaRegenMultiplier = 1.0f;
	m_stunned = false;
	m_stunned = false;
	m_damageTakenMultiplier = 1.0f;
	m_cooldownReduction = 0.0f;
	m_ignoreArmor = 0.0f;
	m_manaUsageMultiplier = 1.0f;
	m_positiveEffectMultiplier = 1.0f;
	m_negativeEffectMultiplier = 1.0f;
	m_castingMovementspeedMultiplier = 0.0f;
	m_armor = baseArmor;
	GetCharacterMovement()->MaxWalkSpeed = baseMovementSpeed;
	m_SetLevelStats();
}

void ANetworkCharacter::m_SetLevelStats()
{

}

void ANetworkCharacter::CheckStatsOverflow()
{
	if (m_health > m_maxHealth || !m_initialised)
		m_health = m_maxHealth;
	if (m_mana > m_maxMana || !m_initialised)
		m_mana = m_maxMana;
}

int32 ANetworkCharacter::DealDamage(UObject* WorldContextObject, int32 damage, float armorPercentageIgnore)
{
	ADealDamageProxy* damageProxy = Cast<ADealDamageProxy>(WorldContextObject);
	if(!damageProxy)
	{
		GWERROR(L"DealDamage can not be called from an object that does not inherit from ADealDamageProxy");
		return 0;
	}

	int32 damageDealth = NativeDealDamage(damageProxy->character, damage, armorPercentageIgnore, damageProxy->abilityInfo);

	return damageDealth;
}
int32 ANetworkCharacter::NativeDealDamage(class ANetworkCharacter* dealer, int32 damage, float armorPercentageIgnore, UAbilityInfo* ability)
{
	// Ensure that this is only called on the server
	check(Role == ROLE_Authority);
	if(dealer == NULL || dealer->IsPendingKill())
	{
		FWARNING("character in dealdamage is null or pendingkill");
		return 0;
	}


	// Scale the damage done or taken by creatures and players
	if (dealer->IsA<ANetworkPlayer>() != this->IsA<ANetworkPlayer>())
	{
		ANetworkPlayer* const player = dealer->IsA<ANetworkPlayer>() ? Cast<ANetworkPlayer>(dealer) : Cast<ANetworkPlayer>(this);
		if (IsValid(player))
		{
			ADefaultPlayerState* const state = Cast<ADefaultPlayerState>(player->PlayerState);
			if (IsValid(state))
			{
				UCurveFloat* const curve = player == this ? player->creatureDamageDealtCurve : player->creatureDamageTakenCurve;
				if (curve)
				{
					const float scalar = curve->GetFloatValue((float)state->GetLevel());
					damage = int32((float)damage * (scalar < 0.0f ? 0.0f : scalar));
				}
			}
		}
	}


	
	if(damage < 0)
	{
		GWERROR(L"Negative damage dealt by " + ability->GetName());
		return 0;
	}
	const int32 damageCap = 1000;
	if(damage > damageCap)
	{
		GWERROR(L"A lot of damage (>" + damageCap + L") dealt by " + ability->GetName() + ", is this intended?");
	}
	if(armorPercentageIgnore > 1.0f || armorPercentageIgnore < 0.0f)
	{
		GWERROR(L"Invalid armorPercentageIgnore given by " + ability->GetName() + "will use 0.0f");
		armorPercentageIgnore = 0.0f;
	//	return 0;
	}

	float totalArmorPercentage = armorPercentageIgnore + dealer->m_ignoreArmor;
	totalArmorPercentage = std::fmax(0.0f, std::fmin(totalArmorPercentage, 1.0f));

	if(m_damageTakenMultiplier == 0.0f || dealer->m_damageMultiplier <= 0.0f)
		return 0;
	float divider = 1 + 0.01f * ((float)m_armor * (1 - totalArmorPercentage));
	float damageToDeal;
	// The damage to deal
	damageToDeal = damage / divider;

	damageToDeal *= dealer->m_damageMultiplier;
	// Multiply by weakness aka damage taken multiplier
	damageToDeal *= m_damageTakenMultiplier;

	// Magic damage has a different multiplier
	if (ability->abilityType == EAbilityType::Ability)
		damageToDeal *= dealer->m_magicDamageMultiplier;

	int32 iDamageToDeal = damageToDeal;
	bool dealdamage = true;
	for (AModifier* mod : m_modifierManager->m_modifiers)
	{
		if (!mod->OnDamage(iDamageToDeal, dealer))
		{
			dealdamage = false;
		}
	}
	if (dealer->GetModifierManager() == nullptr)
		return 0;
	for (AModifier* mod : dealer->GetModifierManager()->m_modifiers)
	{
		if (!mod->OnDealDamage(iDamageToDeal, this))
		{
			dealdamage = false;
		}
	}
	if(!dealdamage)
		return 0;

#if UE_INCLUDE_METRICS
	if (dealer->metricsHandle && IsValid(dealer))
	{
		// Record ability damage dealt
		dealer->metricsHandle->OnPlayerDealDamage(ability->GetStaticHash(), iDamageToDeal);
	}
#endif

	if(iDamageToDeal > m_health)
		iDamageToDeal = m_health;
	RemoveHealth(iDamageToDeal);
	m_lastPlayerDamage = dealer;

	onDamageTaken.Broadcast(dealer, damage, ability);

	// Update the entry for this team in the damage dealt table
	const int32 team = dealer->GetTeam();
	if (experienceGainOnKill > 0 && team < 5)
	{
		auto find = m_teamDamageDealt.find(team);
		if (find != m_teamDamageDealt.end())
		{
			find->second.damage += iDamageToDeal;
			find->second.timer = teamDamageTimer;
		}
		else
			m_teamDamageDealt.emplace(team, DamagePeriod(iDamageToDeal, teamDamageTimer));
	}

	// Spawn combat text
	ADefaultPlayerController* const controller = Cast<ADefaultPlayerController>(dealer->GetController());
	if (IsValid(controller))
	{
		auto find = m_damageAccum.find(controller);
		if (find == m_damageAccum.end())
			m_damageAccum.emplace(controller, DamagePeriod(iDamageToDeal, damageAccumTimer));
		else
			find->second.damage += iDamageToDeal;
	}

	for (AModifier* mod : m_modifierManager->m_modifiers)
		mod->AfterDamage(iDamageToDeal);

	m_timeSinceDamage = 0;

	if(m_health <= 0)
	{
		NativeOnKilled(dealer, ability);
		OnDeath(ability);
	}
	return iDamageToDeal;
}

void ANetworkCharacter::NativeOnKilled(class ANetworkCharacter* killer, class UAbilityInfo* ability)
{
	// Score counting
	ANetworkPlayer* const thisPlayer = Cast<ANetworkPlayer>(this);
	if (IsValid(thisPlayer))
	{
		ADefaultPlayerState* state = Cast<ADefaultPlayerState>(thisPlayer->PlayerState);
		if (IsValid(state)) state->deaths++;

		ANetworkPlayer* const killerPlayer = Cast<ANetworkPlayer>(killer);
		if (IsValid(killerPlayer))
		{
			state = Cast<ADefaultPlayerState>(killerPlayer->PlayerState);
			if (IsValid(state)) state->kills++;
		}
	}

	if (experienceGainOnKill > 0 && !m_teamDamageDealt.empty())
	{
		ADefaultGameState* gameState = Cast<ADefaultGameState>(GetWorld()->GetGameState());
		TArray<ADefaultPlayerState*> players = gameState->GetPlayers<ADefaultPlayerState>();

		// Fetch highest team
		int32 highestTeam = -1;
		int32 highestDamage = 0;
		for (auto i : m_teamDamageDealt)
		{
			if (i.second.damage > highestDamage)
			{
				highestDamage = i.second.damage;
				highestTeam = i.first;
			}
		}
		m_teamDamageDealt.clear();

		// Apply experience
		if (highestTeam > 0)
		{
			for (size_t i = 0; i < players.Num(); i++)
			{
				if (!players[i])
					continue;

				// Award the experience
				if(players[i]->GetTeam() == highestTeam)
				{
					players[i]->GainExperience(experienceGainOnKill);

					// Spawn EXP gain effect
					FTransform t;
					if(players[i]->character)
					{
						t.SetTranslation(players[i]->character->GetActorLocation());
						ATargetedEffect* fx = GetWorld()->SpawnActorDeferred<ATargetedEffect>(expGainEffect, t);
						fx->Init(0.0f, players[i]->character);
						fx->target = GetActorLocation();
						UGameplayStatics::FinishSpawningActor(fx, t);
					}
				}
			}
		}
	}

	// Clear the damage accum table
	if (!m_damageAccum.empty())
	{
		for (auto iter = m_damageAccum.begin(); iter != m_damageAccum.end();)
		{
			// Controller no longer exists
			if (IsValid(iter->first))
			{
				DamagePeriod& value = iter->second;

				if (value.damage > 0)
					iter->first->SpawnArcingCombatText(GetActorLocation(), FString::FromInt(value.damage), FLinearColor::White);
			}

			iter = m_damageAccum.erase(iter);
		}
	}

#if UE_INCLUDE_METRICS
	if(metricsHandle && IsValid(killer))
	{
		// Record player death
		if(killer->metricsHandle)
			metricsHandle->OnPlayerDie(killer->metricsHandle->Id());
		else
			metricsHandle->OnPlayerDie(0);
	}
#endif

	onCharacterKilled.Broadcast(killer, ability);
}

void ANetworkCharacter::OnStandardAttack(ANetworkCharacter* targetCharacter)
{
	if (m_modifierManager==nullptr)
	{
		FWARNING("m_modifierManager==nullptr");
		return;
	}
	for (AModifier* mod : m_modifierManager->m_modifiers)
	{
		if(IsValid(mod))
			mod->OnStandardAttack(targetCharacter);
	}
}
void ANetworkCharacter::AddHealth(int32 health)
{
	NativeAddHealth(health);
}
void ANetworkCharacter::NativeAddHealth(int32 health)
{
	check(Role == ROLE_Authority); // server call only
	check(health >= 0);

	if (m_health < m_maxHealth)
	{
		// Spawn combat text
		ADefaultPlayerController* const controller = Cast<ADefaultPlayerController>(GetController());
		if (IsValid(controller))
		{
			m_healAccumValue += health;
			if (m_healAccumTimer <= 0.0f) m_healAccumTimer += healthAccumTimer;
		}

		m_health += health;
		if (m_health > m_maxHealth)
			m_health = m_maxHealth;
	}
}

void ANetworkCharacter::RemoveHealth(int32 health)
{
	check(Role == ROLE_Authority); // Server only call
	m_health -= health;
}
void ANetworkCharacter::AddMana(float mana)
{
	check(Role == ROLE_Authority); // Server only call
	m_mana += mana * m_manaRegenMultiplier;
	if (m_mana > (m_maxMana-m_blockedMana))
		m_mana = (m_maxMana - m_blockedMana);
}
void ANetworkCharacter::RemoveMana(float mana)
{
	check(Role == ROLE_Authority); // Server only call
	if(mana > m_mana)
	{
		mana = m_mana; // mana has to remain positive or 0
	}
	m_mana -= mana;
}
int32 ANetworkCharacter::GetMana() const
{
	return m_mana;
}
int32 ANetworkCharacter::GetBlockedMana() const
{
	return m_blockedMana;
}
int32 ANetworkCharacter::GetMaxMana() const
{
	return m_maxMana;
}
int32 ANetworkCharacter::GetHealth() const
{
	return m_health;
}
int32 ANetworkCharacter::GetMaxHealth() const
{
	return m_maxHealth;
}
int32 ANetworkCharacter::GetArmor() const
{
	return m_armor;
}
float ANetworkCharacter::GetDamageMultiplier() const
{
	return m_damageMultiplier;
}
float ANetworkCharacter::GetCooldownReduction() const
{
	return m_cooldownReduction;
}
float ANetworkCharacter::GetAttackSpeed() const
{
	return m_attackSpeed;
}
float ANetworkCharacter::GetAttackDamage() const
{
	return m_attackDamageMultiplier;
}
float ANetworkCharacter::GetMagicDamage() const
{
	return m_magicDamageMultiplier;
}
float ANetworkCharacter::GetTotalDamage() const
{
	return m_totalDamage;
}
bool ANetworkCharacter::GetHitable() const
{
	return m_hitable;
}
void ANetworkCharacter::SetHitable(bool hittable)
{
	m_hitable = hittable;
}
bool ANetworkCharacter::IsStunned() const
{
	return m_stunned || m_channelStun;
}
bool ANetworkCharacter::CanRotate() const
{
	if(m_stunned)
		return false;
	if(m_channelStun && !m_allowChannelRotation)
		return false;
	return true;
}

bool ANetworkCharacter::IsSilenced() const
{
	return m_silenced;
}
bool ANetworkCharacter::IsChanneling() const
{
	return m_castingAbility != nullptr;
}
bool ANetworkCharacter::IsRanged() const
{
	return m_isRanged;
}
ANetworkCharacter* ANetworkCharacter::GetLastPlayerDamage() const
{
	return IsValid(m_lastPlayerDamage) ? m_lastPlayerDamage : nullptr;
}

bool ANetworkCharacter::CanBeStunned() const
{
	return canBeStunned;
}

float ANetworkCharacter::GetTimeSinceDamage() const
{
	return m_timeSinceDamage;
}

bool ANetworkCharacter::IsVisible() const
{
	return m_visible;
}