#include "UnrealProject.h"
#include "SpawnerBase.h"
#include "CharacterBase.h"
#include "ItemBase.h"
#include "BaseSkillObject.h"
#include "WiebertAnimation.h"
#include "DefaultGameState.h"
#include "MainCharacterAnimation.h"
#include "SkillTreeState.h"


ACharacterBase::ACharacterBase()
{
	m_team = 0;
	bReplicates = true;

	visionRadius = 1000;
}

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

	// If this actor is attached to a spawner base, it must be ignored
	AActor* const parent = GetAttachParentActor();
	if (parent != nullptr && parent->IsA<ASpawnerBase>())
		return;
	
	// Register to the minimap
	UWorld* const world = GetWorld();
	if (IsValid(world))
	{
		ADefaultGameState* const gameState = Cast<ADefaultGameState>(world->GetGameState());
		if (IsValid(gameState))
		{
			const FVector location = GetActorLocation();
			minimapHandle.position = FVector2D(location.X, location.Y);
			minimapHandle.character = this;
			gameState->minimapQuadtree.AddObject(minimapHandle);
		}
	}
}
void ACharacterBase::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);

	// Unregister to the minimap
	ADefaultGameState* const gameState = Cast<ADefaultGameState>(GetWorld()->GetGameState());
	if (IsValid(gameState) && minimapHandle.root)
		gameState->minimapQuadtree.RemoveObject(minimapHandle);

	minimapHandle.root = nullptr;
	minimapHandle.node = nullptr;
	minimapHandle.character = nullptr;
}
void ACharacterBase::Tick(float deltaTime)
{
	Super::Tick(deltaTime);

	const FVector location = GetActorLocation();
	minimapHandle.position = FVector2D(location.X, location.Y);
}

void ACharacterBase::m_EquipSkills_Implementation(const TArray<UBaseSkillObject*>& skills)
{
	for(UBaseSkillObject* skill : skills)
	{
		// Check if not yet equiped
		if(!m_equipedSkills.Contains(skill))
		{
			AItemBase::CreateItemsFromSkill(GetWorld(), skill, this);
			m_equipedSkills.Add(skill);
		}
	}
}

void ACharacterBase::EquipSkills(const TArray<UBaseSkillObject*>& skills)
{
	m_EquipSkills(skills);
}
void ACharacterBase::EquipSkillsFromSkillTreeState(const struct FSkillTreeState& skState)
{
	TArray<UBaseSkillObject*> skillObjects;
	for(const FSkillTreeStateObject& obj : skState.objects)
	{
		UBaseSkillObject* skillObj = obj.skillObject->GetDefaultObject<UBaseSkillObject>();
		if(!skillObj)
			continue;
		skillObjects.Add(skillObj);
	}
	m_EquipSkills(skillObjects);
}
void ACharacterBase::EquipItems_Implementation(const TArray<TSubclassOf<AItemBase>>& itemClasses)
{
	for(auto& itemClass : itemClasses)
	{
		EquipItem_Implementation(itemClass);
	}
}
void ACharacterBase::EquipItem_Implementation(TSubclassOf<class AItemBase> itemClass)
{
	if(itemClass == nullptr)
		return;

	AItemBase* item = GetWorld()->SpawnActor<AItemBase>(itemClass);
	AItemBase* existing = AItemBase::CheckSlotOccupied(item->type, this);
	if(existing)
		existing->Destroy();
	if(item)
	{
		if(!item->AttachItemToCharacter(this))
		{
			item->Destroy();
		}
	}
}

void ACharacterBase::SendInitialAppearance(APlayerController* pc)
{
	if(Role == ROLE_Authority)
	{
		m_ReceiveInitialAppearance(pc, GetEquipedSkills(), GetCharacterCustomization());
	}
}

void ACharacterBase::m_ReceiveInitialAppearance_Implementation(APlayerController* pc, const TArray<class UBaseSkillObject*>& itemClasses, const FCharacterCustomization& cc)
{
	APlayerController* localPC = GetGameInstance()->GetFirstLocalPlayerController();
	if(pc == localPC)
	{
		EquipSkills(itemClasses);
		SetCustomizations_Implementation(cc);
	}
}

