////////////////////////////////////////////////////////////////////
// SML.cpp
//
// Copyright 2007 cDc@seacave
// Distributed under the Boost Software License, Version 1.0
// (See http://www.boost.org/LICENSE_1_0.txt)

#include "Common.h"
#include "SML.h"

using namespace SEACAVE;


// D E F I N E S ///////////////////////////////////////////////////

#define SML_AUTOVALUES_OFF				0
#define SML_AUTOVALUES_ON				1
#ifndef SML_AUTOVALUES
#define SML_AUTOVALUES					SML_AUTOVALUES_ON
#endif

#define SML_SECTIONOPENBRACKET			_T("{")
#define SML_SECTIONCLOSEBRACKET			_T("}")
#define SML_NAMEOPENBRACKET				_T("[")
#define SML_NAMECLOSEBRACKET			_T("]")
#define SML_NAMETOKEN					_T("=")
#define SML_VALUETOKEN					_T("\n")
#define SML_INDENT						_T("\t")

#define SML_TOKEN_SECTIONOPENBRACKET	SML_SECTIONOPENBRACKET[0]
#define SML_TOKEN_SECTIONCLOSEBRACKET	SML_SECTIONCLOSEBRACKET[0]
#define SML_TOKEN_NAMEOPENBRACKET		SML_NAMEOPENBRACKET[0]
#define SML_TOKEN_NAMECLOSEBRACKET		SML_NAMECLOSEBRACKET[0]
#define SML_TOKEN_NAMETOKEN				SML_NAMETOKEN[0]
#define SML_TOKEN_VALUETOKEN			SML_VALUETOKEN[0]
#define SML_TOKEN_INDENT				SML_INDENT[0]
#define SML_TOKEN_IGNORECHARS			_T("\n\r\t ")


// S T R U C T S ///////////////////////////////////////////////////

/*-----------------------------------------------------------*
 * SML class implementation                                  *
 *-----------------------------------------------------------*/

/**
 * Constructor
 */
SML::SML(const String& name)
	:
	m_strName(name),
	m_fncInitItem(NULL),
	m_fncSaveItem(NULL),
	m_fncReleaseItem(NULL),
	m_fncItemData(NULL)
{
}

/**
 * Destructor
 */
SML::~SML()
{
	Release();
}
/*----------------------------------------------------------------*/

/**
 * Released all used memory for this nod and its children
 */
void SML::Release()
{
	m_arrChildren.ReleaseDelete();
	if (m_fncReleaseItem != NULL) {
		for (SMLITEMMAP::iterator it=GetBegin(); it!=GetEnd(); ++it)
			m_fncReleaseItem(it->second, m_fncItemData);
	}
	SMLITEMMAP::Release();
}
/*----------------------------------------------------------------*/


/**
 * Load the values from a file;
 * all children are generated if they do not exist
 */
