// $Id: IniFile.cpp 8019 2007-07-14 22:30:11Z trick $

/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Deadly Rooms of Death.
 *
 * The Initial Developer of the Original Code is
 * Caravel Software.
 * Portions created by the Initial Developer are Copyright (C) 1995, 1996, 
 * 1997, 2000, 2001, 2002 Caravel Software. All Rights Reserved.
 *
 * Contributor(s):
 * Matt Schikore (Schik)
 *
 * ***** END LICENSE BLOCK ***** */

// Apple and Linux keep isspace() in ctype.h.
#if defined __APPLE__ || defined __linux__ || defined __FreeBSD__
#  include <ctype.h>
#endif

#include "IniFile.h"
#include "Assert.h"
#include "Files.h"

using namespace std;

//******************************************************************************
CIniSection::CIniSection(
//Constructor.
//
//Params:
	const char *pszSetName)  //(in)
{
	ASSERT(pszSetName != NULL);
	this->strName = pszSetName;
}

//******************************************************************************
CIniSection::CIniSection()
//Default constructor.
{
}

//******************************************************************************
bool CIniSection::GetString(
//Gets a string from this section
//
//Params:
	const char* pszKey,      //(in)
	string& strBuffer)    //(out)
//
//Returns:
//True if the string exists, false if not
{
	strBuffer = "";
	map<string,std::list<string> >::iterator iter = this->entries.find(pszKey);
	if (iter == this->entries.end())
		return false;

	bool bFirst = true;
	for (std::list<string>::const_iterator i=iter->second.begin(); i!=iter->second.end(); ++i)
	{
		//Concatenate multiple entries, delineate with a semicolon.
		if (bFirst)
			bFirst = false;
		else
			strBuffer += ";";

		//Strip excess line-ending-characters and other spaces
		//(needed to parse dos-style drod.ini in linux)
		string str = *i;
		int buflen = str.length();
		if (buflen) {
			do
			{--buflen;}
			while (buflen && isspace(str[buflen]));
			str.resize(buflen + 1);
		}
		strBuffer += str;
	}
	return true;
}

//******************************************************************************
bool CIniSection::GetString(
//OUT: list of delineated string segments
	const char* pszKey, list<string>& strBuffer)
{
	strBuffer.clear();
	string str;
	if (!GetString(pszKey, str))
		return false;

	const char *pszStartOfFilename = str.c_str();
	for (char *pszSeek = &(*str.begin()); *pszSeek != '\0'; ++pszSeek)
	{
		if (*pszSeek == ';') //Found delineator
		{
			//Add to list.
			*pszSeek = '\0'; //Null-terminate the string.
			strBuffer.push_back(pszStartOfFilename);
			pszStartOfFilename = pszSeek + 1;
		}
	}

	//Add remainder.
	strBuffer.push_back(pszStartOfFilename);
	return true;
}

//******************************************************************************
bool CIniSection::GetString(
//OUT: list of delineated string segments
	const char* pszKey, list<WSTRING>& wstrBuffer)
{
	wstrBuffer.clear();
	list<string> strs;
	if (!GetString(pszKey, strs))
		return false;

	WSTRING wstr;
	for (list<string>::const_iterator iStr = strs.begin(); iStr != strs.end(); ++iStr)
	{
		AsciiToUnicode(iStr->c_str(), wstr);
		wstrBuffer.push_back(wstr);
	}
	return true;
}

//******************************************************************************
void CIniSection::WriteString(
//Writes a string to this section and key
//
//Params:
  const char *pszKey,   //(in)
  const char *pszValue, //(in)
  const bool bOverwrite)//if true, replace all entries for this key
{
	std::list<string>& texts = this->entries[(string)pszKey];
	if (bOverwrite)
		texts.clear();
	texts.push_back((string)pszValue);
}

//******************************************************************************
void CIniSection::Save(
//Saves the section to the specified (already opened) file
//
//Params:
  FILE* const pFile)       //(in)
{
	ASSERT(pFile);
	fprintf(pFile, "[%s]\n", strName.c_str());

	for (map<string,std::list<string> >::iterator entry = this->entries.begin(); entry != this->entries.end(); ++entry)
		for (std::list<string>::const_iterator i=entry->second.begin(); i!=entry->second.end(); ++i)
			fprintf(pFile, "%s=%s\n", entry->first.c_str(), i->c_str());
	fprintf(pFile, "\n");
}

//******************************************************************************
CIniFile::CIniFile()
//Constructor.
	: bLoaded(false)
	, bDirty(false)
{
}

