// Project Lab - NHTV Igad

#include "UnrealProject.h"
#include "NPCBase.h"
#include "NetworkDoor.h"
#include "DefaultGameMode.h"
#include "CreatureSpawn.h"
#include "Modifier.h"
#include "NativeModifiers.h"
#include "NetworkPlayer.h"
#include "TouhouBoss.h"

ACreatureSpawn::ACreatureSpawn()
{
	respawnTime = 5;
	m_respawnTimer = 0;

	spawnContinuous = true;

	spawnTrigger = CreateDefaultSubobject<USphereComponent>(TEXT("Sphere"));
	spawnTrigger->SetCollisionProfileName(TEXT("PlayerOverlap"));
	spawnTrigger->AttachTo(RootComponent);
	spawnTrigger->OnComponentBeginOverlap.AddDynamic(this, &ACreatureSpawn::OnOverlapBegin);
	spawnTrigger->OnComponentEndOverlap.AddDynamic(this, &ACreatureSpawn::OnOverlapEnd);
	spawnTrigger->SetSphereRadius(3000);
	spawnTrigger->SetVisibility(false);
}

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

	if (Role != ROLE_Authority)
		return; 

	if (aggroRadius < 0) aggroRadius = 0;
	if (respawnTime < 0) respawnTime = 0;

	if (spawns.Num() == 0)
	{
		JWARNING("Empty spawner in scene");
		return;
	}
	else if (spawns.Num() != 1 && isBoss)
	{
		FWARNING("Unexpected amount of spawners for boss");
	}

	// Calculate spawn sub positions
	if (spawns.Num() > 1)
	{
		float angle = (360.0f) / 180.0f * PI;
		float anglestep = (angle) / (spawns.Num() + 1);
		float rot = angle * 0.5f;
		for (int32 i = 0; i < spawns.Num(); i++)
		{
			float f = cosf(rot);
			float r = sinf(rot);
			rot += anglestep;
			if (hasGeneral)
			{
				m_resetPoints.Add(GetActorLocation() + FVector(f * 200, r * 200, 0));
			}
			else m_resetPoints.Add(FVector(formationPoints[i].X,formationPoints[i].Y,0) + GetActorLocation());
		}
	}
	else if (spawns.Num() == 1)
		m_resetPoints.Add(GetActorLocation());

	m_respawnTimer = -1.0f;

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

}

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

	if (Role != ROLE_Authority)
		return;
	
	UWorld* const world = GetWorld();
	if (!world) return;
	
	m_respawnTimer = m_respawnTimer > 0 ? (m_respawnTimer - DeltaTime) : 0;

	/*if (m_nearbyPlayers.empty())
	{
		// Despawn if there are no players nearby and the timer is depleted
		if (m_respawnTimer <= 0)
		{
		//	m_DespawnMobs();
		}
	}
	else
	{*/
		// Respawn time mobs if the timer has ran out and we are missing mobs
		if (m_respawnTimer <= 0 && m_mobCount != m_mobs.Num() && spawnContinuous)
			m_RespawnMobs();
//	}

	// No mobs to update
	if (m_mobs.Num() == 0)
		return;
	
	// Decrement respawntime
	bool anyIdle = false;
	for (int32 i = 0; i < spawns.Num(); i++)
	{
		if (!spawns[i] || !m_mobs[i])
			continue;
		// Check if any mob is idle
		const FVector2D mobPos = FVector2D(m_mobs[i]->GetActorLocation().X, m_mobs[i]->GetActorLocation().Y);
		const FVector2D targetPos = FVector2D(SpawnResetPosition().X, SpawnResetPosition().Y);
		const float distSqr = FVector2D::DistSquared(mobPos, targetPos);
		if (!m_mobs[i]->target && distSqr < deaggroRadius*deaggroRadius)
			anyIdle = true;
	}
	
	//// We can start attacking if any mob is idle and there is a player nearby
	//if (anyIdle)
	//{
	//	for (int32 i = 0; i < spawns.Num(); i++)
	//	{
	//		if (m_mobs[i] == nullptr)
	//			continue;
	//		//find the biggest threat of the enemies
	//		ANetworkCharacter* threat = m_GetBiggestThreat(i);
	//		if (m_mobs[i]->target != nullptr)
	//			continue;
	//		const FVector2D mobPos = FVector2D(m_mobs[i]->GetActorLocation().X, m_mobs[i]->GetActorLocation().Y);
	//		const FVector2D targetPos = FVector2D(m_resetPoints[i].X, m_resetPoints[i].Y);
	//		const float distSqr = FVector2D::DistSquared(mobPos, targetPos);
	//		if (distSqr < aggroRadius * aggroRadius)// (m_mobs[i]->collisionRadius*m_mobs[i]->collisionRadius)*collisionScaler)
	//		{
	//			FPRINT("idledistsettarget");
	//			m_mobs[i]->target = threat;
	//		}
	//
	//	}
	//}
	
	if (isBoss && m_mobs.Num() == 1)
	{
		UWorld* const world = GetWorld();
		if (!world) return;
		ADefaultGameMode* mode = Cast<ADefaultGameMode>(world->GetAuthGameMode());
		if (!mode) return;
	
		//check if it is more than 1 team
		int teamcount = 0;
		int32 team = 0;
		for (auto iter = m_nearbyPlayers.begin(); iter != m_nearbyPlayers.end(); iter++)
		{
			 ANetworkCharacter* player = *iter;
			if ((player->GetActorLocation() - GetActorLocation()).Size() < aggroRadius)
			{
				if (teamcount == 0)
				{
					team = player->GetTeam();
					teamcount++;
				}
				else if (team != player->GetTeam())
				{
					teamcount++;
					break;
				}
			}
		}
		if (teamcount > 1 && m_invulnerableModifier == nullptr)
		{
			if (m_mobs[0] != nullptr)
			{
				ModifierManager* manager = m_mobs[0]->GetModifierManager();
				TSubclassOf<ADamageTakenModifier> subclass = ADamageTakenModifier::StaticClass();
				m_invulnerableModifier = GetWorld()->SpawnActor<AModifier>(subclass);
				if (m_invulnerableModifier != nullptr)
				{
					Cast<ADamageTakenModifier>(m_invulnerableModifier)->damageTakenMultiplier = 0.0f;
					m_invulnerableModifier->character = m_mobs[0];
					manager->AddModifier(m_invulnerableModifier);
					Cast<ATouhouBoss>(m_mobs[0])->SetShield(true);
				}
				else
				{
					FPRINT("failed to spawn m_invulnerableModifier in creaturespawn");
				}
			}
		}
		else if (teamcount <2 && m_invulnerableModifier != nullptr)
		{
			m_invulnerableModifier->ForceDestroy();
			m_invulnerableModifier = nullptr;
		}
	}
}

