/*
** configfile.cpp
** Implements the basic .ini parsing class
**
**---------------------------------------------------------------------------
** Copyright 1998-2008 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "configfile.h"
#include "files.h"

#define READBUFFERSIZE	256

//====================================================================
//
// FConfigFile Constructor
//
//====================================================================

FConfigFile::FConfigFile ()
{
	Sections = CurrentSection = NULL;
	LastSectionPtr = &Sections;
	CurrentEntry = NULL;
	PathName = "";
	OkayToWrite = true;
	FileExisted = true;
}

//====================================================================
//
// FConfigFile Constructor
//
//====================================================================

FConfigFile::FConfigFile (const char *pathname)
{
	Sections = CurrentSection = NULL;
	LastSectionPtr = &Sections;
	CurrentEntry = NULL;
	ChangePathName (pathname);
	LoadConfigFile ();
	OkayToWrite = true;
	FileExisted = true;
}

//====================================================================
//
// FConfigFile Copy Constructor
//
//====================================================================

FConfigFile::FConfigFile (const FConfigFile &other)
{
	Sections = CurrentSection = NULL;
	LastSectionPtr = &Sections;
	CurrentEntry = NULL;
	ChangePathName (other.PathName);
	*this = other;
	OkayToWrite = other.OkayToWrite;
	FileExisted = other.FileExisted;
}

//====================================================================
//
// FConfigFile Destructor
//
//====================================================================

FConfigFile::~FConfigFile ()
{
	FConfigSection *section = Sections;

	while (section != NULL)
	{
		FConfigSection *nextsection = section->Next;
		FConfigEntry *entry = section->RootEntry;

		while (entry != NULL)
		{
			FConfigEntry *nextentry = entry->Next;
			delete[] entry->Value;
			delete[] (char *)entry;
			entry = nextentry;
		}
		delete section;
		section = nextsection;
	}
}

//====================================================================
//
// FConfigFile Copy Operator
//
//====================================================================

FConfigFile &FConfigFile::operator = (const FConfigFile &other)
{
	FConfigSection *fromsection, *tosection;
	FConfigEntry *fromentry;

	ClearConfig ();
	fromsection = other.Sections;
	while (fromsection != NULL)
	{
		fromentry = fromsection->RootEntry;
		tosection = NewConfigSection (fromsection->SectionName);
		while (fromentry != NULL)
		{
			NewConfigEntry (tosection, fromentry->Key, fromentry->Value);
			fromentry = fromentry->Next;
		}
		fromsection = fromsection->Next;
	}
	return *this;
}

//====================================================================
//
// FConfigFile :: ClearConfig
//
// Removes all sections and entries from the config file.
//
//====================================================================

void FConfigFile::ClearConfig ()
{
	CurrentSection = Sections;
	while (CurrentSection != NULL)
	{
		FConfigSection *next = CurrentSection->Next;
		ClearCurrentSection ();
		delete CurrentSection;
		CurrentSection = next;
	}
	Sections = NULL;
	LastSectionPtr = &Sections;
}

//====================================================================
//
// FConfigFile :: ChangePathName
//
//====================================================================

void FConfigFile::ChangePathName (const char *pathname)
{
	PathName = pathname;
}

//====================================================================
//
// FConfigFile :: CreateSectionAtStart
//
// Creates the section at the start of the file if it does not exist.
// Otherwise, simply moves the section to the start of the file.
//
//====================================================================

void FConfigFile::CreateSectionAtStart (const char *name)
{
	NewConfigSection (name);
	MoveSectionToStart (name);
}

//====================================================================
//
// FConfigFile :: MoveSectionToStart
//
// Moves the named section to the start of the file if it exists.
// Otherwise, does nothing.
//
//====================================================================

void FConfigFile::MoveSectionToStart (const char *name)
{
	FConfigSection *section = FindSection (name);

	if (section != NULL)
	{
		FConfigSection **prevsec = &Sections;
		while (*prevsec != NULL && *prevsec != section)
		{
			prevsec = &((*prevsec)->Next);
		}
		*prevsec = section->Next;
		section->Next = Sections;
		Sections = section;
		if (LastSectionPtr == &section->Next)
		{
			LastSectionPtr = prevsec;
		}
	}
}


//====================================================================
//
// FConfigFile :: SetSection
//
// Sets the current section to the named one, optionally creating it
// if it does not exist. Returns true if the section exists (even if
// it was newly created), false otherwise.
//
//====================================================================

bool FConfigFile::SetSection (const char *name, bool allowCreate)
{
	FConfigSection *section = FindSection (name);
	if (section == NULL && allowCreate)
	{
		section = NewConfigSection (name);
	}
	if (section != NULL)
	{
		CurrentSection = section;
		CurrentEntry = section->RootEntry;
		return true;
	}
	return false;
}

//====================================================================
//
// FConfigFile :: SetFirstSection
//
// Sets the current section to the first one in the file. Returns
// false if there are no sections.
//
//====================================================================

bool FConfigFile::SetFirstSection ()
{
	CurrentSection = Sections;
	if (CurrentSection != NULL)
	{
		CurrentEntry = CurrentSection->RootEntry;
		return true;
	}
	return false;
}

//====================================================================
//
// FConfigFile :: SetNextSection
//
// Advances the current section to the next one in the file. Returns
// false if there are no more sections.
//
//====================================================================

bool FConfigFile::SetNextSection ()
{
	if (CurrentSection != NULL)
	{
		CurrentSection = CurrentSection->Next;
		if (CurrentSection != NULL)
		{
			CurrentEntry = CurrentSection->RootEntry;
			return true;
		}
	}
	return false;
}

//====================================================================
//
// FConfigFile :: GetCurrentSection
//
// Returns the name of the current section.
//
//====================================================================

const char *FConfigFile::GetCurrentSection () const
{
	if (CurrentSection != NULL)
	{
		return CurrentSection->SectionName.GetChars();
	}
	return NULL;
}

//====================================================================
//
// FConfigFile :: ClearCurrentSection
//
// Removes all entries from the current section.
//
//====================================================================

void FConfigFile::ClearCurrentSection ()
{
	if (CurrentSection != NULL)
	{
		FConfigEntry *entry, *next;

		entry = CurrentSection->RootEntry;
		while (entry != NULL)
		{
			next = entry->Next;
			delete[] entry->Value;
			delete[] (char *)entry;
			entry = next;
		}
		CurrentSection->RootEntry = NULL;
		CurrentSection->LastEntryPtr = &CurrentSection->RootEntry;
	}
}

//====================================================================
//
// FConfigFile :: DeleteCurrentSection
//
// Completely removes the current section. The current section is
// advanced to the next section. Returns true if there is still a
// current section.
//
//====================================================================

bool FConfigFile::DeleteCurrentSection()
{
	if (CurrentSection != NULL)
	{
		FConfigSection *sec;

		ClearCurrentSection();

		// Find the preceding section.
		for (sec = Sections; sec != NULL && sec->Next != CurrentSection; sec = sec->Next)
		{ }

		sec->Next = CurrentSection->Next;
		if (LastSectionPtr == &CurrentSection->Next)
		{
			LastSectionPtr = &sec->Next;
		}

		delete CurrentSection;

		CurrentSection = sec->Next;
		return CurrentSection != NULL;
	}
	return false;
}

//====================================================================
//
// FConfigFile :: ClearKey
//
// Removes a key from the current section, if found. If there are
// duplicates, only the first is removed.
//
//====================================================================

void FConfigFile::ClearKey(const char *key)
{
	if (CurrentSection->RootEntry == NULL)
	{
		return;
	}
	FConfigEntry **prober = &CurrentSection->RootEntry, *probe = *prober;

	while (probe != NULL && stricmp(probe->Key, key) != 0)
	{
		prober = &probe->Next;
		probe = *prober;
	}
	if (probe != NULL)
	{
		*prober = probe->Next;
		if (CurrentSection->LastEntryPtr == &probe->Next)
		{
			CurrentSection->LastEntryPtr = prober;
		}
		delete[] probe->Value;
		delete[] (char *)probe;
	}
}

//====================================================================
//
// FConfigFile :: SectionIsEmpty
//
// Returns true if the current section has no entries. If there is
// no current section, it is also considered empty.
//
//====================================================================

bool FConfigFile::SectionIsEmpty()
{
	return (CurrentSection == NULL) || (CurrentSection->RootEntry == NULL);
}


//====================================================================
//
// FConfigFile :: NextInSection
//
// Provides the next key/value pair in the current section. Returns
// true if there was another, false otherwise.
//
//====================================================================

bool FConfigFile::NextInSection (const char *&key, const char *&value)
{
	FConfigEntry *entry = CurrentEntry;

	if (entry == NULL)
		return false;

	CurrentEntry = entry->Next;
	key = entry->Key;
	value = entry->Value;
	return true;
}

//====================================================================
//
// FConfigFile :: GetValueForKey
//
// Returns the value for the specified key in the current section,
// returning NULL if the key does not exist.
//
//====================================================================

const char *FConfigFile::GetValueForKey (const char *key) const
{
	FConfigEntry *entry = FindEntry (CurrentSection, key);

	if (entry != NULL)
	{
		return entry->Value;
	}
	return NULL;
}

//====================================================================
//
// FConfigFile :: SetValueForKey
//
// Sets they key/value pair as specified in the current section. If
// duplicates are allowed, it always creates a new pair. Otherwise, it
// will overwrite the value of an existing key with the same name.
//
//====================================================================

void FConfigFile::SetValueForKey (const char *key, const char *value, bool duplicates)
{
	if (CurrentSection != NULL)
	{
		FConfigEntry *entry;

		if (duplicates || (entry = FindEntry (CurrentSection, key)) == NULL)
		{
			NewConfigEntry (CurrentSection, key, value);
		}
		else
		{
			entry->SetValue (value);
		}
	}
}

//====================================================================
//
// FConfigFile :: FindSection
//
//====================================================================

FConfigFile::FConfigSection *FConfigFile::FindSection (const char *name) const
{
	FConfigSection *section = Sections;

	while (section != NULL && section->SectionName.CompareNoCase(name) != 0)
	{
		section = section->Next;
	}
	return section;
}

//====================================================================
//
// FConfigFile :: RenameSection
//
//====================================================================

void FConfigFile::RenameSection (const char *oldname, const char *newname) const
{
	FConfigSection *section = FindSection(oldname);

	if (section != NULL)
	{
		section->SectionName = newname;
	}
}

//====================================================================
//
// FConfigFile :: FindEntry
//
//====================================================================

FConfigFile::FConfigEntry *FConfigFile::FindEntry (
	FConfigFile::FConfigSection *section, const char *key) const
{
	FConfigEntry *probe = section->RootEntry;

	while (probe != NULL && stricmp (probe->Key, key) != 0)
	{
		probe = probe->Next;
	}
	return probe;
}

//====================================================================
//
// FConfigFile :: NewConfigSection
//
//====================================================================

FConfigFile::FConfigSection *FConfigFile::NewConfigSection (const char *name)
{
	FConfigSection *section;

	section = FindSection (name);
	if (section == NULL)
	{
		section = new FConfigSection;
		section->RootEntry = NULL;
		section->LastEntryPtr = &section->RootEntry;
		section->Next = NULL;
		section->SectionName = name;
		*LastSectionPtr = section;
		LastSectionPtr = &section->Next;
	}
	return section;
}

//====================================================================
//
// FConfigFile :: NewConfigEntry
//
//====================================================================

FConfigFile::FConfigEntry *FConfigFile::NewConfigEntry (
	FConfigFile::FConfigSection *section, const char *key, const char *value)
{
	FConfigEntry *entry;
	size_t keylen;

	keylen = strlen (key);
	entry = (FConfigEntry *)new char[sizeof(*section)+keylen];
	entry->Value = NULL;
	entry->Next = NULL;
	memcpy (entry->Key, key, keylen);
	entry->Key[keylen] = 0;
	*(section->LastEntryPtr) = entry;
	section->LastEntryPtr = &entry->Next;
	entry->SetValue (value);
	return entry;
}

//====================================================================
//
// FConfigFile :: LoadConfigFile
//
//====================================================================

void FConfigFile::LoadConfigFile ()
{
	FileReader file;
	bool succ;

	FileExisted = false;
	if (!file.OpenFile (PathName))
	{
		return;
	}

	succ = ReadConfig (&file);
	FileExisted = succ;
}

//====================================================================
//
// FConfigFile :: ReadConfig
//
//====================================================================

bool FConfigFile::ReadConfig (void *file)
{
	uint8_t readbuf[READBUFFERSIZE];
	FConfigSection *section = NULL;
	ClearConfig ();

	while (ReadLine ((char*)readbuf, READBUFFERSIZE, file) != NULL)
	{
		uint8_t *start = readbuf;
		uint8_t *equalpt;
		uint8_t *endpt;

		// Remove white space at start of line
		while (*start && *start <= ' ')
		{
			start++;
		}
		// Remove comment lines
		if (*start == '#' || (start[0] == '/' && start[1] == '/'))
		{
			continue;
		}
		// Do not process tail of long line
		const bool longline = (READBUFFERSIZE - 1) == strlen((char*)readbuf) && '\n' != readbuf[READBUFFERSIZE - 2];
		if (longline)
		{
			endpt = start + READBUFFERSIZE - 2;
		}
		else
		{
			// Remove white space at end of line
			endpt = start + strlen ((char*)start) - 1;
			while (endpt > start && *endpt <= ' ')
			{
				endpt--;
			}
			// Remove line feed '\n' character
			endpt[1] = 0;
			if (endpt <= start)
				continue;	// Nothing here
		}

		if (*start == '[')
		{ // Section header
			if (*endpt == ']')
				*endpt = 0;
			section = NewConfigSection ((char*)start+1);
		}
		else if (section == NULL)
		{
			return false;
		}
		else
		{ // Should be key=value
			equalpt = (uint8_t*)strchr ((char*)start, '=');
			if (equalpt != NULL && equalpt > start)
			{
				// Remove white space in front of =
				uint8_t *whiteprobe = equalpt - 1;
				while (whiteprobe > start && isspace(*whiteprobe))
				{
					whiteprobe--;
				}
				whiteprobe[1] = 0;
				// Remove white space after =
				whiteprobe = equalpt + 1;
				while (*whiteprobe && isspace(*whiteprobe))
				{
					whiteprobe++;
				}
				*(whiteprobe - 1) = 0;
				// Check for multi-line value
				if (whiteprobe[0] == '<' && whiteprobe[1] == '<' && whiteprobe[2] == '<' && whiteprobe[3] != '\0')
				{
					ReadMultiLineValue (file, section, (char*)start, (char*)whiteprobe + 3);
				}
				else if (longline)
				{
					const FString key = (char*)start;
					FString value = (char*)whiteprobe;
					
					while (ReadLine ((char*)readbuf, READBUFFERSIZE, file) != NULL)
					{
						const size_t endpos = (0 == readbuf[0]) ? 0 : (strlen((char*)readbuf) - 1);
						const bool endofline = '\n' == readbuf[endpos];
						
						if (endofline)
						{
							readbuf[endpos] = 0;
						}
						
						value += (char*)readbuf;
						
						if (endofline)
						{
							break;
						}
					}
					
					NewConfigEntry (section, key.GetChars(), value.GetChars());
				}
				else
				{
					NewConfigEntry (section, (char*)start, (char*)whiteprobe);
				}
			}
		}
	}
	return true;
}

//====================================================================
//
// FConfigFile :: ReadMultiLineValue
//
// Reads a multi-line value, with format as follows:
//
//    key=<<<ENDTAG
//    ... blah blah blah ...
//    >>>ENDTAG
//
// The final ENDTAG must be on a line all by itself.
//
//====================================================================

FConfigFile::FConfigEntry *FConfigFile::ReadMultiLineValue(void *file, FConfigSection *section, const char *key, const char *endtag)
{
	char readbuf[READBUFFERSIZE];
	FString value;
	size_t endlen = strlen(endtag);

	// Keep on reading lines until we reach a line that matches >>>endtag
	while (ReadLine(readbuf, READBUFFERSIZE, file) != NULL)
	{
		// Does the start of this line match the endtag?
		if (readbuf[0] == '>' && readbuf[1] == '>' && readbuf[2] == '>' &&
			strncmp(readbuf + 3, endtag, endlen) == 0)
		{ // Is there nothing but line break characters after the match?
			size_t i;
			for (i = endlen + 3; readbuf[i] != '\0'; ++i)
			{
				if (readbuf[i] != '\n' && readbuf[i] != '\r')
				{ // Not a line break character
					break;
				}
			}
			if (readbuf[i] == '\0')
			{ // We're done; strip the previous line's line breaks, since it's not part of the value.
				value.StripRight("\n\r");
			}
			break;
		}
		// Append this line to the value.
		value << readbuf;
	}
	return NewConfigEntry(section, key, value);
}

//====================================================================
//
// FConfigFile :: ReadLine
//
//====================================================================

char *FConfigFile::ReadLine (char *string, int n, void *file) const
{
	return ((FileReader *)file)->Gets (string, n);
}

//====================================================================
//
// FConfigFile :: WriteConfigFile
//
//====================================================================

bool FConfigFile::WriteConfigFile () const
{
	if (!OkayToWrite && FileExisted)
	{ // Pretend it was written anyway so that the user doesn't get
	  // any "config not written" notifications, but only if the file
	  // already existed. Otherwise, let it write out a default one.
		return true;
	}

	FileWriter *file = FileWriter::Open (PathName);
	FConfigSection *section;
	FConfigEntry *entry;

	if (file == NULL)
		return false;

	WriteCommentHeader (file);

	section = Sections;
	while (section != NULL)
	{
		entry = section->RootEntry;
		if (section->Note.IsNotEmpty())
		{
			file->Write (section->Note.GetChars(), section->Note.Len());
		}
		file->Printf ("[%s]\n", section->SectionName.GetChars());
		while (entry != NULL)
		{
			if (strpbrk(entry->Value, "\r\n") == NULL)
			{ // Single-line value
				file->Printf ("%s=%s\n", entry->Key, entry->Value);
			}
			else
			{ // Multi-line value
				const char *endtag = GenerateEndTag(entry->Value);
				file->Printf ("%s=<<<%s\n%s\n>>>%s\n", entry->Key,
					endtag, entry->Value, endtag);
			}
			entry = entry->Next;
		}
		section = section->Next;
		file->Write ("\n", 1);
	}
	delete file;
	return true;
}

//====================================================================
//
// FConfigFile :: GenerateEndTag
//
// Generates a terminator sequence for multi-line values that does
// not appear anywhere in the value.
//
//====================================================================

const char *FConfigFile::GenerateEndTag(const char *value)
{
	static const char Base64Table[] =
		"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._";
	static char EndTag[25] = "EOV-";

	// Try different 20-character sequences until we find one that
	// isn't in the value. We create the sequences by generating two
	// 64-bit random numbers and Base64 encoding the first 15 bytes
	// from them.
	union { uint16_t rand_num[8]; uint8_t rand_bytes[16]; };
	do
	{
		for (auto &r : rand_num) r = (uint16_t)rand();

		for (int i = 0; i < 5; ++i)
		{
			//uint32_t three_bytes = (rand_bytes[i*3] << 16) | (rand_bytes[i*3+1] << 8) | (rand_bytes[i*3+2]); // ???
			EndTag[4+i*4  ] = Base64Table[rand_bytes[i*3] >> 2];
			EndTag[4+i*4+1] = Base64Table[((rand_bytes[i*3] & 3) << 4) | (rand_bytes[i*3+1] >> 4)];
			EndTag[4+i*4+2] = Base64Table[((rand_bytes[i*3+1] & 15) << 2) | (rand_bytes[i*3+2] >> 6)];
			EndTag[4+i*4+3] = Base64Table[rand_bytes[i*3+2] & 63];
		}
	}
	while (strstr(value, EndTag) != NULL);
	return EndTag;
}

//====================================================================
//
// FConfigFile :: WriteCommentHeader
//
// Override in a subclass to write a header to the config file.
//
//====================================================================

void FConfigFile::WriteCommentHeader (FileWriter *file) const
{
}

//====================================================================
//
// FConfigFile :: FConfigEntry :: SetValue
//
//====================================================================

void FConfigFile::FConfigEntry::SetValue (const char *value)
{
	if (Value != NULL)
	{
		delete[] Value;
	}
	Value = new char[strlen (value)+1];
	strcpy (Value, value);
}

//====================================================================
//
// FConfigFile :: GetPosition
//
// Populates a struct with the current position of the parse cursor.
//
//====================================================================

void FConfigFile::GetPosition (FConfigFile::Position &pos) const
{
	pos.Section = CurrentSection;
	pos.Entry = CurrentEntry;
}

//====================================================================
//
// FConfigFile :: SetPosition
//
// Sets the parse cursor to a previously retrieved position.
//
//====================================================================

void FConfigFile::SetPosition (const FConfigFile::Position &pos)
{
	CurrentSection = pos.Section;
	CurrentEntry = pos.Entry;
}

//====================================================================
//
// FConfigFile :: SetSectionNote
//
// Sets a comment note to be inserted into the INI verbatim directly
// ahead of the section. Notes are lost when the INI is read so must
// be explicitly set to be maintained.
//
//====================================================================

void FConfigFile::SetSectionNote(const char *section, const char *note)
{
	SetSectionNote(FindSection(section), note);
}

void FConfigFile::SetSectionNote(const char *note)
{
	SetSectionNote(CurrentSection, note);
}

void FConfigFile::SetSectionNote(FConfigSection *section, const char *note)
{
	if (section != NULL)
	{
		if (note == NULL)
		{
			note = "";
		}
		section->Note = note;
	}
}