#include "UnrealProject.h"
#include "BlueprintAbilityLibrary.h"

#include "DealDamageProxy.h"
#include "AbilityEventGroup.h"
#include "AbilityTriggerBase.h"
#include "Modifier.h"
#include "NetworkCharacter.h"
#include "ProjectileBase.h"
#include "Class.h"

ULib::ULib(const FObjectInitializer& init)
	: Super(init)
{
}
AActor* ULib::BeginSpawning2(UObject* WorldContextObject, TSubclassOf<AActor> ActorClass)
{
	return UGameplayStatics::BeginDeferredActorSpawnFromClass(WorldContextObject, ActorClass, FTransform());
}
AActor* ULib::FinishSpawning2(class AActor* Actor)
{
	if(!Actor)
		return nullptr;
	return UGameplayStatics::FinishSpawningActor(Actor, FTransform());
}

AActor* ULib::BeginSpawningGroup(UObject* WorldContextObject, TSubclassOf<AActor> ActorClass)
{
	if(!WorldContextObject->GetWorld()->GetAuthGameMode())
		return nullptr;
	AActor* actor = UGameplayStatics::BeginDeferredActorSpawnFromClass(WorldContextObject, ActorClass, FTransform());
	if(!actor)
		return nullptr;
	AAbilityEventGroup* newGroup = Cast<AAbilityEventGroup>(actor);
	check(newGroup);

	AAbilityEventGroup* parent = Cast<AAbilityEventGroup>(WorldContextObject);
	//GWPRINT(L"Parent = " + parent);
	if(!parent)
	{
		GWERROR(L"Cannot call FinishSpawningGroup from any other source than AAbilityEventGroup");
		newGroup->Destroy();
		return nullptr;
	}
	parent->TransitionTo(newGroup);
	return newGroup;
}
AActor* ULib::FinishSpawningGroup(class AActor* Actor)
{
	if(!Actor)
		return nullptr;
	return FinishSpawning2(Actor);
}

AActor* ULib::BeginSpawningModifier(UObject* WorldContextObject, TSubclassOf<AActor> ActorClass)
{
	if(!WorldContextObject->GetWorld()->GetAuthGameMode())
		return nullptr;
	AActor* actor = UGameplayStatics::BeginDeferredActorSpawnFromClass(WorldContextObject, ActorClass, FTransform());
	if(!actor)
		return nullptr;
	AModifier* newModifier = Cast<AModifier>(actor);
	check(newModifier);

	ADealDamageProxy* damageProxy = Cast<ADealDamageProxy>(WorldContextObject);
	if(damageProxy)
	{
		newModifier->character = damageProxy->character;
		newModifier->abilityInfo = damageProxy->abilityInfo;
	}
	else
	{
		GWERROR(L"Cannot spawn modifier from a source that is not a ADealDamageProxy");
		newModifier->Destroy();
		return nullptr;
	}
	return newModifier;
}
AActor* ULib::FinishSpawningModifier(class AActor* Actor, class ANetworkCharacter* target)
{
	if(!IsValid(Actor))
		return nullptr;
	AModifier* mod = Cast<AModifier>(Actor);
	if(!IsValid(target))
	{
		FString name = "<none>";
		if(mod->abilityInfo)
			name = mod->abilityInfo->GetName();
		GWERROR(L"Modifier needs a target (spawned by " + name + L")");
		Actor->Destroy();
		return nullptr;
	}
	//GWPRINT(L"Modifier Target = " + target);
	ModifierManager* mm = target->GetModifierManager();
	if(!mm)
	{
		GWWARNING(L"Can't spawn modifier on a target that doesn't have a modifier manager. Might be in the process of destroying");
		Actor->Destroy();
		return nullptr;
	}
	mod->target = target;
	check(mod);
	mm->AddModifier(mod);

	return FinishSpawning2(Actor);
}

