// Project Lab - NHTV Igad

#include "UnrealProject.h"
#include "NPCBase.h"
#include "MinionAnimInstance.h"
#include "SpawnerBase.h"
#include "modifier.h"
#include "NetworkPlayer.h"
#include "TeamData.h"
#include "BossBase.h"
#include "DrawDebugHelpers.h"
#include "AI/Navigation/NavigationPath.h"

static UTeamData* teamData;

ANPCBase::ANPCBase()
{
	teamData = ConstructorHelpers::FObjectFinder<UTeamData>(L"/Game/Assets/TeamData").Object;

	PrimaryActorTick.bCanEverTick = true;
	m_spawn = nullptr;
	m_dead = false;
	baseAttackSpeed = 2.0f;	
	baseMaxHealth = 200.0f;
	baseArmor = 30.0f;
	baseMaxMana = 1000.0f;
	baseAttackSpeed = 1.0f;
	maxAvoidanceForce = 200.0f;
	m_reachedStart = false;
	//visionTrigger = CreateDefaultSubobject<UCubeComponent>(TEXT("Vision Trigger"));

}
void ANPCBase::BeginPlay()
{
	Super::BeginPlay();
	if (Role != ROLE_Authority)
		return;

	m_health = m_maxHealth = baseMaxHealth;
	m_mana = m_maxMana = baseMaxMana;
	if (m_spawn)
		m_targetPoint = m_spawn->GetActorLocation();
	else m_targetPoint = GetActorLocation();
	collisionRadius = GetCapsuleComponent()->GetScaledCapsuleRadius();
	m_walkSpeed = GetCharacterMovement()->MaxWalkSpeed;
	m_OnRep_Team();
	GetCapsuleComponent()->BodyInstance.bLockXRotation = true;
	GetCapsuleComponent()->BodyInstance.bLockYRotation = true;
	animInstance = Cast<UMinionAnimInstance>(GetMesh()->GetAnimInstance());
	patrolTime = 5.0f;
}
void ANPCBase::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);
	if (Role != ROLE_Authority)
		return;
	if(m_spawn && EndPlayReason == EEndPlayReason::Destroyed)
		m_spawn->OnMobDie(this);

}
void ANPCBase::Tick(float deltaTime)
{
	Super::Tick(deltaTime);
	if (Role != ROLE_Authority)
		return;
	UNavigationSystem* navSys = UNavigationSystem::GetCurrent(GetWorld());
	if (!navSys || !GetController() || !m_spawn)
	{
		//RERROR("Boss base does not have a navigation system");
		return;
	}

	if (!IsValid(target))
		target = nullptr;

	if (m_dead)
	{
		return;
	}
	if (!IsValid(target))
	{
		m_spawn->GetNewTarget(this);
	}

	if (!IsValid(target) && !m_hasGeneral)
	{
		MoveToPoint(m_targetPoint);
	}
	else
	{
		if (FVector::DistSquared(m_spawn->GetActorLocation(), GetActorLocation()) > m_spawn->deaggroRadius * m_spawn->deaggroRadius)
		{
			// Reset aggro radius
			target = nullptr;
			m_isPulled = false;
		}
	}



}
int32  ANPCBase::NativeDealDamage(class ANetworkCharacter* dealer, int32 damage, float armorPercentageIgnore, class UAbilityInfo* ability)
{
	target = dealer;
	m_OnRep_Team();
	return Super::NativeDealDamage(dealer, damage, armorPercentageIgnore, ability);
}
void ANPCBase::ResetModifiers()
{
	Super::ResetModifiers();
	m_maxHealth = baseMaxHealth;
	m_maxMana = baseMaxMana;
	m_attackSpeed = baseAttackSpeed;
	m_armor = baseArmor;
}

void ANPCBase::OnDeath(UAbilityInfo* ability)
{
	//should be removed later, this is in here untill the animations are actually in the game
	Super::OnDeath(ability);
	return;
	target = nullptr;
	m_dead = true;
	m_modifierManager->ClearModifiers();
}

