#ifndef _H_XMLLOADER_H
#define _H_XMLLOADER_H

#include <vector>
#include <string>
#include <memory>
#include <unordered_map>
#include <glm/glm.hpp>

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

struct Tile;

struct EntityData {
	bool isPlayer;
	bool animated;
	int x = 0, y = 0;
	std::string graphic;
	std::string weapon = "pistolGun";
	std::string script;
};

struct MapData {
	std::string name;
	struct TileSet {
		int startID = 1;
		std::string path;
	};
	std::vector<TileSet> tileSets;
	int width = 0, height = 0;
	float tileSize = 32.f;
	// 3D array, 0: layer, 1: y, 2: x
	// Holding tile startID + ids, 0 is an empty tile
	std::vector<std::vector<std::vector<int>>> tiles;
};

struct TileSetData {
	std::string name;
	std::string type;
	std::string file;
	int width = 0, height = 0;
	int columns = 0;
	int tileCount = 0;
	float tileSize = 64.f;
	struct TileData {
		int id = 0;
		std::string type;
		bool walkable = true;
		struct ObjectData {
			int id = 0;
			std::string name;
			glm::vec2 pos;
			glm::vec2 size;
			bool collidable = false;
			bool pickup = false;
		};
		std::vector<std::shared_ptr<ObjectData>> objects;
	};
	std::vector<std::shared_ptr<TileData>> tiles;
};

struct SceneData {
	std::string id;
	std::string type;
	std::string bgFile;

	std::shared_ptr<MapData> map;
	std::vector<EntityData> entities;
};

struct WeaponData {
	std::string name;
	float fireSpeed = 250.0f;
	int clipSize = 21;
	int maxAmmo = 512;
	std::string script = "";
	std::string graphic;
	std::string animSet;
	bool animated = false;
	float sizeX = 50.f, sizeY = 50.f;
	float offsetX = 0.f, offsetY = 0.f;
	float bulletSizeX = 50.f, bulletSizeY = 50.f;
	std::string bulletGraphic;
	bool bulletAnimated = false;
	float bulletSpread = 1.0f, bulletSpeed = 3.0f, bulletDrop = 500.f;
	float modMin = 0.5f, modMax = 1.0f;
};

struct AnimationData {
	std::string name;
	std::string type;
	std::string spriteAtlas;
	// Each entity will have a set of animations, 
	// this animation set is meant to keep each group
	// of animations within their set.
	// The actual set name is based on the file the animation
	// is held in.
	std::string animSet;
	bool directional = false;
	bool oneShot;
	float FPS = 1.f;
	float frameSize;
};

class XMLLoader
{
public:
	XMLLoader() {}
	bool loadScenes(const char* sceneFolder);
	bool loadWeapons(const char* weaponFolder);
	bool loadAnimations(const char* animationFolder);
	bool loadTileSets(const char* tileSetFolder);
	bool loadMaps(const char* mapFolder);

	const std::shared_ptr<SceneData> getSceneData(const std::string& id) const { 
		try {
			return scenes.at(id);
		}
		catch (std::exception&) {
			return nullptr;
		}
	}

	const std::shared_ptr<MapData> getMapData(const std::string& name) const {
		try {
			return maps.at(name);
		}
		catch (std::exception&) {
			return nullptr;
		}
	}

	const std::shared_ptr<WeaponData> getWeaponData(const std::string& name) const {
		try {
			return weapons.at(name);
		}
		catch (std::exception&) {
			return nullptr;
		}
	}

	const std::shared_ptr<AnimationData> getAnimationData(const std::string& name) const {
		try {
			return animations.at(name);
		}
		catch (std::exception&) {
			return nullptr;
		}
	}

	const std::shared_ptr<TileSetData> getTileSetData(const std::string& name) const {
		try {
			return tileSets.at(name);
		}
		catch (std::exception&) {
			return nullptr;
		}
	}

	// return a full set of animations, may need further optimization.
	// one idea is when loading animations we create a seperate map that holds each set by their reference, so we can just do a simple,
	// hash table lookup.
	std::vector<std::shared_ptr<AnimationData>> getAnimationSet(const std::string& set) const {
		std::vector<std::shared_ptr<AnimationData>> animSet;
		animSet.reserve(animations.size());
		for (const auto& [name, anim] : animations) {
			if (anim->animSet == set) animSet.push_back(anim);
		}
		animSet.shrink_to_fit();
		return animSet;
	}

	void clearData() { scenes.clear(); weapons.clear(); animations.clear(); maps.clear(); tileSets.clear(); }
protected:
	bool loadXmlScene(const char* xmlFile, SceneData* out);
	bool loadEntityData(const char* xmlFile, SceneData* out);
	bool loadTile(tinyxml2::XMLElement* tileElement, TileSetData::TileData* out);
	bool loadObject(tinyxml2::XMLElement* objectElement, TileSetData::TileData::ObjectData* out);
private:
	std::unordered_map<std::string, std::shared_ptr<SceneData>> scenes;
	std::unordered_map<std::string, std::shared_ptr<WeaponData>> weapons;
	std::unordered_map<std::string, std::shared_ptr<AnimationData>> animations;
	std::unordered_map<std::string, std::shared_ptr<MapData>> maps;
	std::unordered_map<std::string, std::shared_ptr<TileSetData>> tileSets;
};

#endif // _H_XMLLOADER_H