#include "FurnitureLibrary.hpp"
#include <OgreResourceGroupManager.h>
#include <OgreDataStream.h>
#include <OgreLogManager.h>
#include <algorithm>

FurnitureLibrary &FurnitureLibrary::getInstance()
{
	static FurnitureLibrary instance;
	return instance;
}

void FurnitureLibrary::loadAll()
{
	if (m_loaded)
		return;
	m_loaded = true;
	m_definitions.clear();

	std::vector<Ogre::String> names;
	const std::vector<Ogre::String> &groups =
		Ogre::ResourceGroupManager::getSingleton()
			.getResourceGroups();
	for (const auto &group : groups) {
		std::vector<Ogre::String> groupNames =
			*Ogre::ResourceGroupManager::getSingleton()
				 .findResourceNames(group, "furniture-*.json");
		names.insert(names.end(), groupNames.begin(),
			   groupNames.end());
	}

	for (const auto &name : names) {
		Ogre::String group =
			Ogre::ResourceGroupManager::getSingleton()
				.findGroupContainingResource(name);
		Ogre::DataStreamPtr stream =
			Ogre::ResourceGroupManager::getSingleton()
				.openResource(name, group);
		Ogre::String jsonStr = stream->getAsString();
		try {
			nlohmann::json jdata = nlohmann::json::parse(jsonStr);
			FurnitureDefinition def;
			def.name = jdata.value("name", name);
			def.meshName = jdata.value("mesh", "");
			def.jsonData = jdata;
			if (jdata.contains("tags") && jdata["tags"].is_array()) {
				for (auto &tag : jdata["tags"]) {
					def.tags.push_back(tag.get<std::string>());
				}
			}
			if (!def.meshName.empty()) {
				m_definitions.push_back(std::move(def));
			}
		} catch (const std::exception &e) {
			Ogre::LogManager::getSingleton().logMessage(
				"FurnitureLibrary: Failed to parse " + name +
				": " + e.what());
		}
	}

	Ogre::LogManager::getSingleton().logMessage(
		"FurnitureLibrary: Loaded " +
		std::to_string(m_definitions.size()) +
		" furniture definitions");
}

const std::vector<FurnitureDefinition> &FurnitureLibrary::getDefinitions()
	const
{
	return m_definitions;
}

const FurnitureDefinition *FurnitureLibrary::findByName(
	const std::string &name) const
{
	for (const auto &def : m_definitions) {
		if (def.name == name)
			return &def;
	}
	return nullptr;
}

std::vector<const FurnitureDefinition *> FurnitureLibrary::findByTags(
	const std::vector<std::string> &requiredTags,
	const std::vector<std::string> &forbiddenTags) const
{
	std::vector<const FurnitureDefinition *> results;
	for (const auto &def : m_definitions) {
		bool match = true;
		for (const auto &req : requiredTags) {
			if (std::find(def.tags.begin(), def.tags.end(), req) ==
			    def.tags.end()) {
				match = false;
				break;
			}
		}
		if (!match)
			continue;
		for (const auto &forb : forbiddenTags) {
			if (std::find(def.tags.begin(), def.tags.end(), forb) !=
			    def.tags.end()) {
				match = false;
				break;
			}
		}
		if (match)
			results.push_back(&def);
	}
	return results;
}

const FurnitureDefinition *FurnitureLibrary::selectByTags(
	const std::vector<std::string> &requiredTags,
	std::mt19937 &rng,
	const std::vector<std::string> &forbiddenTags) const
{
	auto candidates = findByTags(requiredTags, forbiddenTags);
	if (candidates.empty())
		return nullptr;

	std::uniform_int_distribution<size_t> dist(0, candidates.size() - 1);
	return candidates[dist(rng)];
}
