#include "EBBullet.h"

void AEBBullet::Deactivate() {
	//server only
	if (!HasAuthority()) { return; }
	OnDeactivated();
	this->DeactivateToPool();
	DeactivationBroadcast();
}

AEBBullet* AEBBullet::GetFromPool(UWorld* World, UClass* BulletClass) {
	AEBBullet* Pool = Cast<AEBBullet>(BulletClass->GetDefaultObject());

	if (Pool) {
		//find first of correct class;
		bool CleanupRequired=false;

		int32 FoundIndex = Pool->Pooled.IndexOfByPredicate(
			[&](auto InItem) {
			if (InItem.IsValid() && InItem->GetWorld() == World) {
				return true;
			}
			else {
				CleanupRequired = true;
				return false;
			}
		});

		TWeakObjectPtr<AEBBullet> Found = nullptr;
		if (FoundIndex != INDEX_NONE) {
			Found = Pool->Pooled[FoundIndex];
			Pool->Pooled.RemoveAtSwap(FoundIndex,EAllowShrinking::Yes);
		}

		if (CleanupRequired) {
#ifdef WITH_EDITOR
		if (Pool->DebugPooling) {
			GEngine->AddOnScreenDebugMessage(2, 2, FColor::White, TEXT("Invalid reference in pool, cleaning up"));
		}
#endif
			Pool->Pooled.RemoveAll([&](auto InItem) {
				if (InItem.IsValid() && InItem->GetWorld() == World) {
					return false;
				}
				else {
					return true;
				}
			});
		}

		return(Found.Get());
	}
	else {
		return nullptr;
	}
}

AEBBullet* AEBBullet::SpawnOrReactivate(UWorld* World, TSubclassOf<class AEBBullet> BulletClass, const FTransform& Transform, FVector BulletVelocity, AActor* BulletOwner, APawn* BulletInstigator, int nextEventFireID) {
	AEBBullet* bullet;

	AEBBullet* Recycled = GetFromPool(World, BulletClass); 

	if (Recycled) {
		AEBBullet* Default = Cast<AEBBullet>(BulletClass->GetDefaultObject());

		Recycled->Reset();
		Recycled->fireEventID = nextEventFireID;
		Recycled->SetOwner(BulletOwner);
		Recycled->SetInstigator(BulletInstigator);
		Recycled->SetActorTransform(Transform,false,nullptr,ETeleportType::TeleportPhysics);
		Recycled->Velocity = BulletVelocity;
		Recycled->SetActorHiddenInGame(Default->IsHidden());
		Recycled->SetActorTickEnabled(true);
		Recycled->CanRetrace = false;
		Recycled->IgnoredActors = Default->IgnoredActors;
		Recycled->SafeDelay = Default->SafeDelay;
		Recycled->SetLifeSpan(Default->InitialLifeSpan);
		Recycled->FinishSpawning(Transform);
		//if (!Recycled->HasActorBegunPlay()){ Recycled->BeginPlay(); }
		//Recycled->ReactivationBroadcast(UGameplayStatics::RebaseLocalOriginOntoZero(Recycled->GetWorld(), Transform.GetLocation()), BulletVelocity, BulletOwner, BulletInstigator);
#ifdef WITH_EDITOR
		if (Recycled->DebugPooling) {
			GEngine->AddOnScreenDebugMessage(0, 2, FColor::Green, TEXT("Recycling pooled bullet"));
		}
#endif
		return Recycled;
	}
	else {
		bullet = Cast<AEBBullet>(World->SpawnActorDeferred<AEBBullet>(BulletClass, Transform, BulletOwner, BulletInstigator));
		bullet->RandomStream.GenerateNewSeed();
		bullet->Velocity = BulletVelocity;
		bullet->fireEventID = nextEventFireID;
		bullet->FinishSpawning(Transform);
		//UGameplayStatics::FinishSpawningActor(bullet, Transform);
#ifdef WITH_EDITOR
		if (bullet->DebugPooling) {
			GEngine->AddOnScreenDebugMessage(0, 2, FColor::Orange, TEXT("Spawning new bullet"));
		}
#endif
		return bullet;
	}
}

void AEBBullet::FinishSpawning(FTransform Transform) {
	if(IsRecycled){
		if (!HasActorBegunPlay()){ 
			BeginPlay(); 
		}
		ReactivationBroadcast(UGameplayStatics::RebaseLocalOriginOntoZero(this->GetWorld(), Transform.GetLocation()), this->Velocity, this->GetOwner(), this->GetInstigator(), fireEventID);
	}else{
		UGameplayStatics::FinishSpawningActor(this, Transform);
	}
}

void AEBBullet::ReactivationBroadcast_Implementation(FVector_NetQuantize NewLocation, FVector NewVelocity, AActor* BulletOwner, APawn* BulletInstigator, int nextEventFireID) {
	if (!HasAuthority()) {
		AEBBullet* Default = Cast<AEBBullet>(this->StaticClass()->GetDefaultObject());

		SetOwner(BulletOwner);
		SetInstigator(BulletInstigator);
		fireEventID = nextEventFireID;
		SetActorLocation(UGameplayStatics::RebaseZeroOriginOntoLocal(GetWorld(), NewLocation));
		Velocity = NewVelocity;
		CanRetrace = false;

		SetActorHiddenInGame(Default->IsHidden());
		SetActorTickEnabled(true);
		SafeDelay = Default->SafeDelay;
		OwnerSafe = Default->SafeLaunch;
		BeginPlay();
	}
}

void AEBBullet::DeactivationBroadcast_Implementation() {
	if (!HasAuthority()) {
		OnDeactivated();
		this->DeactivateToPool();
	}
}

void AEBBullet::LifeSpanExpired() {
	Deactivate();
}

void AEBBullet::DeactivateToPool() {
	AEBBullet* Pool = Cast<AEBBullet>(GetClass()->GetDefaultObject());

	if (Pool && EnablePooling) {
		SetActorHiddenInGame(true);
		SetActorTickEnabled(false);
		Pool->Pooled.Add(this);
		EndPlay(EEndPlayReason::RemovedFromWorld);

		if (Pool->Pooled.Num() > MaxPoolSize) {
			AEBBullet* Oldest = (Pool->Pooled[0].Get());
			Pool->Pooled.RemoveAtSwap(0,EAllowShrinking::Yes);
			if (Oldest) { Oldest->Destroy(); }
		}

#ifdef WITH_EDITOR
		if (DebugPooling) {
			GEngine->AddOnScreenDebugMessage(2, 2, FColor::White, FString("Bullet pooled: ") + FString::FromInt(Pool->Pooled.Num()));
		}
#endif
	}
	else {
		Destroy();
	}
}