#include "olcPGE_ResourcePack.h"

namespace olc {
	ResourcePack::ResourcePack()
	{

	}

	ResourcePack::~ResourcePack()
	{
		ClearPack();
	}

	olc::rcode ResourcePack::AddToPack(std::string sFile)
	{
		std::ifstream ifs(sFile, std::ifstream::binary);
		if (!ifs.is_open()) return olc::FAIL;

		// Get File Size
		std::streampos p = 0;
		p = ifs.tellg();
		ifs.seekg(0, std::ios::end);
		p = ifs.tellg() - p;
		ifs.seekg(0, std::ios::beg);

		// Create entry
		sEntry e;
		e.data = nullptr;
		e.nFileSize = (uint32_t)p;

		// Read file into memory
		e.data = new uint8_t[(uint32_t)e.nFileSize];
		ifs.read((char*)e.data, e.nFileSize);
		ifs.close();

		// Add To Map
		mapFiles[sFile] = e;
		return olc::OK;
	}

	olc::rcode ResourcePack::RemoveFromPack(std::string sFile)
	{	
		if (mapFiles.count(sFile) > 0) {	
			sEntry e = mapFiles.at(sFile);
			if (e.data != nullptr) {
				delete[] e.data;
			}
			mapFiles.erase(sFile);
			return olc::OK;
		} else {
			return olc::FAIL;
		}
	}

	olc::rcode ResourcePack::SavePack(std::string sFile)
	{
		std::ofstream ofs(sFile, std::ofstream::binary);
		if (!ofs.is_open()) return olc::FAIL;

		// 1) Write Map
		size_t nMapSize = mapFiles.size();
		ofs.write((char*)&nMapSize, sizeof(size_t));
		for (auto &e : mapFiles)
		{
			size_t nPathSize = e.first.size();
			ofs.write((char*)&nPathSize, sizeof(size_t));
			ofs.write(e.first.c_str(), nPathSize);
			ofs.write((char*)&e.second.nID, sizeof(uint32_t));
			ofs.write((char*)&e.second.nFileSize, sizeof(uint32_t));
			ofs.write((char*)&e.second.nFileOffset, sizeof(uint32_t));
		}

		// 2) Write Data
		std::streampos offset = ofs.tellp();
		for (auto &e : mapFiles)
		{
			e.second.nFileOffset = (uint32_t)offset;
			ofs.write((char*)e.second.data, e.second.nFileSize);
			offset += e.second.nFileSize;
		}

		// 3) Rewrite Map (it has been updated with offsets now)
		ofs.seekp(std::ios::beg);
		ofs.write((char*)&nMapSize, sizeof(size_t));
		for (auto &e : mapFiles)
		{
			size_t nPathSize = e.first.size();
			ofs.write((char*)&nPathSize, sizeof(size_t));
			ofs.write(e.first.c_str(), nPathSize);
			ofs.write((char*)&e.second.nID, sizeof(uint32_t));
			ofs.write((char*)&e.second.nFileSize, sizeof(uint32_t));
			ofs.write((char*)&e.second.nFileOffset, sizeof(uint32_t));
		}
		ofs.close();

		return olc::OK;
	}

	olc::rcode ResourcePack::LoadPack(std::string sFile)
	{
		std::ifstream ifs(sFile, std::ifstream::binary);
		if (!ifs.is_open()) return olc::FAIL;

		// 1) Read Map
		size_t nMapEntries;
		ifs.read((char*)&nMapEntries, sizeof(size_t));
		for (size_t i = 0; i < nMapEntries; i++)
		{
			size_t nFilePathSize = 0;
			ifs.read((char*)&nFilePathSize, sizeof(size_t));

			std::string sFileName(nFilePathSize, ' ');
			for (size_t j = 0; j < nFilePathSize; j++)
				sFileName[j] = ifs.get();

			sEntry e;
			e.data = nullptr;
			ifs.read((char*)&e.nID, sizeof(uint32_t));
			ifs.read((char*)&e.nFileSize, sizeof(uint32_t));
			ifs.read((char*)&e.nFileOffset, sizeof(uint32_t));
			mapFiles[sFileName] = e;
		}

		// 2) Read Data
		for (auto &e : mapFiles)
		{
			e.second.data = new uint8_t[(uint32_t)e.second.nFileSize];
			ifs.seekg(e.second.nFileOffset);
			ifs.read((char*)e.second.data, e.second.nFileSize);
			e.second._config();
		}

		ifs.close();
		return olc::OK;
	}

	olc::ResourcePack::sEntry ResourcePack::GetStreamBuffer(std::string sFile)
	{
		return mapFiles[sFile];
	}

	std::vector<std::string> ResourcePack::GetEntries() 
	{
		std::vector<std::string> result;
		result.reserve(mapFiles.size());
		for (auto& e: mapFiles) {
			result.push_back(e.first);
		}
		return result;
	}

	olc::rcode ResourcePack::ClearPack()
	{
		for (auto &e : mapFiles)
		{
			if (e.second.data != nullptr)
				delete[] e.second.data;
		}

		mapFiles.clear();
		return olc::OK;
	}
}