AActor* ULib::BeginSpawningTrigger(UObject* WorldContextObject, TSubclassOf<AActor> ActorClass, const FTransform& SpawnTransform)
{
	if(!WorldContextObject->GetWorld()->GetAuthGameMode())
		return nullptr;
	AActor* actor = UGameplayStatics::BeginDeferredActorSpawnFromClass(WorldContextObject, ActorClass, SpawnTransform);
	if(!actor)
		return nullptr;
	AAbilityTriggerBase* newTrigger = Cast<AAbilityTriggerBase>(actor);
	check(newTrigger);
	ADealDamageProxy* damageProxy = Cast<ADealDamageProxy>(WorldContextObject);
	if(damageProxy)
	{
		newTrigger->character = damageProxy->character;
		newTrigger->abilityInfo = damageProxy->abilityInfo;
	}
	else
	{
		GWERROR(L"Cannot spawn trigger from a source that is not a ADealDamageProxy");
		actor->Destroy();
		return nullptr;
	}
	return newTrigger;
}
AActor* ULib::FinishSpawningTrigger(class AActor* Actor, const FTransform & SpawnTransform)
{
	if(!Actor)
		return nullptr;
	return UGameplayStatics::FinishSpawningActor(Actor, SpawnTransform);
}

AActor* ULib::BeginSpawningProjectile(UObject* WorldContextObject, TSubclassOf<AActor> ActorClass, const FTransform& SpawnTransform)
{
	if(!WorldContextObject->GetWorld()->GetAuthGameMode())
		return nullptr;
	AActor* actor = UGameplayStatics::BeginDeferredActorSpawnFromClass(WorldContextObject, ActorClass, SpawnTransform);
	if(!actor)
		return nullptr;
	AProjectileBase* newProjectile = Cast<AProjectileBase>(actor);
	check(newProjectile);
	ADealDamageProxy* damageProxy = Cast<ADealDamageProxy>(WorldContextObject);
	if(damageProxy)
	{
		newProjectile->character = damageProxy->character;
		newProjectile->abilityInfo = damageProxy->abilityInfo;
	}
	else
	{
		GWERROR(L"Cannot spawn projectile from a source that is not a ADealDamageProxy");
		actor->Destroy();
		return nullptr;
	}
	return newProjectile;
}
AActor* ULib::FinishSpawningProjectile(class AActor* Actor, const FTransform& SpawnTransform)
{
	return UGameplayStatics::FinishSpawningActor(Actor, SpawnTransform);
}

bool ULib::IsContainedInCone(FVector origin, FVector forward, float coneAngle, AActor* other)
{
	if(other->IsPendingKillPending())
		return false;

	//always hits at 360 degrees
	if(coneAngle >= 360.0f)
		return true;

	FVector dist = other->GetActorLocation() - origin;
	dist.Normalize();
	forward.Normalize();
	float dot = FVector::DotProduct(dist, forward);
	float angle = acosf(dot);

	float tempMaxAngle = coneAngle / 360;
	tempMaxAngle *= PI;

	bool ret = tempMaxAngle > angle;
	return ret;
}
int32 ULib::CharacterConeOverlap(UObject* WorldContextObject, FVector origin, float coneRadius, 
	FCharacterConeOverlapCallback callback, FVector forward, float coneAngle, EAbilityFilter filter)
{
	UWorld* world = GEngine->GetWorldFromContextObject(WorldContextObject);
	check(world);

	if(world->GetAuthGameMode() == nullptr)
		return 0;
	m_overlapResultArray.Empty();
	// Get the character
	ANetworkCharacter* character = Cast<ANetworkCharacter>(WorldContextObject);
	if(!character)
	{
		ADealDamageProxy* proxy = Cast<ADealDamageProxy>(WorldContextObject);
		if(!proxy)
		{
			GWERROR(L"CharacterConeOverlap failed, no caller character or proxy");
			return 0;
		}
		character = proxy->character;
	}

	FCollisionShape shape;
	shape.SetCapsule(coneRadius, 400.0f);

	TArray<FOverlapResult> overlaps;
	if(!world->OverlapMultiByObjectType(
		overlaps, origin, FQuat::Identity,
		FCollisionObjectQueryParams::AllDynamicObjects, shape,
		FCollisionQueryParams::DefaultQueryParam))
	{
		return 0;
	}
	TArray<class ANetworkCharacter*> test;
	if(callback.IsBound())
	{
		for(int32 i = 0; i < overlaps.Num(); i++)
		{
			FOverlapResult& overlap = overlaps[i];
			ANetworkCharacter* target = Cast<ANetworkCharacter>(overlap.GetActor());
			if (target && (overlap.Component->GetCollisionProfileName() == FName("Units") || overlap.Component->GetCollisionProfileName() == FName("Players")) &&
				IsContainedInCone(origin, forward, coneAngle, target) &&
				ULib::CheckAbilityFilter(filter, character, target))
			{
			
				callback.Execute(target);
				m_overlapResultArray.Add(target);
			}
		}
	}
	// = test;
	return overlaps.Num();
}

