#pragma once

#include "yaml.h"

#include "StrFormat.h"

#define SS_YAML_KEY_FILEHDR "File_hdr"
#define SS_YAML_KEY_TAG "Tag"
#define SS_YAML_KEY_VERSION "Version"
#define SS_YAML_KEY_UNIT "Unit"
#define SS_YAML_KEY_TYPE "Type"
#define SS_YAML_KEY_CARD "Card"
#define SS_YAML_KEY_STATE "State"
#define SS_YAML_KEY_DEVICE "Device"

#define SS_YAML_VALUE_AWSS "AppleWin Save State"

struct MapValue;
typedef std::map<std::string, MapValue> MapYaml;

struct MapValue
{
	std::string value;
	MapYaml* subMap;
};

class YamlHelper
{
friend class YamlLoadHelper;	// YamlLoadHelper can access YamlHelper's private members

public:
	YamlHelper(void) :
		m_hFile(NULL)
	{
		memset(&m_parser, 0, sizeof(m_parser));
		memset(&m_newEvent, 0, sizeof(m_newEvent));
		MakeAsciiToHexTable();
	}

	~YamlHelper(void)
	{
		FinaliseParser();
	}

	int InitParser(const char* pPathname);
	void FinaliseParser(void);

	UINT ParseFileHdr(const char* tag);

	int GetScalar(std::string& scalar);
	void GetMapStartEvent(void);

private:
	void GetNextEvent(void);
	int ParseMap(MapYaml& mapYaml);
	std::string GetMapValue(MapYaml& mapYaml, const std::string &key, bool& bFound);
	UINT LoadMemory(MapYaml& mapYaml, const LPBYTE pMemBase, const size_t kAddrSpaceSize, const UINT offset);
	bool GetSubMap(MapYaml** mapYaml, const std::string &key, const bool canBeNull=false);
	void GetMapRemainder(std::string& mapName, MapYaml& mapYaml);

	void MakeAsciiToHexTable(void);

	yaml_parser_t m_parser;
	yaml_event_t m_newEvent;

	std::string m_scalarName;

	FILE* m_hFile;
	char m_AsciiToHex[256];

	MapYaml m_mapYaml;
};

// -----

class YamlLoadHelper
{
public:
	YamlLoadHelper(YamlHelper& yamlHelper)
		: m_yamlHelper(yamlHelper),
		  m_pMapYaml(&yamlHelper.m_mapYaml),
		  m_bDoGetMapRemainder(true),
		  m_topLevelMapName(yamlHelper.m_scalarName),
		  m_currentMapName(m_topLevelMapName),
		  m_bIteratingOverMap(false)
	{
		if (!m_yamlHelper.ParseMap(yamlHelper.m_mapYaml))
		{
			m_bDoGetMapRemainder = false;
			throw std::runtime_error(m_currentMapName + ": Failed to parse map");
		}
	}

	~YamlLoadHelper(void)
	{
		if (m_bDoGetMapRemainder)
			m_yamlHelper.GetMapRemainder(m_topLevelMapName, m_yamlHelper.m_mapYaml);
	}

	INT LoadInt(const std::string key);
	UINT LoadUint(const std::string key);
	UINT64 LoadUint64(const std::string key);
	bool LoadBool(const std::string key);
	std::string LoadString_NoThrow(const std::string& key, bool& bFound);
	std::string LoadString(const std::string& key);
	float LoadFloat(const std::string & key);
	double LoadDouble(const std::string & key);
	void LoadMemory(const LPBYTE pMemBase, const size_t size, const UINT offset=0);
	void LoadMemory(std::vector<BYTE>& memory, const size_t size, const UINT offset=0);

	bool GetSubMap(const std::string & key, const bool canBeNull=false)
	{
		YamlStackItem item = {m_pMapYaml, m_currentMapName};
		m_stackMap.push(item);
		bool res = m_yamlHelper.GetSubMap(&m_pMapYaml, key, canBeNull);
		if (!res)
			m_stackMap.pop();
		else
			m_currentMapName = key;
		return res;
	}

	void PopMap(void)
	{
		if (m_stackMap.empty())
			return;

		YamlStackItem item = m_stackMap.top();
		m_stackMap.pop();

		m_pMapYaml = item.pMapYaml;
		m_currentMapName = item.mapName;
	}

	std::string GetMapNextSlotNumber(void)
	{
		if (!m_bIteratingOverMap)
		{
			m_iter = m_pMapYaml->begin();
			m_bIteratingOverMap = true;
		}

		if (m_iter == m_pMapYaml->end())
		{
			m_bIteratingOverMap = false;
			return "";
		}

		std::string scalar = m_iter->first;
		++m_iter;
		return scalar;
	}

private:
	YamlHelper& m_yamlHelper;
	MapYaml* m_pMapYaml;
	bool m_bDoGetMapRemainder;

