#include "Preconditions.h"

#include "Game.h"
#include "dLogger.h"

#include <sstream>

#include "InventoryComponent.h"
#include "MissionComponent.h"
#include "Character.h"
#include "CharacterComponent.h"
#include "DestroyableComponent.h"
#include "GameMessages.h"


std::map<uint32_t, Precondition*> Preconditions::cache = {};

Precondition::Precondition(const uint32_t condition)
{
	std::stringstream query;

	query << "SELECT type, targetLOT, targetCount FROM Preconditions WHERE id = " << std::to_string(condition) << ";";

	auto result = CDClientDatabase::ExecuteQuery(query.str());

	if (result.eof())
	{
		this->type = PreconditionType::ItemEquipped;
		this->count = 1;
		this->values = { 0 };
		
		Game::logger->Log("Precondition", "Failed to find precondition of id (%i)!\n", condition);

		return;
	}

	this->type = static_cast<PreconditionType>(result.fieldIsNull(0) ? 0 : result.getIntField(0));

	if (!result.fieldIsNull(1))
	{
		std::istringstream stream(result.getStringField(1));
		std::string token;

		while (std::getline(stream, token, ','))
		{
			uint32_t value;
			if (GeneralUtils::TryParse(token, value))
			{
				this->values.push_back(value);
			}
		}
	}

	this->count = result.fieldIsNull(2) ? 1 : result.getIntField(2);

	result.finalize();
}


bool Precondition::Check(Entity* player, bool evaluateCosts) const
{
	if (values.empty())
	{
		return true; // There are very few of these
	}

	bool any;

	// Guesses
	switch (type) {
	case PreconditionType::HasItem:
	case PreconditionType::ItemEquipped:
	case PreconditionType::HasAchievement:
	case PreconditionType::MissionAvailable:
	case PreconditionType::OnMission:
	case PreconditionType::MissionComplete:
	case PreconditionType::PetDeployed:
	case PreconditionType::HasFlag:
	case PreconditionType::WithinShape:
	case PreconditionType::InBuild:
	case PreconditionType::TeamCheck:
	case PreconditionType::IsPetTaming:
	case PreconditionType::HasFaction:
	case PreconditionType::DoesNotHaveFaction:
	case PreconditionType::HasRacingLicence:
	case PreconditionType::DoesNotHaveRacingLicence:
	case PreconditionType::LegoClubMember:
	case PreconditionType::NoInteraction:
		any = true;
		break;
	case PreconditionType::DoesNotHaveItem:
	case PreconditionType::ItemNotEquipped:
	case PreconditionType::HasLevel:
		any = false;
		break;
	default:
		any = true;
		break;
	}

	auto passedAny = false;
	
	for (const auto value : values)
	{
		const auto passed = CheckValue(player, value, evaluateCosts);
		
		if (passed && any)
		{
			return true;
		}

		if (!passed && !any)
		{
			return false;
		}

		if (passed)
		{
			passedAny = true;
		}
	}

	return passedAny;
}