int32 ULib::ClosestCharacterConeOverlap(UObject* WorldContextObject, FVector origin, float coneRadius,
	FClosestCharacterConeOverlapCallback callback, FVector forward, float coneAngle, EAbilityFilter filter)
{
	UWorld* world = GEngine->GetWorldFromContextObject(WorldContextObject);
	check(world);

	if (world->GetAuthGameMode() == nullptr)
		return 0;

	// Get the character
	ANetworkCharacter* character = Cast<ANetworkCharacter>(WorldContextObject);
	if (!character)
	{
		ADealDamageProxy* proxy = Cast<ADealDamageProxy>(WorldContextObject);
		if (!proxy)
		{
			GWERROR(L"CharacterConeOverlap failed, no caller character or proxy");
			return 0;
		}
		character = proxy->character;
	}

	FCollisionShape shape;
	shape.SetCapsule(coneRadius, 400.0f);

	TArray<FOverlapResult> overlaps;
	if (!world->OverlapMultiByObjectType(
		overlaps, origin, FQuat::Identity,
		FCollisionObjectQueryParams::AllDynamicObjects, shape,
		FCollisionQueryParams::DefaultQueryParam))
	{
		return 0;
	}
	ANetworkCharacter* closest = nullptr;
	float dist = 9999999999999.0f;
	if (callback.IsBound())
	{
		for (int32 i = 0; i < overlaps.Num(); i++)
		{
			FOverlapResult& overlap = overlaps[i];
			ANetworkCharacter* target = Cast<ANetworkCharacter>(overlap.GetActor());
			if (target && IsContainedInCone(origin, forward, coneAngle, target) &&	ULib::CheckAbilityFilter(filter, character, target))
			{
				if ((target->GetActorLocation() - origin).Size() < dist)
				{
					closest = target;
					dist = (target->GetActorLocation() - origin).Size();
				}
			}
		}
	}
	if(closest)
				callback.Execute(closest);
	return overlaps.Num();

}
TArray<ANetworkCharacter*> ULib::m_overlapResultArray;
bool ULib::CheckAbilityFilter(EAbilityFilter filter, class ANetworkCharacter* self, class ANetworkCharacter* other)
{
	if(!self)
		return false;
	if(!other)
		return false;
	if(((uint8)filter & ABILITY_FILTER_ENEMY) != 0)
	{
		if(self->GetTeam() != other->GetTeam())
			return true;
	}
	if(((uint8)filter & ABILITY_FILTER_ALLY) != 0)
	{
		if(self->GetTeam() == other->GetTeam())
			return true;
	}
	return false;
}

TArray<ANetworkCharacter*>ULib::GetCharacterOverlaps()
{
	TArray<ANetworkCharacter*> returnArray;
	for (ANetworkCharacter* character : m_overlapResultArray)
	{
		if(IsValid(character))
			returnArray.Add(character);
	}
	return returnArray;
}