bool SML::Load(const String& fileName)
{
	File oStream(fileName, File::READ, File::OPEN);
	if (!oStream.isOpen())
		return false;
	return Load(oStream);
}
bool SML::Load(ISTREAM& oStream)
{
	// create the section token filter and mem-file
	MemFile memFile;
	TokenIStream filter(&oStream);
	filter.setTrimTokens(SML_TOKEN_IGNORECHARS);
	// and parse section
	return ParseSection(filter, memFile);
}
bool SML::ParseSection(TokenIStream& filter, MemFile& memFile)
{
	while (true) {
		// find first section start (NameOpenBracket)
		filter.setToken(SML_TOKEN_NAMEOPENBRACKET);
		filter.readLine(memFile);

		// parse values before the new section or end of section
		const size_f_t posMemFile = memFile.getPos();
		MemFile valuesMemFile;
		TokenIStream sectionFilter(&memFile, SML_TOKEN_SECTIONCLOSEBRACKET);
		const size_f_t lenValues = sectionFilter.readLine(valuesMemFile);
		if (ParseValues(valuesMemFile) == false)
			return false; // Parse Error: invalid values
		ASSERT(valuesMemFile.getSize() == 0);

		// if end of section found, return
		if (!sectionFilter.isEOS()) {
			// if a name open bracket was found before the EOS,
			// then restore it for the parent next ParseSection()
			memFile.setPos(posMemFile+(lenValues+1)*sizeof(TCHAR));
			if (!filter.isEOS())
				filter.restoreToken();
			break;
		}
		else {
			ASSERT(memFile.getSize()-posMemFile == lenValues*(size_f_t)sizeof(TCHAR));
			ASSERT(posMemFile == 0 || memFile.getSize()-posMemFile == memFile.getSizeLeft() || memFile.getSizeLeft() == 0);
			memFile.setSize(0);
		}

		// if no more data, return
		if (filter.isEOS())
			break;

		// parse child section name
		filter.setToken(SML_TOKEN_NAMECLOSEBRACKET);
		const size_t lenName = filter.readLine(memFile);
		ASSERT(!filter.isEOS());
		if (lenName == 0)
			return false; // Parse Error: invalid section name
		const String strChildName((LPCTSTR)memFile.getData(), lenName);
		memFile.growSize(-((size_f_t)(lenName*sizeof(TCHAR))));
		ASSERT(memFile.getSize() == 0);
		// create the child with the given name
		const IDX idxChild = CreateChildUnique(strChildName);
		LPSML const pChild = m_arrChildren[idxChild];
		pChild->SetFncItem(m_fncInitItem, m_fncSaveItem, m_fncReleaseItem, m_fncItemData);

		// parse child section
		filter.setToken(SML_TOKEN_SECTIONOPENBRACKET);
		filter.readLine(memFile);
		filter.trimBackLine(memFile);
		ASSERT(memFile.getSize() == 0);
		if (pChild->ParseSection(filter, memFile) == false)
			return false;
	}
	return true;
}
bool SML::ParseValues(MemFile& valuesMemFile)
{
	if (valuesMemFile.getSize() == 0)
		return true;
	// loop through all name/value pairs
	MemFile memLine;
	TokenIStream filterLine(&valuesMemFile);
	filterLine.setTrimTokens(SML_TOKEN_IGNORECHARS);
	filterLine.setToken(SML_TOKEN_VALUETOKEN);
	MemFile memValue;
	TokenIStream filterValue(&memLine);
	filterValue.setTrimTokens(SML_TOKEN_IGNORECHARS);
	filterValue.setToken(SML_TOKEN_NAMETOKEN);
	do {
		// parse value name and value
		filterLine.readLine(memLine);
		filterLine.trimBackLine(memLine);
		filterLine.trimFrontLine(memLine);
		if (memLine.isEOF())
			continue; // empty line, return
		// parse value name
		const size_t lenNameValueReal = (size_t)memLine.getSizeLeft();
		const size_t lenName = filterValue.readLine(memValue);
		#if SML_AUTOVALUES == SML_AUTOVALUES_ON
		String szName;
		if (filterValue.trimBackLine(memValue) == lenName || lenNameValueReal == lenName) {
			// no name found, auto generate the name
			szName = _T("Item") + String::ToString(size());
		} else {
			// read the name
			szName = (LPCTSTR)memValue.getData();
			memValue.setSize(0);
		}
		#else
		filterValue.trimBackLine(memValue);
		String szName = (LPCTSTR)memValue.getData();
		ASSERT(!filterValue.isEOS() && !szName.IsEmpty());
		if (filterValue.isEOS() || szName.IsEmpty()) {
			memValue.setSize(0);
			memLine.setSize(0);
			filterValue.setPos(0);
			continue; // Parse Error: invalid syntax ('=' not found)
		}
		ASSERT(szName.size() == memValue.getSizeLeft());
		memValue.setSize(0);
		#endif
		SMLVALUE& val = operator[](szName);
		// parse value
		filterValue.read(memValue);
		LPCTSTR szValue = filterValue.trimFrontLine(memValue);
		val.val = szValue;
		ASSERT((size_f_t)_tcslen(szValue) == memValue.getSizeLeft());
		memValue.setSize(0);
		memLine.setSize(0);
		filterValue.setPos(0);
	} while (!filterLine.isEOS());
	// all file processed, safe to reset it to 0
	valuesMemFile.setSize(0);
	return true;
}
/*----------------------------------------------------------------*/


/**
 * Write to a file the values of this node and its children.
 * Set to false the second parameter in order not to save the empty children.
 */