//******************************************************************************
CIniFile::~CIniFile()
//Destructor.
{
	if (this->bLoaded && this->bDirty)
	{
		FILE* pFile = CFiles::Open(wstrFilename.c_str(), "w");
		if (pFile != NULL)
		{
			for (map<string, CIniSection>::iterator i = this->sections.begin();
					i != this->sections.end();	++i)
			{
				i->second.Save(pFile);
			}
			fclose(pFile);
		}
	}
}

//******************************************************************************
bool CIniFile::Load(
//Loads the INI file
//
//Params:
  const WCHAR *wszSetFilename) //(in)
{
	this->bLoaded = true;
	this->bDirty = false;
	char buffer[MAX_PROFILE_STRING*2+2];
	wstrFilename = wszSetFilename;

	FILE* pFile = CFiles::Open( wstrFilename.c_str(), "r");
	if (NULL == pFile) return false;

	string curSection;
	do {
		fgets(buffer, sizeof(buffer), pFile);
		if (feof(pFile)) break;
		if (buffer[0] == '[')
		{
			string sectionName(&buffer[1]);
			string::size_type end = sectionName.find_first_of( ']' );
			if (end != string::npos)
			{
				sectionName.erase( end );
				curSection = sectionName;
			}
			else
			{
				// Can't find the closing ] in a section name
				fclose(pFile);
				return false;
			}
		}
		else
		{
			string line(buffer);
			bool bIsWhitespace = true;
			for (string::iterator c = line.begin(); c != line.end(); c++)
			{
				if (!isspace(*c))
				{
					bIsWhitespace = false;
					break;
				}
			}
			if (bIsWhitespace) continue;

			// It's a key/value pair, not a section name
			string::size_type equalsPos = line.find_first_of('=');
			if (equalsPos == string::npos)
			{
				// No = in the line
				fclose(pFile);
				return false;
			}
			else
			{
				string key = line.substr(0, equalsPos);
				string value = line.substr(equalsPos+1);
				string::iterator last = value.end();
				last--;
				value.erase(last);

				// Now add the pair to the correct section.
				// See if the section already exists.
				map<string, CIniSection>::iterator sec;
				sec = sections.find(curSection);
				if (sec == sections.end())
				{
					// Section doesn't exist - make a new one
					CIniSection newSection(curSection.c_str());
					sec=sections.insert(make_pair(curSection, newSection)).first;
				}
				sec->second.WriteString(key.c_str(), value.c_str(), false);
			}
		}
	} while (true);

	fclose(pFile);
	return true;
}

//******************************************************************************
bool CIniFile::GetString(
//Gets the value of the specified section/key
//
//Params:
	const char* pszSection,  //(in)
	const char* pszKey,      //(in)
	string& strBuffer)       //(out)
//
//Returns:
//True if the section/key exists, false if not
{
	map<string, CIniSection>::iterator sec = this->sections.find(pszSection);
	if (sec == this->sections.end())
	{
		// section does not exist
		strBuffer = "";
		return false;
	}

	return sec->second.GetString(pszKey, strBuffer);
}

//**************************
bool CIniFile::GetString(
	const char* pszSection, const char* pszKey, list<string>& strBuffer)
{
	map<string, CIniSection>::iterator sec = this->sections.find(pszSection);
	if (sec == this->sections.end())
	{
		// section does not exist
		strBuffer.clear();
		return false;
	}

	return sec->second.GetString(pszKey, strBuffer);
}

//**************************
bool CIniFile::GetString(
	const char* pszSection, const char* pszKey, list<WSTRING>& wstrBuffer)
{
	map<string, CIniSection>::iterator sec = this->sections.find(pszSection);
	if (sec == this->sections.end())
	{
		// section does not exist
		wstrBuffer.clear();
		return false;
	}

	return sec->second.GetString(pszKey, wstrBuffer);
}

//******************************************************************************
void CIniFile::WriteString(
//Writes a key/value to a section
//
//Params:
  const char *pszSection,  //(in)
  const char *pszKey,      //(in)
  const char *pszValue,    //(in)
  const bool bOverwrite)   //(in) if true, replace all strings under this key [default=true]
{
	ASSERT(this->bLoaded);
	this->bDirty = true;

	map<string, CIniSection>::iterator sec = sections.find(pszSection);
	if (sec == sections.end())
	{
		// Section does not exist yet -- create it.
		CIniSection newSection(pszSection);
		sec = this->sections.insert(make_pair(pszSection, newSection)).first;
	}
	sec->second.WriteString(pszKey, pszValue, bOverwrite);
}
