#include "utility/xmlloader.h"
#include "graphics/tiletype.h"

#include <sstream>
#include <fstream>
#include <filesystem>
#include <tinyxml2.h>

const WeaponData* XMLLoader::getWeaponDataByName(const char* name) const
{
	for (int i = 0; i < weaponData.size(); i++)
	{
		if (weaponData[i].name == name)
			return &weaponData[i];
	}
	return nullptr;
}

bool XMLLoader::loadScenes(const char* sceneFolder)
{
	std::filesystem::path folder(sceneFolder);
	for (auto& file : std::filesystem::directory_iterator(folder))
	{
		if (!file.path().has_extension() || !file.path().has_filename() || !file.exists() || file.is_directory())
			continue;
		SceneData scene;
		if (!loadXmlScene((const char*)file.path().string().c_str(), &scene))
			continue;
		scenes.try_emplace(scene.id, std::make_shared<SceneData>(scene));
	}
	return true;
}

bool XMLLoader::loadXmlScene(const char* xmlFile, SceneData* out)
{
	tinyxml2::XMLDocument doc;
	if (doc.LoadFile(xmlFile) != tinyxml2::XML_SUCCESS)
		return false;
	tinyxml2::XMLElement* scene = doc.FirstChildElement("scene");

	const char* type, * id;
	if (scene->QueryStringAttribute("type", &type) != tinyxml2::XML_SUCCESS ||
		scene->QueryStringAttribute("id", &id) != tinyxml2::XML_SUCCESS)
		return false;
	out->type = type;
	out->id = id;
	if (!loadMap(xmlFile, out))
		return false;
	if (!loadEntityData(xmlFile, out))
		return false;
	return true;
}

bool XMLLoader::loadMap(const char* xmlFile, SceneData* out)
{
	tinyxml2::XMLDocument doc;
	if (doc.LoadFile(xmlFile) != tinyxml2::XML_SUCCESS)
		return false;

	tinyxml2::XMLElement* map = doc.FirstChildElement("scene")->FirstChildElement("map");
	const char* name, * file;
	if (map->QueryStringAttribute("name", &name) != tinyxml2::XML_SUCCESS ||
		map->QueryIntAttribute("width", &out->map.width) != tinyxml2::XML_SUCCESS ||
		map->QueryIntAttribute("height", &out->map.height) != tinyxml2::XML_SUCCESS ||
		map->QueryStringAttribute("file", &file) != tinyxml2::XML_SUCCESS)
		return false;
	map->QueryFloatAttribute("tilesize", &out->map.tileSize);

	out->map.name = name;
	out->map.file = file;

	auto spriteIDs = map->FirstChildElement("spriteids");
	auto tileData = map->FirstChildElement("tiledata");
	if (spriteIDs == NULL || tileData == NULL)
		return false;

	auto data = tileData->FirstChildElement("row");

	for (tinyxml2::XMLElement* e = spriteIDs->FirstChildElement("row"); e != NULL; e = e->NextSiblingElement("row"))
	{
		std::vector<std::shared_ptr<Tile>> buffer;
		std::string row = e->Attribute("data", NULL);
		std::string dataRow = (data == NULL) ? "0" : data->Attribute("data", NULL);

		std::string tileString;
		std::stringstream ss(row);
		std::stringstream ssData(dataRow);

		Tile t;

		// TODO: Read TileData and move this loop to make sure we're reading SpriteID refer to xml
		while (!ss.eof())
		{
			std::getline(ss, tileString, ',');
			std::stringstream idss(tileString);
			int id;
			idss >> id;
			if (!ssData.eof())
			{
				std::getline(ssData, tileString, ',');
				std::stringstream datass(tileString);
				int dataID;
				datass >> dataID;
				t.tileData = (Tile::TileData)dataID;
			}
			else
				t.tileData = (Tile::TileData)1;
			t.spriteID = (Tile::SpriteID)id;
			buffer.push_back(std::make_shared<Tile>(t));
		}
		if (data != NULL)
			data = data->NextSiblingElement("row");
		out->map.groundTiles.push_back(buffer);
	}
	return true;
}

bool XMLLoader::loadEntityData(const char* xmlFile, SceneData* out)
{
	tinyxml2::XMLDocument doc;
	if (doc.LoadFile(xmlFile) != tinyxml2::XML_SUCCESS)
		return false;

	tinyxml2::XMLElement* entities = doc.FirstChildElement()->FirstChildElement("entities");

	// Adding the player. Player must be in the scene or the scene will not load!
	tinyxml2::XMLElement* playerElement = entities->FirstChildElement("player");
	if (playerElement == NULL)
		return false;
	int x, y;
	float frameSize = 64.f;
	bool isDirectional = false;
	const char* spritePath, * weaponName = "pistolGun";
	if (playerElement->QueryIntAttribute("x", &x) != tinyxml2::XML_SUCCESS ||
		playerElement->QueryIntAttribute("y", &y) != tinyxml2::XML_SUCCESS)
		return false;
	playerElement->QueryStringAttribute("weapon", &weaponName);
	tinyxml2::XMLElement* sprite = playerElement->FirstChildElement("sprite");
	if (sprite == NULL)
		return false;
	if (sprite->QueryStringAttribute("file", &spritePath) != tinyxml2::XML_SUCCESS)
		return false;
	sprite->QueryFloatAttribute("framesize", &frameSize);
	sprite->QueryBoolAttribute("directional", &isDirectional);
	out->entities.push_back({ true, x, y, spritePath, frameSize, weaponName, "", isDirectional});

	// Adding every other entity...
	// TODO: Add npcs to game and enable their use with XMLLoader

	for (tinyxml2::XMLElement* e = entities->FirstChildElement("entity"); e != NULL; e = e->NextSiblingElement("entity"))
	{
		int x, y;
		float frameSize = 64.f;
		const char* spritePath, * weaponName = "pistolGun", * scriptPath;
		bool isDirectional = false;
		if (e->QueryIntAttribute("x", &x) != tinyxml2::XML_SUCCESS ||
			e->QueryIntAttribute("y", &y) != tinyxml2::XML_SUCCESS)
			return false;
		e->QueryStringAttribute("weapon", &weaponName);
		tinyxml2::XMLElement* sprite = e->FirstChildElement("sprite");
		if (sprite == NULL)
			return false;
		if (sprite->QueryStringAttribute("file", &spritePath) != tinyxml2::XML_SUCCESS)
			return false;
		sprite->QueryFloatAttribute("frameSize", &frameSize);
		sprite->QueryBoolAttribute("directional", &isDirectional);
		tinyxml2::XMLElement* script = e->FirstChildElement("script");
		if (script == NULL || script->QueryStringAttribute("file", &scriptPath))
		{
			out->entities.push_back({ false, x, y, spritePath, frameSize, weaponName, "", isDirectional });
			continue;
		}
		out->entities.push_back({ false, x, y, spritePath, frameSize, weaponName, scriptPath, isDirectional });
	}

	return true;
}