bool SML::Save(const String& fileName, SAVEFLAG flags) const
{
	File oStream(fileName, File::WRITE, File::CREATE | File::TRUNCATE);
	if (!oStream.isOpen())
		return false;
	return Save(oStream, flags);
}
bool SML::Save(OSTREAM& oStream, SAVEFLAG flags) const
{
	// save all values and all children
	return SaveIntern(oStream, _T(""), flags);
}
bool SML::SaveBracketize(OSTREAM& oStream, const String& strIndent, SAVEFLAG flags) const
{
	// save its name
	oStream.print(_T("%s" SML_NAMEOPENBRACKET "%s" SML_NAMECLOSEBRACKET "\n"), strIndent.c_str(), m_strName.c_str());
	oStream.print(_T("%s" SML_SECTIONOPENBRACKET "\n"), strIndent.c_str());
	// save all values and all children
	SaveIntern(oStream, strIndent+SML_TOKEN_INDENT, flags);
	// close bracket
	oStream.print(_T("%s" SML_SECTIONCLOSEBRACKET "\n"), strIndent.c_str());
	return true;
}
bool SML::SaveIntern(OSTREAM& oStream, const String& strIndent, SAVEFLAG flags) const
{
	const Flags& flgs = (const Flags&)flags;
	if (flgs.isAnySet(SORT | SORTINV)) {
		// sort values alphabetically and save them
		StringArr lines(0, GetSize());
		for (SMLITEMMAP::const_iterator item=GetBegin(); item!=GetEnd(); ++item)
			if (!m_fncSaveItem || m_fncSaveItem((*item).second, m_fncItemData))
				lines.InsertSort(String::FormatString(_T("%s%s " SML_NAMETOKEN " %s" SML_VALUETOKEN), strIndent.c_str(), (*item).first.c_str(), (*item).second.val.c_str()),
					(flgs.isAnySet(SORT) ? String::CompareAlphabetically : String::CompareAlphabeticallyInv));
		FOREACH(l, lines)
			oStream.print(lines[l]);
	} else {
		// save all values directly
		for (SMLITEMMAP::const_iterator item=GetBegin(); item!=GetEnd(); ++item)
			if (!m_fncSaveItem || m_fncSaveItem((*item).second, m_fncItemData))
				oStream.print(_T("%s%s " SML_NAMETOKEN " %s" SML_VALUETOKEN), strIndent.c_str(), (*item).first.c_str(), (*item).second.val.c_str());
	}
	// save now all children
	bool bFirst = IsEmpty();
	FOREACH(i, m_arrChildren) {
		const SML* pSML = m_arrChildren[i];
		// skip empty children
		if (!flgs.isSet(SAVEEMPTY) && pSML->IsEmpty() && pSML->m_arrChildren.IsEmpty())
			continue;
		// insert a new line to separate from the above section (only for visual aspect)
		if (bFirst)
			bFirst = false;
		else
			oStream.print(_T("\n"));
		// save child
		pSML->SaveBracketize(oStream, strIndent, flags);
	}
	return true;
}
/*----------------------------------------------------------------*/


/**
 * Create and insert a new child
 */
IDX SML::CreateChild(const String& strChildName)
{
	return InsertChild(new SML(strChildName));
}
IDX SML::CreateChildUnique(const String& strChildName)
{
	return InsertChildUnique(new SML(strChildName));
}
/*----------------------------------------------------------------*/


/**
 * Insert a new child
 */
IDX SML::InsertChild(const LPSML pSML)
{
	return m_arrChildren.InsertSort(pSML, SML::Compare);
}
IDX SML::InsertChildUnique(const LPSML pSML)
{
	const std::pair<IDX,bool> res(m_arrChildren.InsertSortUnique(pSML, SML::Compare));
	if (res.second)
		delete pSML;
	return res.first;
}
/*----------------------------------------------------------------*/


/**
 * Remove an existing child
 */
void SML::RemoveChild(IDX idx)
{
	m_arrChildren.RemoveAtMove(idx);
}
/*----------------------------------------------------------------*/

/**
 * Remove and destroy an existing child
 */
void SML::DestroyChild(IDX idx)
{
	delete m_arrChildren[idx];
	RemoveChild(idx);
}
/*----------------------------------------------------------------*/


/**
 * Retrieve an child by its name; NO_INDEX if unexistent
 */
IDX SML::GetChild(const String& name) const
{
	return m_arrChildren.FindFirst(&name, SML::CompareName);
}
/*----------------------------------------------------------------*/


/**
 * Retrieve an item; NULL if unexistent
 */
const SMLVALUE* SML::GetValue(const String& key) const
{
	const_iterator it = Find(key);
	if (it == GetEnd())
		return NULL;
	return &(it->second);
}
/*----------------------------------------------------------------*/


/**
 * Insert or retrieve an item; initialize it if necessary
 */
SMLVALUE& SML::GetValue(const String& key)
{
	bool bExisted;
	SMLVALUE& val = (*Insert(key, bExisted)).second;
	if (!bExisted && m_fncInitItem != NULL)
		m_fncInitItem(key, val, m_fncItemData);
	return val;
}
/*----------------------------------------------------------------*/


/**
 * Retrieve an unnamed item by index
 */
const SMLVALUE& SML::GetValue(IDX idx) const
{
	ASSERT(idx < this->size());
	const String szName(_T("Item") + String::ToString(idx));
	return this->at(szName);
}
/*----------------------------------------------------------------*/


/**
 * Reset items' init and release functions
 */
void SML::SetFncItem(TFncInitItem fncInit, TFncSaveItem fncSave, TFncReleaseItem fncRelease, void* data)
{
	m_fncInitItem = fncInit;
	m_fncSaveItem = fncSave;
	m_fncReleaseItem = fncRelease;
	m_fncItemData = data;
}
/*----------------------------------------------------------------*/


/**
 * Compare two SML nodes by name
 */
int STCALL SML::Compare(const void* l, const void* r)
{
	return _tcscmp((*((const SML**)l))->GetName(), (*((const SML**)r))->GetName());
}
/*----------------------------------------------------------------*/

/**
 * Compare a name and a SML node name
 */
int STCALL SML::CompareName(const void* l, const void* r)
{
	return _tcscmp((*((const SML**)l))->GetName(), ((const String*)r)->c_str());
}
/*----------------------------------------------------------------*/