void ANPCBase::CalcAvoidance(const FVector& target)
{ 
	UNavigationSystem* NavSys = UNavigationSystem::GetCurrent(GetWorld());
//	NavSys->SimpleMoveToLocation(this->Controller, target);
	if (Cast<ABossBase>(this))
	{
		NavSys->SimpleMoveToLocation(this->Controller, target);
		return;
	}
	FVector newLoc = FVector::ZeroVector;
	newLoc = GetClosestPathLocation(target) - GetActorLocation();
	newLoc.Normalize();
	newLoc *= GetCharacterMovement()->GetMaxSpeed();
	newLoc = newLoc + CalcAvoidanceVector(); //+ calc avoicance vector;
	FVector vel = TruncateVector(GetCharacterMovement()->Velocity + newLoc, GetCharacterMovement()->GetMaxSpeed());
	vel += GetActorLocation();
	NavSys->SimpleMoveToLocation(this->Controller, vel);
	SetActorRotation(FVector(FVector(vel.X, vel.Y, GetActorLocation().Z) - GetActorLocation()).Rotation());
	
}

void ANPCBase::PatrolBetweenLocations(FVector startlocation, FVector endLocation, float deltaTime)
{
	FVector actorlocation = GetActorLocation();
	if (m_reachedStart)
	{
		MoveToPoint(endLocation);
		enableVisonCone = false;
	}
	else
	{
		MoveToPoint(startlocation);
		enableVisonCone = false;
	}
	//wait a few second to walk back
	float dist = FVector::DistSquaredXY(startlocation, actorlocation);
	if (dist < 50.0f*50.0f)
	{
		m_patrolTimer  -= deltaTime;
		enableVisonCone = true;
		if (m_patrolTimer <= 0)
		{
			m_reachedStart = true;
			m_patrolTimer = patrolTime;
	
		}


	}
	dist = FVector::DistSquaredXY(endLocation, actorlocation);
	if (dist<  50.0f*50.0f)
	{
		m_patrolTimer -= deltaTime;
		enableVisonCone = true;
		if (m_patrolTimer <= 0)
		{
			m_reachedStart = false;
			m_patrolTimer = patrolTime;
		}
	}
	
}
void ANPCBase::MoveToPoint(const FVector& target)
{
	UNavigationSystem* NavSys = UNavigationSystem::GetCurrent(GetWorld());
	GetCharacterMovement()->MaxWalkSpeed = m_walkSpeed;
	float distance = FVector::DistSquared(target, GetActorLocation());
	if (IsPendingKill() || IsPendingKillPending())
		return;

	if (!IsStunned())
	{
		CalcAvoidance( target);
		SetActorRotation(FVector(FVector(target.X, target.Y, GetActorLocation().Z) - GetActorLocation()).Rotation());
		
	}
	else
	{
		
		NavSys->SimpleMoveToLocation(this->Controller, GetActorLocation());
	}



}
void ANPCBase::MoveToPointSlow(const FVector& target, float dist)
{
	UNavigationSystem* NavSys = UNavigationSystem::GetCurrent(GetWorld());
	if (IsPendingKill() || IsPendingKillPending())
		return;
	GetCharacterMovement()->MaxWalkSpeed = m_walkSpeed/2;
	if (m_lastLoc != target)
	{
		if (m_lastLoc == FVector::ZeroVector || FVector::DistSquaredXY(m_lastLoc, GetActorLocation()) < dist*dist)
		{
			//RPRINT("test");
			m_lastLoc =UNavigationSystem::ProjectPointToNavigation(GetWorld(), target);
			//m_lastLoc = target;
		}
	}
	MoveToPoint(m_lastLoc);
	
}
void ANPCBase::OnOverlapBegin(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	if (!IsValid(OtherActor))
		return;
	if (!OtherActor->IsA(ANPCBase::StaticClass()))
		return;
	collisionList.Add(Cast<ANPCBase>(OtherActor));

}
void ANPCBase::OnOverlapEnd(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	if (!IsValid(OtherActor))
		return;
	if (!OtherActor->IsA(ANPCBase::StaticClass()))
		return;
	collisionList.Remove(Cast<ANPCBase>(OtherActor));
}
void ANPCBase::SetSpawn(class ASpawnerBase* spawn)
{
	check(Role == ROLE_Authority);
	check(!m_spawn);
	m_spawn = spawn;
}
void ANPCBase::SetControlPoint(FVector controlPoint)
{
	check(Role == ROLE_Authority);
	m_targetPoint = controlPoint;
}

