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

#include <string>
#include <unordered_map>
#include <vector>

namespace Deer {
	/*
	class DataSource {
	  public:
	    using DataImporter = [ClassOfTheDataImporter]
	};
	*/
	using StorageMetadata = std::unordered_map<std::string, std::string>;
	template <typename T>
	concept HasMetadata = requires(const std::string& location) {
		{ T::loadMetadata(location) } -> std::same_as<StorageMetadata>;
		{ T::saveMetadata(StorageMetadata{}, location) };
	};

	class StorageData {
	  public:
		StorageData() = default;
		StorageData(uint32_t dataSize) : size(dataSize), data(MakeScope<uint8_t[]>(dataSize)) {}

		inline uint8_t* getData() { return data.get(); }
		inline const uint8_t* getData() const { return data.get(); }
		inline uint32_t getSize() const { return size; }
		inline StorageMetadata& getMetadata() { return metadata; }

		template <typename DataImporter, typename T>
		Scope<T> deserialize();

		template <typename DataImporter, typename T>
		static StorageData serialize(const T&);

		inline explicit operator bool() const { return size != 0; }

	  private:
		StorageMetadata metadata;
		Scope<uint8_t[]> data = nullptr;
		uint32_t size = 0;
	};

	template <typename DataSource>
	class StorageBackend {
	  public:
		static StorageData loadData(const std::string& location);
		static void saveData(const std::string& location, const StorageData& data);

		static StorageMetadata loadMetadata(const std::string& location);
		static void saveMetadata(const StorageMetadata& metadata, const std::string& location);

		static std::vector<std::string> indexResources(const std::string& location);
	};

	template <typename DataSource>
	class DataManager {
	  public:
		template <typename T>
		static Scope<T> load(const std::string& dataId) {
			StorageData data = StorageBackend<DataSource>::loadData(dataId);

			if constexpr (HasMetadata<StorageBackend<DataSource>>) {
				data.getMetadata() = StorageBackend<DataSource>::loadMetadata(dataId);
				data.getMetadata()["dataId"] = dataId;
			}

			return data.deserialize<typename DataSource::DataImporter, T>();
		}

		template <typename T>
		static void store(const std::string& dataId, T& value) {
			StorageData data = StorageData::serialize<typename DataSource::DataImporter, T>(value);
			StorageBackend<DataSource>::saveData(dataId, data);
			if constexpr (HasMetadata<StorageBackend<DataSource>>) {
				StorageBackend<DataSource>::saveMetadata(data.getMetadata(), dataId);
			}
		}
	};
} // namespace Deer