#pragma once

#include "math_types.h"
#include <iostream>
#include <fstream>
#include <unordered_map>
#include <cstring>
#include <sys/stat.h>

struct WriteSerializer {
	enum { IsReading = 0 };
	enum { IsWriting = 1 };

	std::ofstream stream;

	bool SerializeData (const std::string &key, const char *data, size_t size) {
		size_t key_length = key.size();
		stream.write (reinterpret_cast<char*>(&key_length), sizeof (size_t));
		stream << key;
		stream.write (reinterpret_cast<char*>(&size), sizeof(size_t));
		stream.write (data, size);

		return true;
	}

	void Open(const char* filename) {
		stream.open(filename, std::ofstream::trunc);
	}

	void Close() {
		stream.close();
	}
};

struct ReadSerializer {
	enum { IsReading = 1 };
	enum { IsWriting = 0 };

	struct Block {
		size_t size;
		void *pdata;

		Block() : size(0), pdata(nullptr) {}
		Block(const Block& other) :
			size(other.size),
			pdata(other.pdata)
		{}
		Block& operator=(const Block& other) {
			if (this != &other) {
				size = other.size;
				pdata = other.pdata;
			}
			return *this;
		}
	};

	bool SerializeData (const std::string &key, char *data, size_t size) {
		std::unordered_map<std::string, Block>::iterator iter;
		iter = blocks.find(key);
		if (iter == blocks.end()) {
			return false;
		}

//		std::cout << "found block for " << key << ", size: " << iter->second.size << std::endl;
		assert (size == iter->second.size);
//		std::cout << "copying from " << iter->second.pdata << " to " << (void*) data << std::endl; 
//		std::cout << "copying from " << blocks[key].pdata << " to " << (void*) data << std::endl; 
		assert (blocks[key].size == size);
		memcpy (data, blocks[key].pdata, size);
		return true;
	}

	void Open(const char* filename) {
		// early out if file does not (yet) exist
		struct stat fstat;
		if (stat(filename, &fstat) == -1) {
			return;
		}

		std::ifstream stream(filename, std::ios::binary);

		size_t key_size;
		size_t block_size;

		while (!stream.eof() && stream.peek() != std::ifstream::traits_type::eof()) {
			// read key size
			stream.read(reinterpret_cast<char*>(&key_size), sizeof(size_t));
//			std::cout << "read key size " << key_size << std::endl;
			assert (key_size < 1000);
			// read key
			std::string key (key_size, 0);
			stream.read(&key[0], key_size);

			// create block
			blocks[key] = Block();
			Block *block = &blocks[key];

			// read block size
			stream.read(reinterpret_cast<char*>(&block->size), sizeof(size_t));
//			std::cout << "read block " << key << ", size = " << block->size << std::endl;
			block->pdata = new char[block->size + 1];
//			std::cout << "block addr = " << block->pdata << std::endl;
			stream.read(reinterpret_cast<char*>(block->pdata), block->size);
		}

		stream.close();
}

	void Close() {
	}

	std::unordered_map<std::string, Block> blocks;
};

template <typename Serializer>
bool SerializeBool (Serializer &serializer, const std::string &key, bool& value) {
	return serializer.SerializeData(key, reinterpret_cast<char*>(&value), sizeof(bool));
}

template <typename Serializer>
bool SerializeInt (Serializer &serializer, const std::string &key, int& value) {
	return serializer.SerializeData(key, reinterpret_cast<char*>(&value), sizeof(int));
}

template <typename Serializer>
bool SerializeFloat (Serializer &serializer, const std::string &key, float& value) {
	return serializer.SerializeData(key, reinterpret_cast<char*>(&value), sizeof(float));
}


template <typename Serializer>
bool SerializedUint16 (Serializer &serializer, const std::string &key, uint16_t& value) {
	return serializer.SerializeData(key, reinterpret_cast<char*>(&value), sizeof(uint16_t));
}

template <typename Serializer>
bool SerializeVec3 (Serializer &serializer, const std::string &key, Vector3f& value) {
	return serializer.SerializeData(key, reinterpret_cast<char*>(&value), sizeof(Vector3f));
}

template <typename Serializer>
bool SerializeVec4 (Serializer &serializer, const std::string &key, Vector4f& value) {
	return serializer.SerializeData(key, reinterpret_cast<char*>(&value), sizeof(Vector4f));
}
