/*
AppleWin : An Apple //e emulator for Windows

Copyright (C) 1994-1996, Michael O'Brien
Copyright (C) 1999-2001, Oliver Schmidt
Copyright (C) 2002-2005, Tom Charlesworth
Copyright (C) 2006-2015, Tom Charlesworth, Michael Pohoreski

AppleWin is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

AppleWin is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with AppleWin; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "StdAfx.h"

#include "YamlHelper.h"
#include "Log.h"

#include <sstream>

int YamlHelper::InitParser(const char* pPathname)
{
	m_hFile = fopen(pPathname, "r");
	if (m_hFile == NULL)
	{
		return 0;
	}

	if (!yaml_parser_initialize(&m_parser))
	{
		return 0;
	}

	// Note: C/C++ > Pre-Processor: YAML_DECLARE_STATIC;
	yaml_parser_set_input_file(&m_parser, m_hFile);

	return 1;
}

void YamlHelper::FinaliseParser(void)
{
	if (m_hFile)
		fclose(m_hFile);

	m_hFile = NULL;

	yaml_event_delete(&m_newEvent);
	yaml_parser_delete(&m_parser);
}

UINT YamlHelper::ParseFileHdr(const char* tag)
{
	std::string scalar;
	if (!GetScalar(scalar))
		throw std::runtime_error(SS_YAML_KEY_FILEHDR ": Failed to find scalar");

	if (scalar != SS_YAML_KEY_FILEHDR)
		throw std::runtime_error("Failed to find file header");

	GetMapStartEvent();

	YamlLoadHelper yamlLoadHelper(*this);

	//

	std::string value = yamlLoadHelper.LoadString(SS_YAML_KEY_TAG);
	if (value != tag)
	{
		//printf("%s: Bad tag (%s) - expected %s\n", SS_YAML_KEY_FILEHDR, value.c_str(), tag);
		throw std::runtime_error(SS_YAML_KEY_FILEHDR ": Bad tag");
	}

	return yamlLoadHelper.LoadUint(SS_YAML_KEY_VERSION);
}

void YamlHelper::GetNextEvent(void)
{
	yaml_event_delete(&m_newEvent);
	if (!yaml_parser_parse(&m_parser, &m_newEvent))
	{
		std::ostringstream error("Save-state parser error: ");
		if (m_parser.problem != NULL)
			error << m_parser.problem;
		else
			error << "unknown";
		error << " @ " << m_parser.problem_mark.line << ":" << m_parser.problem_mark.column;

		throw std::runtime_error(error.str());
	}
}

int YamlHelper::GetScalar(std::string& scalar)
{
	int res = 1;
	bool bDone = false;

	while (!bDone)
	{
		GetNextEvent();

		switch(m_newEvent.type)
		{
		case YAML_SCALAR_EVENT:
			scalar = m_scalarName = (const char*)m_newEvent.data.scalar.value;
			res = 1;
			bDone = true;
			break;
		case YAML_SEQUENCE_END_EVENT:
			res = 0;
			bDone = true;
			break;
		case YAML_MAPPING_END_EVENT:
			res = 0;
			bDone = true;
			break;
		case YAML_STREAM_END_EVENT:
			res = 0;
			bDone = true;
			break;
		}
	}

	return res;
}

void YamlHelper::GetMapStartEvent(void)
{
	GetNextEvent();

	if (m_newEvent.type != YAML_MAPPING_START_EVENT)
	{
		//printf("Unexpected yaml event (%d)\n", m_newEvent.type);
		throw std::runtime_error("Unexpected yaml event");
	}
}

int YamlHelper::ParseMap(MapYaml& mapYaml)
{
	mapYaml.clear();

	const char*& pValue = (const char*&) m_newEvent.data.scalar.value;

	bool bKey = true;
	std::string pKey;
	int res = 1;
	bool bDone = false;

	while (!bDone)
	{
		GetNextEvent();

		switch(m_newEvent.type)
		{
		case YAML_STREAM_END_EVENT:
			res = 0;
			bDone = true;
			break;
		case YAML_MAPPING_START_EVENT:
			{
				MapValue mapValue;
				mapValue.value = "";
				mapValue.subMap = new MapYaml;
				mapYaml[pKey] = mapValue;
				res = ParseMap(*mapValue.subMap);
				if (!res)
					throw std::runtime_error("ParseMap: premature end of file during map parsing");
				bKey = true;	// possibly more key,value pairs in this map
			}
			break;
		case YAML_MAPPING_END_EVENT:
			bDone = true;
			break;
		case YAML_SCALAR_EVENT:
			if (bKey)
			{
				_ASSERT(pValue);  // std::string(NULL) generates AccessViolation
				pKey = pValue;
			}
			else
			{
				MapValue mapValue;
				mapValue.value = pValue;
				mapValue.subMap = NULL;
				mapYaml[pKey] = mapValue;
				pKey.clear();
			}

			bKey = bKey ? false : true;
			break;
		case YAML_SEQUENCE_START_EVENT:
		case YAML_SEQUENCE_END_EVENT:
			throw std::runtime_error("ParseMap: Sequence event unsupported");
		}
	}

	return res;
}

std::string YamlHelper::GetMapValue(MapYaml& mapYaml, const std::string& key, bool& bFound)
{
	MapYaml::const_iterator iter = mapYaml.find(key);
	if (iter == mapYaml.end() || iter->second.subMap != NULL)
	{
		bFound = false;	// not found
		return "";
	}

	std::string value = iter->second.value;

	mapYaml.erase(iter);

	bFound = true;
	return value;
}

bool YamlHelper::GetSubMap(MapYaml** mapYaml, const std::string& key, const bool canBeNull/*=false*/)
{
	MapYaml::const_iterator iter = (*mapYaml)->find(key);
	if (iter == (*mapYaml)->end() || (!canBeNull && iter->second.subMap == NULL))
	{
		return false;	// not found
	}

	*mapYaml = iter->second.subMap;
	return true;
}

