#pragma once
#include "Deer/DataManagment.h"
#include "Deer/Log.h"
#include "Deer/Tools/Memory.h"
#include "Deer/Tools/Path.h"
#include "Deer/Tools/TypeDefs.h"

#include <cxxabi.h>
#include <unordered_map>
#include <vector>

namespace Deer {
	template <typename T>
	class ResourceManager;

	template <typename T>
	class Resource {
	  public:
		int32_t getResourceId() const { return resourceId; }

		bool isValid() const { return ResourceManager<T>::isValid(*this); }
		T& getData() { return ResourceManager<T>::getResourceData(*this); }

		const std::string& getStorageId() const { return ResourceManager<T>::getStorageId(*this); }

		inline explicit operator bool() const { return resourceId >= 0; }

		static Resource<T> unsafeFromId(int32_t id) {
			Resource<T> res;
			res.resourceId = id;
			return res;
		}

	  private:
		// -1 = no resource loaded
		int32_t resourceId = -1;
		friend ResourceManager<T>;
	};

	template <typename T>
	class ResourceBuilder {
	  public:
		using BaseDataType = char;
		static Scope<T> buildResource(const BaseDataType& baseData) {
			static_assert(sizeof(T) == 0, "ResourceBuilder must be specialized for this type T");
			return nullptr;
		}
	};

	template <typename T>
	class ResourceManager {
	  private:
		struct ResourceData {
		  public:
			ResourceData(const std::string& _resourceId, Scope<T>&& _data)
			    : storageId(_resourceId), data(std::move(_data)) {}

			Scope<T> data;
			const std::string storageId;
		};

		static std::vector<ResourceData> resources;
		static std::unordered_map<std::string, Resource<T>> resourceCache;

	  public:
		template <typename DataSource>
		static Resource<T> loadResource(const std::string& storageId) {
			if (resourceCache.contains(storageId))
				return resourceCache[storageId];

			using ResourceBuilderBaseDataType = typename ResourceBuilder<T>::BaseDataType;
			Scope<T> data;

			if constexpr (!std::is_void_v<ResourceBuilderBaseDataType>) {
				Scope<ResourceBuilderBaseDataType> baseData = DataManager<DataSource>::template load<ResourceBuilderBaseDataType>(storageId);
				if (!baseData) {
					const char* baseDataType = abi::__cxa_demangle(typeid(ResourceBuilderBaseDataType).name(), 0, 0, nullptr);
					const char* dataType = abi::__cxa_demangle(typeid(T).name(), 0, 0, nullptr);
					DEER_CORE_ERROR("Error loading base resource {} for resource {} with id {}", baseDataType, dataType, storageId.c_str());
					return Resource<T>();
				}
				data = ResourceBuilder<T>::buildResource(*baseData.get());
			} else {
				data = ResourceBuilder<T>::buildResource(); // No base data
			}

			Resource<T> resource = Resource<T>::unsafeFromId(resources.size());
			resources.push_back({storageId, std::move(data)});
			resourceCache[storageId] = resource;

			return resource;
		}

		static Resource<T> loadResourceFromData(const typename ResourceBuilder<T>::BaseDataType& resourceData, const std::string& storageId) {
			if (resourceCache.contains(storageId))
				return resourceCache[storageId];
				
			Scope<T> data = ResourceBuilder<T>::buildResource(resourceData);

			Resource<T> resource = Resource<T>::unsafeFromId(resources.size());
			resources.push_back({storageId, std::move(data)});
			resourceCache[storageId] = resource;

			return resource;
		}

		static void unloadResources() {
			resourceCache.clear();
			resources.clear();
		}

		static inline bool isValid(Resource<T> res) {
			return res.resourceId >= 0 && res.resourceId < static_cast<int32_t>(resources.size());
		}

		static inline const std::string& getStorageId(Resource<T> res) {
			const static std::string invalid("NULL");
			if (!isValid(res))
				return invalid;

			return resources[res.resourceId].storageId;
		}

		static T& getResourceData(Resource<T> res) {
			return *resources[res.resourceId].data;
		}
	};

	template <typename T>
	std::vector<typename ResourceManager<T>::ResourceData> ResourceManager<T>::resources;
	template <typename T>
	std::unordered_map<std::string, Resource<T>> ResourceManager<T>::resourceCache;
} // namespace Deer
