#include "dZoneManager.h"
#include "PetDigServer.h"
#include "DestroyableComponent.h"
#include "MissionComponent.h"
#include "EntityManager.h"
#include "Character.h"
#include "PetComponent.h"
#include "User.h"
#include "eMissionState.h"

std::vector<LWOOBJID> PetDigServer::treasures{};

const DigInfo PetDigServer::defaultDigInfo = DigInfo{ 3495, -1, -1, false, false, false, false };

/**
 * Summary of all the special treasure behaviors, indexed by their lot
 */
const std::map<LOT, DigInfo> PetDigServer::digInfoMap{
	// Regular treasures
	{3495, defaultDigInfo},

	// Pet cove treasure
	{7612, DigInfo { 7612, -1, -1, false, false, false, false }},

	// Gnarled Forest flag treasure
	{7410, DigInfo { 7410, -1, -1, false, true, false, false }},

	// Gnarled Forest crab treasure
	{9308, DigInfo { 9308, 7694, -1, false, false, false, false }},

	// Avant Gardens mission treasure
	{9307, DigInfo { 9307, -1, -1, false, true, false, true }},

	// Avant Gardens bouncer treasure
	{7559, DigInfo { 7559, -1, -1, false, false, true, false }},

	// Crux Prime dragon treasure
	{13098, DigInfo { 13098, 13067, 1298, false, false, false, false }},

	// Bone treasure (can only be digged using the dragon)
	{12192, DigInfo { 12192, -1, -1, true, false, false, false }},
};

void PetDigServer::OnStartup(Entity* self) {
	treasures.push_back(self->GetObjectID());
	const auto digInfoIterator = digInfoMap.find(self->GetLOT());
	const auto digInfo = digInfoIterator != digInfoMap.end() ? digInfoIterator->second : defaultDigInfo;

	// Reset any bouncers that might've been created by the previous dig
	if (digInfo.bouncer) {
		auto bounceNumber = GeneralUtils::UTF16ToWTF8(self->GetVar<std::u16string>(u"BouncerNumber"));
		auto bouncerSpawners = Game::zoneManager->GetSpawnersByName("PetBouncer" + bounceNumber);
		auto switchSpawners = Game::zoneManager->GetSpawnersByName("PetBouncerSwitch" + bounceNumber);

		for (auto* bouncerSpawner : bouncerSpawners) {
			for (auto* bouncer : bouncerSpawner->m_Info.nodes)
				bouncerSpawner->Deactivate();
			bouncerSpawner->Reset();
		}

		for (auto* switchSpawner : switchSpawners) {
			switchSpawner->Deactivate();
			switchSpawner->Reset();
		}
	}
}

void PetDigServer::OnDie(Entity* self, Entity* killer) {
	const auto iterator = std::find(treasures.begin(), treasures.end(), self->GetObjectID());
	if (iterator != treasures.end()) {
		treasures.erase(iterator);
	}

	auto* owner = killer->GetOwner();
	const auto digInfoIterator = digInfoMap.find(self->GetLOT());
	const auto digInfo = digInfoIterator != digInfoMap.end() ? digInfoIterator->second : defaultDigInfo;

	if (digInfo.spawnLot >= 0) {
		PetDigServer::SpawnPet(self, owner, digInfo);
	} else if (digInfo.builderOnly) {

		// Some treasures may only be retrieved by the player that built the diggable
		auto builder = self->GetVar<LWOOBJID>(u"builder"); // Set by the pet dig build script
		if (builder != owner->GetObjectID())
			return;
	} else if (digInfo.xBuild) {
		PetDigServer::HandleXBuildDig(self, owner, killer);
		return;
	} else if (digInfo.bouncer) {
		PetDigServer::HandleBouncerDig(self, owner);
	}

	PetDigServer::ProgressPetDigMissions(owner, self);

	self->SetNetworkVar<bool>(u"treasure_dug", true);
	// TODO: Reset other pets

	// Handles smashing leftovers (edge case for the AG X)
	auto* xObject = Game::entityManager->GetEntity(self->GetVar<LWOOBJID>(u"X"));
	if (xObject != nullptr) {
		xObject->Smash(xObject->GetObjectID(), eKillType::VIOLENT);
	}
}

void PetDigServer::OnUse(Entity* self, Entity* user) {
	LOG("Treasure used!");

	auto* petComponent = PetComponent::GetActivePet(user->GetObjectID());
	if (!petComponent) return;

	if(petComponent->GetIsReadyToDig()) { // TODO: Add handling of the "first time" dig message
		petComponent->SetTreasureTime(2.0f);

		auto* destroyableComponent = user->GetComponent<DestroyableComponent>();
		if (!destroyableComponent) return;
		
		auto imagination = destroyableComponent->GetImagination();
		imagination -= 1; // TODO: Get rid of this magic number
		destroyableComponent->SetImagination(imagination);
		Game::entityManager->SerializeEntity(user);
	}
}