void YamlHelper::GetMapRemainder(std::string& mapName, MapYaml& mapYaml)
{
	for (MapYaml::iterator iter = mapYaml.begin(); iter != mapYaml.end(); ++iter)
	{
		if (iter->second.subMap)
		{
			std::string subMapName(iter->first);
			GetMapRemainder(subMapName, *iter->second.subMap);
			delete iter->second.subMap;
		}
		else
		{
			const char* pKey = iter->first.c_str();
			LogOutput("%s: Unknown key (%s)\n", mapName.c_str(), pKey);
			LogFileOutput("%s: Unknown key (%s)\n", mapName.c_str(), pKey);
		}
	}

	mapYaml.clear();
}

//

void YamlHelper::MakeAsciiToHexTable(void)
{
	memset(m_AsciiToHex, -1, sizeof(m_AsciiToHex));

	for (int i = '0'; i<= '9'; i++)
		m_AsciiToHex[i] = i - '0';

	for (int i = 'A'; i<= 'F'; i++)
		m_AsciiToHex[i] = i - 'A' + 0xA;

	for (int i = 'a'; i<= 'f'; i++)
		m_AsciiToHex[i] = i - 'a' + 0xA;
}

UINT YamlHelper::LoadMemory(MapYaml& mapYaml, const LPBYTE pMemBase, const size_t kAddrSpaceSize, const UINT offset)
{
	UINT bytes = 0;

	for (MapYaml::iterator it = mapYaml.begin(); it != mapYaml.end(); ++it)
	{
		const char* pKey = it->first.c_str();
		UINT addr = strtoul(pKey, NULL, 16);
		if (addr >= (kAddrSpaceSize + offset))
			throw std::runtime_error("Memory: line address too big: " + it->first);

		LPBYTE pDst = (LPBYTE) (pMemBase + addr);
		const LPBYTE pDstEnd = (LPBYTE) (pMemBase + kAddrSpaceSize + offset);

		if (it->second.subMap)
			throw std::runtime_error("Memory: unexpected sub-map");

		const char* pValue = it->second.value.c_str();
		size_t len = strlen(pValue);
		if (len & 1)
			throw std::runtime_error("Memory: hex data must be an even number of nibbles on line address: " + it->first);

		for (UINT i = 0; i<len; i+=2)
		{
			if (pDst >= pDstEnd)
				throw std::runtime_error("Memory: hex data overflowed address space on line address: " + it->first);

			BYTE ah = m_AsciiToHex[ (BYTE)(*pValue++) ];
			BYTE al = m_AsciiToHex[ (BYTE)(*pValue++) ];
			if ((ah | al) & 0x80)
				throw std::runtime_error("Memory: hex data contains illegal character on line address: " + it->first);

			*pDst++ = (ah<<4) | al;
			bytes++;
		}
	}

	mapYaml.clear();

	return bytes;
}