void ACharacterBase::Destroyed()
{
	auto destroyItems = m_items;
	for(auto item : destroyItems)
	{
		if(IsValid(item))
		{
			item->Destroy();
		}
	}
	Super::Destroyed();
}

TArray<TSubclassOf<AItemBase>> ACharacterBase::GetEquipedItemClasses() const
{
	TArray<TSubclassOf<AItemBase>> ret;
	for(auto item : m_items)
	{
		ret.Add(item->GetClass());
	}
	return ret;
}
const TArray<class AItemBase*>& ACharacterBase::GetEquipedItems() const
{
	return m_items;
}

const FCharacterCustomization& ACharacterBase::GetCharacterCustomization() const
{
	static FCharacterCustomization dummy;
	USkeletalMeshComponent* skMesh = GetMesh();
	if(skMesh)
	{
		UWiebertAnimation* animBP = Cast<UWiebertAnimation>(skMesh->GetAnimInstance());
		if(animBP)
		{
			return animBP->charCustomization;
		}
		else
		{
			UMainCharacterAnimation* charBP = Cast<UMainCharacterAnimation>(skMesh->GetAnimInstance());
			if (charBP)
			{
				return charBP->charCustomization;
			}
		}
	}
	return dummy;
}

const TArray<class UBaseSkillObject*>& ACharacterBase::GetEquipedSkills() const
{
	return m_equipedSkills;
}

void ACharacterBase::UnequipSkills_Implementation(const TArray<UBaseSkillObject*>& skills)
{
	for(auto skill : skills)
	{
		if(m_equipedSkills.Contains(skill))
		{
			TArray<AItemBase*> items;
			m_mappedItems.MultiFind(skill, items);
			for(auto item : items)
				item->Destroy();
			m_equipedSkills.Remove(skill);
		}
	}
}

void ACharacterBase::ClearEquipment_Implementation()
{
	auto clearItems = m_items;
	for(auto item : clearItems)
	{
		if(IsValid(item))
		{
			item->Destroy();
		}
	}
	m_mappedItems.Reset();
	m_equipedSkills.SetNum(0);
	m_items.SetNum(0);
}
void ACharacterBase::SetCustomizations_Implementation(const FCharacterCustomization& cc)
{
	USkeletalMeshComponent* skMesh = GetMesh();
	if(skMesh)
	{
		UWiebertAnimation* animBP = Cast<UWiebertAnimation>(skMesh->GetAnimInstance());
		if(animBP)
		{
			animBP->SetCharacterCustomization(cc);
		}
		else
		{
			UMainCharacterAnimation* charBP = Cast<UMainCharacterAnimation>(skMesh->GetAnimInstance());
			if (charBP)
			{
				charBP->SetCharacterCustomization(cc);
			}
		}
	}
	else
	{
		GERROR("Can't set customization, no skeletal mesh on character");
	}
}

void ACharacterBase::m_OnItemAdded(AItemBase* item)
{
	// Add item to list of items assigned to given skill object
	if(item->skillObject)
		m_mappedItems.Add(item->skillObject, item);
	m_items.Add(item);
}
void ACharacterBase::m_OnItemRemoved(AItemBase* item)
{
	// Remove mapped item
	m_mappedItems.RemoveSingle(item->skillObject, item);
	m_items.Remove(item);
}

void ACharacterBase::m_OnRep_Team()
{

}

int32 ACharacterBase::GetTeam() const
{
	return m_team;
}
void ACharacterBase::SetTeam(int32 team)
{
	check(Role == ROLE_Authority); // Server only call
	m_team = team;
	m_OnRep_Team();
}

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

	DOREPLIFETIME(ACharacterBase, m_team);
	DOREPLIFETIME(ACharacterBase, playerName);
}

void ACharacterBase::RegisterEffect(class AEffect* effect)
{
	if(effect != nullptr)
		m_effects.Add(effect);
}
void ACharacterBase::UnRegisterEffect(class AEffect* effect)
{
	if(effect == nullptr)
	{
		FWARNING("invalid effect is unregistering");
		return;
	}
	m_effects.Remove(effect);
}
TArray<class AEffect*> ACharacterBase::GetEffects()
{
	return m_effects;
}