int32 ACreatureSpawn::OnMobDie(class ANPCBase* mob)
{
	const int32 idx = Super::OnMobDie(mob);

	m_respawnTimer = respawnTime;

	if (m_mobCount == 0)
	{
		for (int32 i = 0; i < doorsToOpen.Num(); i++)
		{
			if (doorsToOpen[i])
				doorsToOpen[i]->SetDoorState(false);
		}
	}

	return idx;
}

void ACreatureSpawn::SpawnMobs()
{
	m_RespawnMobs();
}

void ACreatureSpawn::OnOverlapBegin(AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	if (!OtherActor)
		return;
	if (!OtherActor->IsA(ANetworkCharacter::StaticClass()))
		return;

	ANetworkCharacter* player = Cast<ANetworkCharacter>(OtherActor);
	if(player)
		m_OnPlayerEnterOverlap(*player);
}
void ACreatureSpawn::OnOverlapEnd(AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	if (!OtherActor)
		return;
	if (!OtherActor->IsA(ANetworkCharacter::StaticClass()))
		return;

	ANetworkCharacter* player = Cast<ANetworkCharacter>(OtherActor);
	if(player)
		m_OnPlayerExitOverlap(*player);
}

void ACreatureSpawn::m_DespawnMobs()
{
	for (int32 i = 0; i < m_mobs.Num(); i++)
	{
		if (m_mobs[i])
		{
			m_mobs[i]->UnsetSpawn();
			m_mobs[i]->Destroy();
			m_mobs[i] = nullptr;
		}
	}

	for (int32 i = 0; i < doorsToOpen.Num(); i++)
	{
		if (doorsToOpen[i])
			doorsToOpen[i]->SetDoorState(false);
	}
	formationEnemies.Empty();
	m_mobCount = 0;
}
void ACreatureSpawn::m_RespawnMobs()
{
	UWorld* const world = GetWorld();
	if (!world)
		return;
	for (int32 i = 0; i < m_mobs.Num(); i++)
	{
		if (!spawns[i])
			continue;
		if (m_mobs[i] == nullptr)
		{
			// Respawn!
			FTransform spawnTransform = GetTransform();
			FVector spawnVector = FVector();
			spawnTransform.SetLocation(m_resetPoints[i]  + FVector(0, 0, 120));
			if (SpawnLocation.Num()>0)
			{
				int randnum = rand() % SpawnLocation.Num();
				spawnTransform = SpawnLocation[randnum]->GetTransform();
				FVector rotation = FVector(GetActorLocation() - spawnTransform.GetLocation());
				spawnTransform.SetRotation(FQuat(rotation.Rotation()));
				//spawnVector = SpawnLocation->GetActorLocation() - GetActorLocation();
				//spawnTransform.SetLocation(spawnVector);
			}

			ANPCBase* character = world->SpawnActorDeferred<ANPCBase>(spawns[i], spawnTransform, nullptr, nullptr, ESpawnActorCollisionHandlingMethod::AlwaysSpawn);
			
			character->SetSpawn(this);
			character->SetTeam((int32)team);
			m_mobs[i] = character;
			m_mobCount++;
			m_OnMobSpawn(i);
			UGameplayStatics::FinishSpawningActor(character, spawnTransform);
	 		character->SpawnDefaultController();
		}
	}

	m_respawnTimer = respawnTime;
}


void ACreatureSpawn::GetNewTarget(class ANPCBase* mob)
{
	if (mob == nullptr)
		return;

	int32 index = -1;
	for (ANPCBase* currentMob : m_mobs)
	{
		index++;
		if (mob != currentMob)
			continue;


		ANetworkCharacter* threat = nullptr;
		bool end = false;
		//keeps looping to make sure if the biggest threat is not in aggrorange it still works;
		int32 biggestThreat = -1;
		float dist = 1e34;
		for (ANetworkCharacter *character:m_nearbyPlayers)
		{
			if (character->GetTeam() == (int)team)
				continue;

			//check if it is bigger than the last biggestThreat
	
				const FVector2D spawnpost = FVector2D(GetActorLocation().X, GetActorLocation().Y);
				const FVector2D targetPos = FVector2D(character->GetActorLocation().X, character->GetActorLocation().Y);
				const float distSqr = FVector2D::DistSquared(spawnpost, targetPos);
				//making sure the new biggest thread is withing the aggroRadius
				if (distSqr < aggroRadius * aggroRadius&&dist>distSqr)
				{
					dist = distSqr;
					threat = character;
				
				}
			
		}
	

		m_resetTimer = resetTimer;
		currentMob->target = threat;
		return;
	}

	return;
}