//-------------------------------------

INT YamlLoadHelper::LoadInt(const std::string key)
{
	bool bFound;
	std::string value = m_yamlHelper.GetMapValue(*m_pMapYaml, key, bFound);
	if (value == "")
	{
		m_bDoGetMapRemainder = false;
		throw std::runtime_error(m_currentMapName + ": Missing: " + key);
	}
	return strtol(value.c_str(), NULL, 0);
}

UINT YamlLoadHelper::LoadUint(const std::string key)
{
	bool bFound;
	std::string value = m_yamlHelper.GetMapValue(*m_pMapYaml, key, bFound);
	if (value == "")
	{
		m_bDoGetMapRemainder = false;
		throw std::runtime_error(m_currentMapName + ": Missing: " + key);
	}
	return strtoul(value.c_str(), NULL, 0);
}

UINT64 YamlLoadHelper::LoadUint64(const std::string key)
{
	bool bFound;
	std::string value = m_yamlHelper.GetMapValue(*m_pMapYaml, key, bFound);
	if (value == "")
	{
		m_bDoGetMapRemainder = false;
		throw std::runtime_error(m_currentMapName + ": Missing: " + key);
	}
	return _strtoui64(value.c_str(), NULL, 0);
}

bool YamlLoadHelper::LoadBool(const std::string key)
{
	bool bFound;
	std::string value = m_yamlHelper.GetMapValue(*m_pMapYaml, key, bFound);
	if (value == "true")
		return true;
	else if (value == "false")
		return false;
	m_bDoGetMapRemainder = false;
	throw std::runtime_error(m_currentMapName + ": Missing: " + key);
}

std::string YamlLoadHelper::LoadString_NoThrow(const std::string& key, bool& bFound)
{
	std::string value = m_yamlHelper.GetMapValue(*m_pMapYaml, key, bFound);
	return value;
}

std::string YamlLoadHelper::LoadString(const std::string& key)
{
	bool bFound;
	std::string value = LoadString_NoThrow(key, bFound);
	if (!bFound)
	{
		m_bDoGetMapRemainder = false;
		throw std::runtime_error(m_currentMapName + ": Missing: " + key);
	}
	return value;
}

float YamlLoadHelper::LoadFloat(const std::string& key)
{
	bool bFound;
	std::string value = m_yamlHelper.GetMapValue(*m_pMapYaml, key, bFound);
	if (value == "")
	{
		m_bDoGetMapRemainder = false;
		throw std::runtime_error(m_currentMapName + ": Missing: " + key);
	}
#if (_MSC_VER >= 1900)
	return strtof(value.c_str(), NULL);			// MSVC++ 14.0  _MSC_VER == 1900 (Visual Studio 2015 version 14.0)
#else
	return (float) strtod(value.c_str(), NULL);	// NB. strtof() requires VS2015
#endif
}

double YamlLoadHelper::LoadDouble(const std::string& key)
{
	bool bFound;
	std::string value = m_yamlHelper.GetMapValue(*m_pMapYaml, key, bFound);
	if (value == "")
	{
		m_bDoGetMapRemainder = false;
		throw std::runtime_error(m_currentMapName + ": Missing: " + key);
	}
	return strtod(value.c_str(), NULL);
}