bool Precondition::CheckValue(Entity* player, const uint32_t value, bool evaluateCosts) const
{
	auto* missionComponent = player->GetComponent<MissionComponent>();
	auto* inventoryComponent = player->GetComponent<InventoryComponent>();
	auto* destroyableComponent = player->GetComponent<DestroyableComponent>();
	auto* characterComponent = player->GetComponent<CharacterComponent>();
	auto* character = player->GetCharacter();

	Mission* mission;

	switch (type)
	{
	case PreconditionType::ItemEquipped:
		return inventoryComponent->IsEquipped(value);
	case PreconditionType::ItemNotEquipped:
		return !inventoryComponent->IsEquipped(value);
	case PreconditionType::HasItem:
		if (evaluateCosts) // As far as I know this is only used for quickbuilds, and removal shouldn't actually be handled here.
		{
			inventoryComponent->RemoveItem(value, count);

			return true;
		}

		return inventoryComponent->GetLotCount(value) >= count;
	case PreconditionType::DoesNotHaveItem:
		return inventoryComponent->IsEquipped(value) < count;
	case PreconditionType::HasAchievement:
		mission = missionComponent->GetMission(value);

		return mission == nullptr || mission->GetMissionState() >= MissionState::MISSION_STATE_COMPLETE;
	case PreconditionType::MissionAvailable:
		mission = missionComponent->GetMission(value);

		return mission == nullptr || mission->GetMissionState() >= MissionState::MISSION_STATE_AVAILABLE;
	case PreconditionType::OnMission:
		mission = missionComponent->GetMission(value);

		return mission == nullptr || mission->GetMissionState() >= MissionState::MISSION_STATE_ACTIVE;
	case PreconditionType::MissionComplete:
		mission = missionComponent->GetMission(value);

		return mission == nullptr || mission->GetMissionState() >= MissionState::MISSION_STATE_COMPLETE;
	case PreconditionType::PetDeployed:
		return false; // TODO
	case PreconditionType::HasFlag:
		return character->GetPlayerFlag(value);
	case PreconditionType::WithinShape:
		return true; // Client checks this one
	case PreconditionType::InBuild:
		return character->GetBuildMode();
	case PreconditionType::TeamCheck:
		return false; // TODO
	case PreconditionType::IsPetTaming:
		return false; // TODO
	case PreconditionType::HasFaction:
		for (const auto faction : destroyableComponent->GetFactionIDs())
		{
			if (faction == static_cast<int>(value))
			{
				return true;
			}
		}

		return false;
	case PreconditionType::DoesNotHaveFaction:
		for (const auto faction : destroyableComponent->GetFactionIDs())
		{
			if (faction == static_cast<int>(value))
			{
				return false;
			}
		}

		return true;
	case PreconditionType::HasRacingLicence:
		return false; // TODO
	case PreconditionType::DoesNotHaveRacingLicence:
		return false; // TODO
	case PreconditionType::LegoClubMember:
		return false; // TODO
	case PreconditionType::NoInteraction:
		return false; // TODO
	case PreconditionType::HasLevel:
		return characterComponent->GetLevel() >= value;
	default:
		return true; // There are a couple more unknown preconditions. Always return true in this case.
	}
}

PreconditionExpression::PreconditionExpression(const std::string& conditions)
{
	if (conditions.empty())
	{
		empty = true;

		return;
	}
	
	std::stringstream a;
	std::stringstream b;

	auto bor = false;

	auto done = false;

	for (auto i = 0u; i < conditions.size(); ++i)
	{
		if (done)
		{
			break;
		}

		const auto character = conditions[i];

		switch (character)
		{
		case '|':
			bor = true;
			b << conditions.substr(i + 1);
			done = true;
			break;
		case ' ':
		case ')':
			break;
		case ',':
		case '&':
		case ';':
		case '(':
			b << conditions.substr(i + 1);
			done = true;
			break;
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			a << character;
			break;
		default:
			break;
		}
	}

	this->m_or = bor;

	const auto aString = a.str();

	if (!aString.empty())
	{
		this->condition = std::stoul(a.str());
	}
	else
	{
		this->condition = 0;
	}

	const auto bString = b.str();

	if (!bString.empty())
	{
		this->next = new PreconditionExpression(bString);
	}
	else
	{
		this->next = nullptr;
	}
}


bool PreconditionExpression::Check(Entity* player, bool evaluateCosts) const
{
	if (empty)
	{
		return true;
	}

	if (player->GetGMLevel() >= 9) // Developers can skip this for testing
	{
		return true;
	}
	
	const auto a = Preconditions::Check(player, condition, evaluateCosts);

	if (!a)
	{
		GameMessages::SendNotifyClientFailedPrecondition(player->GetObjectID(), player->GetSystemAddress(), u"", condition);
	}
	
	const auto b = next == nullptr ? true : next->Check(player);
	
	return m_or ? a || b : a && b;
}

PreconditionExpression::~PreconditionExpression()
{
	delete next;
}


bool Preconditions::Check(Entity* player, const uint32_t condition, bool evaluateCosts)
{
	Precondition* precondition;

	const auto& index = cache.find(condition);

	if (index != cache.end())
	{
		precondition = index->second;
	}
	else
	{
		precondition = new Precondition(condition);

		cache.insert_or_assign(condition, precondition);
	}

	return precondition->Check(player, evaluateCosts);
}


PreconditionExpression Preconditions::CreateExpression(const std::string& conditions)
{
	return PreconditionExpression(conditions);
}


Preconditions::~Preconditions()
{
	for (const auto& condition : cache)
	{
		delete condition.second;
	}

	cache.clear();
}