#include "MissionPrerequisites.h"

#include <sstream>
#include <ctime>

#include "CDClientManager.h"
#include "Logger.h"


PrerequisiteExpression::PrerequisiteExpression(const std::string& str) {
	std::stringstream a;
	std::stringstream b;
	std::stringstream s;

	auto bor = false;

	auto sub = false;

	auto done = false;

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

		const auto character = str[i];

		switch (character) {
		case '|':
			bor = true;
			b << str.substr(i + 1);
			done = true;
			break;
		case ' ':
		case ')':
			break;
		case ',':
		case '&':
		case '(':
			b << str.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':
			if (sub) {
				s << character;
			} else {
				a << character;
			}
			break;
		case ':':
			sub = true;
			break;
		default:
			break;
		}
	}

	this->m_or = bor;

	const auto aString = a.str();

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

	const auto subString = s.str();

	if (!subString.empty()) {
		this->sub = std::stoul(s.str());
	} else {
		this->sub = 0;
	}

	const auto bString = b.str();

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


bool PrerequisiteExpression::Execute(const std::unordered_map<uint32_t, Mission*>& missions) const {
	auto a = this->a == 0;

	auto b = this->b == nullptr;

	if (!a) {
		const auto index = missions.find(this->a);

		if (index != missions.end()) {
			const auto* mission = index->second;

			if (this->sub != 0) {
				// Special case for one Wisp Lee repeatable mission.
				a = mission->GetMissionState() == static_cast<eMissionState>(this->sub);
			} else if (mission->IsComplete()) {
				a = true;
			}
		}
	}

	if (!b) {
		b = this->b->Execute(missions);
	}

	if (this->m_or) {
		return a || b;
	}

	return a && b;
}


PrerequisiteExpression::~PrerequisiteExpression() {
	delete b;
}


bool MissionPrerequisites::CanAccept(const uint32_t missionId, const std::unordered_map<uint32_t, Mission*>& missions) {
	const auto& missionIndex = missions.find(missionId);

	if (missionIndex != missions.end()) {
		auto* mission = missionIndex->second;
		const auto& info = mission->GetClientInfo();

		if (info.repeatable) {
			const auto prerequisitesMet = CheckPrerequisites(missionId, missions);

			// Checked by client
			const time_t time = std::time(nullptr);
			const time_t lock = mission->GetTimestamp() + info.cooldownTime * 60;

			// If there's no time limit, just check the prerequisites, otherwise make sure both conditions are met
			return (info.cooldownTime == -1 ? prerequisitesMet : (lock - time < 0)) && prerequisitesMet;
		}

		// Mission is already accepted and cannot be repeatedly accepted
		return false;
	}

	// Mission is not yet accepted, check the prerequisites
	return CheckPrerequisites(missionId, missions);
}

bool MissionPrerequisites::CheckPrerequisites(uint32_t missionId, const std::unordered_map<uint32_t, Mission*>& missions) {
	const auto& index = expressions.find(missionId);
	if (index != expressions.end()) {
		return index->second->Execute(missions);
	}

	auto* missionsTable = CDClientManager::GetTable<CDMissionsTable>();
	const auto missionEntries = missionsTable->Query([=](const CDMissions& entry) {
		return entry.id == static_cast<int>(missionId);
		});

	if (missionEntries.empty())
		return false;

	auto* expression = new PrerequisiteExpression(missionEntries[0].prereqMissionID);
	expressions.insert_or_assign(missionId, expression);

	return expression->Execute(missions);
}

std::unordered_map<uint32_t, PrerequisiteExpression*> MissionPrerequisites::expressions = {};