void YamlLoadHelper::LoadMemory(const LPBYTE pMemBase, const size_t size, const UINT offset/*=0*/)
{
	m_yamlHelper.LoadMemory(*m_pMapYaml, pMemBase, size, offset);
}

void YamlLoadHelper::LoadMemory(std::vector<BYTE>& memory, const size_t size, const UINT offset/*=0*/)
{
	memory.reserve(size);	// expand (but don't shrink) vector's capacity (NB. vector's size doesn't change)
	const UINT bytes = m_yamlHelper.LoadMemory(*m_pMapYaml, &memory[0], size, offset);
	memory.resize(bytes);	// resize so that vector contains /bytes/ elements - so that size() gives correct value.
}

//-------------------------------------

void YamlSaveHelper::Save(const char* format, ...)
{
	fwrite(m_szIndent, 1, m_indent, m_hFile);

	va_list vl;
	va_start(vl, format);
	vfprintf(m_hFile, format, vl);
	va_end(vl);
}

void YamlSaveHelper::SaveInt(const char* key, int value)
{
	Save("%s: %d\n", key, value);
}

void YamlSaveHelper::SaveUint(const char* key, UINT value)
{
	Save("%s: %u\n", key, value);
}

void YamlSaveHelper::SaveHexUint4(const char* key, UINT value)
{
	Save("%s: 0x%01X\n", key, value & 0xf);
}

void YamlSaveHelper::SaveHexUint8(const char* key, UINT value)
{
	Save("%s: 0x%02X\n", key, value);
}

void YamlSaveHelper::SaveHexUint12(const char* key, UINT value)
{
	Save("%s: 0x%03X\n", key, value);
}

void YamlSaveHelper::SaveHexUint16(const char* key, UINT value)
{
	Save("%s: 0x%04X\n", key, value);
}

void YamlSaveHelper::SaveHexUint24(const char* key, UINT value)
{
	Save("%s: 0x%06X\n", key, value);
}

void YamlSaveHelper::SaveHexUint32(const char* key, UINT value)
{
	Save("%s: 0x%08X\n", key, value);
}

void YamlSaveHelper::SaveHexUint64(const char* key, UINT64 value)
{
	Save("%s: 0x%016llX\n", key, value);
}

void YamlSaveHelper::SaveBool(const char* key, bool value)
{
	Save("%s: %s\n", key, value ? "true" : "false");
}

void YamlSaveHelper::SaveString(const char* key,  const char* value)
{
	if (value[0] == 0)
		value = "\"\"";

	// libyaml supports UTF-8 and not accented ANSI characters (GH#838)
	// . So convert ANSI to UTF-8, which is a 2-step process:

	// 1) ANSI -> unicode
	{
		int size = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, value, -1, NULL, 0);
		if (size == 0)
			throw std::runtime_error("Unable to convert to unicode: " + std::string(value));
		if (size > m_wcStrSize)
		{
			delete[] m_pWcStr;
			m_pWcStr = new WCHAR[size];
			m_wcStrSize = size;
		}
		int res = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, value, -1, m_pWcStr, m_wcStrSize);
		if (!res)
			throw std::runtime_error("Unable to convert to unicode: " + std::string(value));
	}

	// 2) unicode -> UTF-8
	{
		// NB. WC_ERR_INVALID_CHARS only defined when WIN_VER >= 0x600 - but stdafx.h defines it as 0x500
		int size = WideCharToMultiByte(CP_UTF8, 0/*WC_ERR_INVALID_CHARS*/, m_pWcStr, -1, NULL, 0, NULL, NULL);
		if (size == 0)
			throw std::runtime_error("Unable to convert to UTF-8: " + std::string(value));
		if (size > m_mbStrSize)
		{
			delete[] m_pMbStr;
			m_pMbStr = new char[size];
			m_mbStrSize = size;
		}
		int res = WideCharToMultiByte(CP_UTF8, 0/*WC_ERR_INVALID_CHARS*/, m_pWcStr, -1, m_pMbStr, m_mbStrSize, NULL, NULL);
		if (!res)
			throw std::runtime_error("Unable to convert to UTF-8: " + std::string(value));
	}

	if (std::string(m_pMbStr).find("#") == std::string::npos)
		Save("%s: %s\n", key, m_pMbStr);
	else
		Save("%s: \"%s\"\n", key, m_pMbStr);	// Wrap the string in quotes when it contains the comment character "#" (GH#1066)
}