void PetDigServer::HandleXBuildDig(const Entity* self, Entity* owner, Entity* pet) {
	auto playerID = self->GetVar<LWOOBJID>(u"builder");
	if (playerID == LWOOBJID_EMPTY || playerID != owner->GetObjectID())
		return;

	auto* playerEntity = Game::entityManager->GetEntity(playerID);
	if (!playerEntity || !playerEntity->GetParentUser() || !playerEntity->GetParentUser()->GetLastUsedChar())
		return;

	auto* player = playerEntity->GetCharacter();
	const auto groupID = self->GetVar<std::u16string>(u"groupID");
	int32_t playerFlag = 0;

	// The flag that the player dug up
	if (groupID == u"Flag1") {
		playerFlag = 61;
	} else if (groupID == u"Flag2") {
		playerFlag = 62;
	} else if (groupID == u"Flag3") {
		playerFlag = 63;
	}

	// If the player doesn't have the flag yet
	if (playerFlag != 0 && !player->GetPlayerFlag(playerFlag)) {
		auto* petComponent = pet->GetComponent<PetComponent>();
		if (petComponent != nullptr) {
			// TODO: Pet state = 9 ??
		}

		// Shows the flag object to the player
		player->SetPlayerFlag(playerFlag, true);
	}

	auto* xObject = Game::entityManager->GetEntity(self->GetVar<LWOOBJID>(u"X"));
	if (xObject != nullptr) {
		xObject->Smash(xObject->GetObjectID(), eKillType::VIOLENT);
	}
}

void PetDigServer::HandleBouncerDig(const Entity* self, const Entity* owner) {
	auto bounceNumber = GeneralUtils::UTF16ToWTF8(self->GetVar<std::u16string>(u"BouncerNumber"));
	auto bouncerSpawners = Game::zoneManager->GetSpawnersByName("PetBouncer" + bounceNumber);
	auto switchSpawners = Game::zoneManager->GetSpawnersByName("PetBouncerSwitch" + bounceNumber);

	for (auto* bouncerSpawner : bouncerSpawners) {
		bouncerSpawner->Activate();
	}

	for (auto* switchSpawner : switchSpawners) {
		switchSpawner->Activate();
	}
}

/**
 * Progresses the Can You Dig It mission and the Pet Excavator Achievement if the player has never completed it yet
 * \param owner the owner that just made a pet dig something up
 */
void PetDigServer::ProgressPetDigMissions(const Entity* owner, const Entity* chest) {
	auto* missionComponent = owner->GetComponent<MissionComponent>();

	if (missionComponent != nullptr) {
		// Can You Dig It progress
		const auto digMissionState = missionComponent->GetMissionState(843);
		if (digMissionState == eMissionState::ACTIVE) {
			missionComponent->ForceProgress(843, 1216, 1);
		}

		// Pet Excavator progress
		const auto excavatorMissionState = missionComponent->GetMissionState(505);
		if (excavatorMissionState == eMissionState::ACTIVE) {
			if (chest->HasVar(u"PetDig")) {
				int32_t playerFlag = 1260 + chest->GetVarAs<int32_t>(u"PetDig");
				Character* player = owner->GetCharacter();

				// check if player flag is set
				if (!player->GetPlayerFlag(playerFlag)) {
					missionComponent->ForceProgress(505, 767, 1);
					player->SetPlayerFlag(playerFlag, 1);
				}
			}
		}
	}
}

/**
 * Some treasures spawn special pets, this handles that case
 * \param owner the owner that just made a pet dig something up
 * \param digInfo information regarding the treasure, will also contain info about the pet to spawn
 */
void PetDigServer::SpawnPet(Entity* self, const Entity* owner, const DigInfo digInfo) {
	// Some treasures require a mission to be active
	if (digInfo.requiredMission >= 0) {
		auto* missionComponent = owner->GetComponent<MissionComponent>();
		if (missionComponent != nullptr && missionComponent->GetMissionState(digInfo.requiredMission) < eMissionState::ACTIVE) {
			return;
		}
	}

	EntityInfo info{};
	info.lot = digInfo.spawnLot;
	info.pos = self->GetPosition();
	info.rot = self->GetRotation();
	info.spawnerID = self->GetSpawnerID();
	info.settings = {
			new LDFData<LWOOBJID>(u"tamer", owner->GetObjectID()),
			new LDFData<std::string>(u"group", "pet" + std::to_string(owner->GetObjectID())),
			new LDFData<std::string>(u"spawnAnim", "spawn-pet"),
			new LDFData<float>(u"spawnTimer", 1.0)
	};

	auto* spawnedPet = Game::entityManager->CreateEntity(info);
	Game::entityManager->ConstructEntity(spawnedPet);
}

Entity* PetDigServer::GetClosestTresure(NiPoint3 position) {
	float closestDistance = 0;
	Entity* closest = nullptr;

	for (const auto tresureId : treasures) {
		auto* tresure = Game::entityManager->GetEntity(tresureId);

		if (tresure == nullptr) continue;

		float distance = Vector3::DistanceSquared(tresure->GetPosition(), position);

		if (closest == nullptr || distance < closestDistance) {
			closestDistance = distance;
			closest = tresure;
		}
	}

	return closest;
}