void ANPCBase::m_OnRep_Team()
{
	Super::m_OnRep_Team();
	if(!m_materialInstance)
	{
		m_materialInstance = GetMesh()->CreateDynamicMaterialInstance(0);
	}
	if(m_materialInstance)
	{
		float scalar = (float)m_health / (float)m_maxHealth;
		m_materialInstance->SetVectorParameterValue("TeamColor", teamData->GetTeamColor(GetTeam()));
		m_materialInstance->SetScalarParameterValue("FlameEdgeSoftness", 100.0f - (100.0f*scalar));
		m_materialInstance->SetScalarParameterValue("FlameEffectIntensity", (scalar));
		m_materialInstance->SetScalarParameterValue("RandomOffset", FMath::FRand());
	}
}

void ANPCBase::UnsetSpawn()
{
	check(Role == ROLE_Authority);
	m_spawn = nullptr;
}
void ANPCBase::ChangeNPCAnimation_Implementation(uint8 state )
{
	if (IsValid(animInstance))
		animInstance->ChangeAnimationStateTo((EMinionAnimState)(state));
}
FVector ANPCBase::CalcAvoidanceVector()
{
	FVector retVector = FVector::ZeroVector;
	float dynLenght = GetCharacterMovement()->Velocity.Size() / GetCharacterMovement()->GetMaxSpeed();
	//change getactorforward by velocity
	FVector vel = GetCharacterMovement()->Velocity;
	vel.Normalize();
	aheadvec1 =GetActorLocation()+ vel*dynLenght;
	aheadvec2 = GetActorLocation() + vel*dynLenght*0.5;
	//	DrawDebugSphere(GetWorld(), aheadvec1, 24, 32, FColor(255, 255, 255));
	//	DrawDebugSphere(GetWorld(), aheadvec2, 24, 32, FColor(255, 0, 0));
	ANPCBase* obstacle = GetClosestObstacle();
	if (IsValid(obstacle))
	{
		retVector = aheadvec1 - obstacle->GetActorLocation();
		retVector.Z = 0.0f;
		retVector.Normalize();
		retVector *= maxAvoidanceForce;

	}
//	DrawDebugLine(GetWorld(), GetActorLocation(), GetActorLocation() + retVector, FColor(255, 255, 0));
	return retVector;
}///
ANPCBase* ANPCBase::GetClosestObstacle()
{
	ANPCBase* obstacle = nullptr;
	float distance = 1e34;
	for (int i = 0; i < m_spawn->m_mobs.Num(); i++)
	{
		ANPCBase* current = m_spawn->m_mobs[i];
		if(!IsValid(current)||current==this)
			continue;
		float tempdistance = FVector::DistSquared(current->GetActorLocation(), this->GetActorLocation());
		if (tempdistance < distance)
		{
			distance = tempdistance;
			obstacle = current;
		}
	}
	if (!IsValid(obstacle))
		return nullptr;
	if (FVector::DistSquared(aheadvec1, obstacle->GetActorLocation())>100 * 100 && FVector::DistSquared(aheadvec2, obstacle->GetActorLocation())>100 * 100)
		obstacle = nullptr;
	return obstacle;
}
FVector ANPCBase::TruncateVector(FVector inVector, float maxForce)
{
	if (inVector.Size() > maxForce)
	{
		inVector.Normalize();
		inVector *= maxForce;
	}
	return inVector;
}
FVector ANPCBase::GetClosestPathLocation(FVector endlocation)
{
	FVector returnVec = FVector();
	UNavigationPath *tpath;
	UNavigationSystem* navsys = UNavigationSystem::GetCurrent(GetWorld());
	tpath = navsys->FindPathToLocationSynchronously(GetWorld(), GetActorLocation(), endlocation);
	if (tpath == nullptr||tpath->PathPoints.Num()<=1)
		return endlocation;
	return tpath->PathPoints[tpath->PathPoints.Num()-1];
}