#include "GoapBlackboard.hpp"
#include <cstdlib>

std::array<std::string, 64> &GoapBlackboard::getBitNameRegistry()
{
	static std::array<std::string, 64> registry;
	static bool initialized = false;
	if (!initialized) {
		for (auto &s : registry)
			s.clear();
		initialized = true;
	}
	return registry;
}

void GoapBlackboard::setBitName(int index, const std::string &name)
{
	if (index < 0 || index >= 64)
		return;
	getBitNameRegistry()[index] = name;
}

const char *GoapBlackboard::getBitName(int index)
{
	if (index < 0 || index >= 64)
		return nullptr;
	const auto &name = getBitNameRegistry()[index];
	return name.empty() ? nullptr : name.c_str();
}

int GoapBlackboard::findBitByName(const std::string &name)
{
	if (name.empty())
		return -1;
	const auto &registry = getBitNameRegistry();
	for (int i = 0; i < 64; i++) {
		if (registry[i] == name)
			return i;
	}
	return -1;
}

bool GoapBlackboard::satisfies(const GoapBlackboard &other) const
{
	// Only compare bits that both sides consider relevant
	uint64_t relevantMask = bitmask & other.bitmask;

	// Check bits: for every bit set in other's mask (within relevant mask),
	// our bit must match
	uint64_t commonMask = mask & other.mask & relevantMask;
	if ((bits & commonMask) != (other.bits & commonMask))
		return false;

	// Also check bits that other has set but we don't (within relevant mask)
	uint64_t missingMask = other.mask & ~mask & relevantMask;
	if (missingMask) {
		// Other requires bits we don't have set -> fail
		// But only if other has those bits set to 1
		if ((other.bits & missingMask) != 0)
			return false;
	}

	// Check values: for every key in other, our value must match
	for (const auto &pair : other.values) {
		auto it = values.find(pair.first);
		if (it == values.end()) {
			// If we don't have the key, only satisfy if target is 0
			if (pair.second != 0)
				return false;
		} else if (it->second != pair.second) {
			return false;
		}
	}
	return true;
}

void GoapBlackboard::apply(const GoapBlackboard &other)
{
	// Apply bit effects
	bits = (bits & ~other.mask) | (other.bits & other.mask);
	mask |= other.mask;

	// Apply value effects
	for (const auto &pair : other.values)
		values[pair.first] = pair.second;
}

bool GoapBlackboard::getScalarValue(const std::string &key,
				      float &out) const
{
	auto itf = floatValues.find(key);
	if (itf != floatValues.end()) {
		out = itf->second;
		return true;
	}
	auto iti = values.find(key);
	if (iti != values.end()) {
		out = static_cast<float>(iti->second);
		return true;
	}
	return false;
}

int GoapBlackboard::distanceTo(const GoapBlackboard &target,
				       bool ignoreValues) const
{
	int distance = 0;

	// Only compare bits that both sides consider relevant
	uint64_t relevantMask = bitmask & target.bitmask;

	// Bit differences (within relevant mask)
	uint64_t commonMask = mask & target.mask & relevantMask;
	distance += __builtin_popcountll((bits ^ target.bits) & commonMask);

	// Bits target cares about but we don't have (within relevant mask)
	uint64_t missingInUs = target.mask & ~mask & relevantMask;
	distance += __builtin_popcountll(target.bits & missingInUs);

	// Bits we care about but target doesn't (within relevant mask)
	uint64_t missingInTarget = mask & ~target.mask & relevantMask;
	distance += __builtin_popcountll(bits & missingInTarget);

	if (ignoreValues)
		return distance;

	// Value differences (int only — planner ignores float/vec3)
	for (const auto &pair : target.values) {
		auto it = values.find(pair.first);
		if (it == values.end())
			distance += std::abs(pair.second);
		else
			distance += std::abs(it->second - pair.second);
	}

	// Values we have that target doesn't (may need to be cleared)
	for (const auto &pair : values) {
		if (target.values.find(pair.first) == target.values.end())
			distance += std::abs(pair.second);
	}

	return distance;
}

Ogre::String GoapBlackboard::dump() const
{
	Ogre::String result = "Blackboard:\n";

	result += "  Bits:\n";
	for (int i = 0; i < 64; i++) {
		if (hasBit(i)) {
			const char *name = getBitName(i);
			if (name)
				result += "    " + Ogre::String(name) + " = " +
					 (getBit(i) ? "true" : "false") + "\n";
			else
				result += "    bit[" +
					 Ogre::StringConverter::toString(i) + "] = " +
					 (getBit(i) ? "true" : "false") + "\n";
		}
	}

	if (!values.empty()) {
		result += "  Int values:\n";
		for (const auto &pair : values)
			result += "    " + pair.first + " = " +
				  Ogre::StringConverter::toString(pair.second) +
				  "\n";
	}

	if (!floatValues.empty()) {
		result += "  Float values:\n";
		for (const auto &pair : floatValues)
			result += "    " + pair.first + " = " +
				  Ogre::StringConverter::toString(pair.second) +
				  "\n";
	}

	if (!vec3Values.empty()) {
		result += "  Vec3 values:\n";
		for (const auto &pair : vec3Values)
			result += "    " + pair.first + " = (" +
				  Ogre::StringConverter::toString(pair.second.x) +
				  ", " +
				  Ogre::StringConverter::toString(pair.second.y) +
				  ", " +
				  Ogre::StringConverter::toString(pair.second.z) +
				  ")\n";
	}

	if (!stringValues.empty()) {
		result += "  String values:\n";
		for (const auto &pair : stringValues)
			result += "    " + pair.first + " = " + pair.second + "\n";
	}

	return result;
}

void GoapBlackboard::merge(const GoapBlackboard &other)
{
	// Merge bits
	bits = (bits & ~other.mask) | (other.bits & other.mask);
	mask |= other.mask;

	// Merge values
	for (const auto &pair : other.values)
		values[pair.first] = pair.second;
	for (const auto &pair : other.floatValues)
		floatValues[pair.first] = pair.second;
	for (const auto &pair : other.vec3Values)
		vec3Values[pair.first] = pair.second;
	for (const auto &pair : other.stringValues)
		stringValues[pair.first] = pair.second;
}

std::vector<int> GoapBlackboard::getSetBits() const
{
	std::vector<int> result;
	uint64_t m = mask;
	while (m) {
		int bit = __builtin_ctzll(m);
		result.push_back(bit);
		m &= m - 1;
	}
	return result;
}