	struct YamlStackItem
	{
		MapYaml* pMapYaml;
		std::string mapName;
	};
	std::stack<YamlStackItem> m_stackMap;

	std::string m_topLevelMapName;
	std::string m_currentMapName;

	bool m_bIteratingOverMap;
	MapYaml::iterator m_iter;
};

// -----

class YamlSaveHelper
{
public:
	YamlSaveHelper(const std::string & pathname) :
		m_hFile(NULL),
		m_indent(0),
		m_pWcStr(NULL),
		m_wcStrSize(0),
		m_pMbStr(NULL),
		m_mbStrSize(0)
	{
		m_hFile = fopen(pathname.c_str(), "wt");

		// todo: handle ERROR_ALREADY_EXISTS - ask if user wants to replace existing file
		// - at this point any old file will have been truncated to zero

		if(m_hFile == NULL)
			throw std::runtime_error("Save error");

		_tzset();
		time_t ltime;
		time(&ltime);
		char timebuf[26];
		errno_t err = ctime_s(timebuf, sizeof(timebuf), &ltime);	// includes newline at end of string
		fprintf(m_hFile, "# Date-stamp: %s\n", err == 0 ? timebuf : "Error: Datestamp\n\n");

		fprintf(m_hFile, "---\n");

		//

		memset(m_szIndent, ' ', kMaxIndent);
	}

	~YamlSaveHelper()
	{
		if (m_hFile)
		{
			fprintf(m_hFile, "...\n");
			fclose(m_hFile);
		}

		delete[] m_pWcStr;
		delete[] m_pMbStr;
	}

	void Save(const char* format, ...) ATTRIBUTE_FORMAT_PRINTF(2, 3); // 1 is "this"

	void SaveInt(const char* key, int value);
	void SaveUint(const char* key, UINT value);
	void SaveHexUint4(const char* key, UINT value);
	void SaveHexUint8(const char* key, UINT value);
	void SaveHexUint12(const char* key, UINT value);
	void SaveHexUint16(const char* key, UINT value);
	void SaveHexUint24(const char* key, UINT value);
	void SaveHexUint32(const char* key, UINT value);
	void SaveHexUint64(const char* key, UINT64 value);
	void SaveBool(const char* key, bool value);
	void SaveString(const char* key, const char* value);
	void SaveString(const char* key, const std::string & value);
	void SaveFloat(const char* key, float value);
	void SaveDouble(const char* key, double value);
	void SaveMemory(const LPBYTE pMemBase, const UINT uMemSize, const UINT offset=0);

	class Label
	{
	public:
		Label(YamlSaveHelper& rYamlSaveHelper, const char* format, ...)  ATTRIBUTE_FORMAT_PRINTF(3, 4) :  // 1 is "this"
			yamlSaveHelper(rYamlSaveHelper)
		{
			fwrite(yamlSaveHelper.m_szIndent, 1, yamlSaveHelper.m_indent, yamlSaveHelper.m_hFile);

			va_list vl;
			va_start(vl, format);
			vfprintf(yamlSaveHelper.m_hFile, format, vl);
			va_end(vl);

			yamlSaveHelper.m_indent += 2;
			_ASSERT(yamlSaveHelper.m_indent < yamlSaveHelper.kMaxIndent);
		}

		~Label(void)
		{
			yamlSaveHelper.m_indent -= 2;
			_ASSERT(yamlSaveHelper.m_indent >= 0);
		}

		YamlSaveHelper& yamlSaveHelper;
	};

	class Slot : public Label
	{
	public:
		Slot(YamlSaveHelper& rYamlSaveHelper, const std::string & type, UINT slot, UINT version) :
			Label(rYamlSaveHelper, "%d:\n", slot)
		{
			rYamlSaveHelper.Save("%s: %s\n", SS_YAML_KEY_CARD, type.c_str());
			rYamlSaveHelper.Save("%s: %d\n", SS_YAML_KEY_VERSION, version);
		}

		~Slot(void) {}
	};

	void FileHdr(UINT version);
	void UnitHdr(const std::string & type, UINT version);

private:
	FILE* m_hFile;

	int m_indent;
	static const UINT kMaxIndent = 50*2;
	char m_szIndent[kMaxIndent];

	LPWSTR m_pWcStr;
	int m_wcStrSize;
	LPSTR m_pMbStr;
	int m_mbStrSize;
};