void YamlSaveHelper::SaveString(const char* key, const std::string & value)
{
	SaveString(key, value.c_str());
}

void YamlSaveHelper::SaveFloat(const char* key, float value)
{
	Save("%s: %f\n", key, value);
}

void YamlSaveHelper::SaveDouble(const char* key, double value)
{
	Save("%s: %f\n", key, value);
}

// Pre: uMemSize must be multiple of 8
void YamlSaveHelper::SaveMemory(const LPBYTE pMemBase, const UINT uMemSize, const UINT offset/*=0*/)
{
	if (uMemSize & 7)
		throw std::runtime_error("Memory: size must be multiple of 8");

	const UINT kIndent = m_indent;

	const UINT kStride = 64;
	const char szHex[] = "0123456789ABCDEF"; 

	size_t lineSize = kIndent+6+2*kStride+2;	// "AAAA: 00010203...3F\n\00" = 6+ 2*64 +2
	char* const pLine = new char [lineSize];

	for (uint32_t addr = offset; addr < (uMemSize + offset); addr += kStride)
	{
		char* pDst = pLine;
		for (UINT i=0; i<kIndent; i++)
			*pDst++ = ' ';
		*pDst++ = szHex[ (addr>>12)&0xf ];
		*pDst++ = szHex[ (addr>>8)&0xf ];
		*pDst++ = szHex[ (addr>>4)&0xf ];
		*pDst++ = szHex[  addr&0xf ];
		*pDst++ = ':';
		*pDst++ = ' ';

		LPBYTE pMem = pMemBase + addr;

		for (UINT i=0; i<kStride; i+=8)
		{
			if (addr + i >= (uMemSize + offset))	// Support short final line (still multiple of 8 bytes)
			{
				lineSize = lineSize - 2*kStride + 2*i;
				break;
			}

			BYTE d;
			d = *pMem++; *pDst++ = szHex[d>>4]; *pDst++ = szHex[d&0xf];
			d = *pMem++; *pDst++ = szHex[d>>4]; *pDst++ = szHex[d&0xf];
			d = *pMem++; *pDst++ = szHex[d>>4]; *pDst++ = szHex[d&0xf];
			d = *pMem++; *pDst++ = szHex[d>>4]; *pDst++ = szHex[d&0xf];

			d = *pMem++; *pDst++ = szHex[d>>4]; *pDst++ = szHex[d&0xf];
			d = *pMem++; *pDst++ = szHex[d>>4]; *pDst++ = szHex[d&0xf];
			d = *pMem++; *pDst++ = szHex[d>>4]; *pDst++ = szHex[d&0xf];
			d = *pMem++; *pDst++ = szHex[d>>4]; *pDst++ = szHex[d&0xf];
		}

		*pDst++ = '\n';
		*pDst = 0;	// For debugger

		fwrite(pLine, 1, lineSize-1, m_hFile);	// -1 so don't write null terminator
	}

	delete [] pLine;
}

void YamlSaveHelper::FileHdr(UINT version)
{
	fprintf(m_hFile, "%s:\n", SS_YAML_KEY_FILEHDR);
	m_indent = 2;
	SaveString(SS_YAML_KEY_TAG, SS_YAML_VALUE_AWSS);
	SaveInt(SS_YAML_KEY_VERSION, version);
}

void YamlSaveHelper::UnitHdr(const std::string& type, UINT version)
{
	fprintf(m_hFile, "\n%s:\n", SS_YAML_KEY_UNIT);
	m_indent = 2;
	SaveString(SS_YAML_KEY_TYPE, type.c_str());
	SaveInt(SS_YAML_KEY_VERSION, version);
}