float getFloatIfExists(tinyxml2::XMLElement* e)
{
	float buf = 0.f;
	if (e)
	{
		e->QueryFloatText(&buf);
	}
	return buf;
}

bool XMLLoader::loadWeapons(const char* weaponFolder)
{
	// We are gonna check every xml file within the weaponFolder, then check every weapon node within each file.
	std::filesystem::path folder(weaponFolder);
	if (!std::filesystem::exists(folder) || !std::filesystem::is_directory(folder))
		return false;
	for (auto& file : std::filesystem::directory_iterator(folder))
	{
		if (!file.path().has_extension() || !file.path().has_filename() || !file.exists() || file.is_directory())
			continue;
		tinyxml2::XMLDocument doc;
		if (doc.LoadFile(file.path().generic_string().c_str()) != tinyxml2::XML_SUCCESS)
			continue;
		tinyxml2::XMLElement* guns = doc.FirstChildElement("weapons");
		if (guns == NULL)
			continue;

		for (tinyxml2::XMLElement* weapon = guns->FirstChildElement("weapon"); weapon != NULL; weapon = weapon->NextSiblingElement("weapon"))
		{
			// populate weapon data into this temp buffer, then push it into our list of weapons
			WeaponData data;
			const char* name, * sprite, * bulletSprite;
			// Getting top level weapon data, attribs in the weapon Node
			if (weapon->QueryStringAttribute("name", &name) != tinyxml2::XML_SUCCESS)
				continue;
			weapon->QueryFloatAttribute("fireSpeed", &data.fireSpeed);
			// Getting weapon sprite information, held in the sprite node
			tinyxml2::XMLElement* wepSprite = weapon->FirstChildElement("sprite");
			if (wepSprite)
			{
				wepSprite->QueryStringAttribute("file", &sprite);
				wepSprite->QueryBoolAttribute("animated", &data.animated);
				if (data.animated)
					wepSprite->QueryFloatAttribute("frameSize", &data.frameSize);
				if (wepSprite->ChildElementCount() != 0)
				{
					tinyxml2::XMLElement* size = wepSprite->FirstChildElement("size");
					size->QueryFloatAttribute("x", &data.sizeX);
					size->QueryFloatAttribute("y", &data.sizeY);
					tinyxml2::XMLElement* offset = wepSprite->FirstChildElement("offset");
					offset->QueryFloatAttribute("x", &data.offsetX);
					offset->QueryFloatAttribute("y", &data.offsetY);
				}
			}
			else {
				// If we don't have a sprite element, default to pistol
				sprite = "sprites/pistolAtlas256.png";
				data.animated = true;
				data.frameSize = 256.f;
				data.sizeX = 30.f;
				data.sizeY = 30.f;
			}
			// getting bullet information held in the bullet node and each child node held therein
			tinyxml2::XMLElement* bullet = weapon->FirstChildElement("bullet");

			if (bullet->QueryStringAttribute("sprite",						&bulletSprite)			!= tinyxml2::XML_SUCCESS ||
				bullet->QueryBoolAttribute(	 "animated",					&data.bulletAnimated)	!= tinyxml2::XML_SUCCESS ||
				bullet->FirstChildElement("size")->QueryFloatAttribute("x", &data.bulletSizeX)		!= tinyxml2::XML_SUCCESS ||
				bullet->FirstChildElement("size")->QueryFloatAttribute("y", &data.bulletSizeY)		!= tinyxml2::XML_SUCCESS)
				continue;

			data.name = name;
			data.sprite = sprite;
			data.bulletSprite = bulletSprite;

			if (data.animated)
				if (bullet->QueryFloatAttribute("frameSize", &data.bulletFrameSize) != tinyxml2::XML_SUCCESS)
					continue;
			tinyxml2::XMLElement* spread	= bullet->FirstChildElement("spread");
			tinyxml2::XMLElement* speed		= bullet->FirstChildElement("speed");
			tinyxml2::XMLElement* drop		= bullet->FirstChildElement("drop");
			tinyxml2::XMLElement* modifier	= bullet->FirstChildElement("modifier");
			if (spread)
				spread->QueryFloatText(&data.bulletSpread);
			if (speed)
				speed->QueryFloatText(&data.bulletSpeed);
			if (drop)
				drop->QueryFloatText(&data.bulletDrop);
			if (modifier)
			{
				modifier->QueryFloatAttribute("min", &data.modMin);
				modifier->QueryFloatAttribute("max", &data.modMax);
			}

			weaponData.push_back(data);
		}
	}
	return (!weaponData.empty());
}