#include <ZUtil/ZIniReader.hpp>

#include <ZSTL/ZBasicStringAlgo.hpp>

bool ZIniReader::Read(const char* data, size_t length)
{
	/*
		Algorithm:
		1) Read a line, ignore blank lines and comment lines
		2) If line is KVP
		2.1) No section? -> Error
		2.2) Section exists? -> Parse KVP
		2.2.1) Takes form of K = V? -> Add KVP
		2.2.2) Doesn't? -> Error
		3) Else if section
		3.1) If already seen section -> Error
		3.2) Create section hashtable, insert it
		4) Next line
	*/

	ClearData();
	ErrorMessage.Clear();

	ZHashMap<ZString, ZString>* currentSection = NULL;

	size_t i = 0;	//Current index into file data
	size_t lineStart = 0;	//Start of line
	size_t lineLength;
	uint32_t lineNumber = 1;

	//Parse loop
	while(i < length)
	{
		char c = data[i];
		i++;

		//Treat final character as EOL too
		if(i < length)
		{
			//Split on \n or \r
			if(c != 0x0A && c != 0x0D)
				continue;
			else //Found EOL character
			{
				//Line length does NOT include EOL character
				lineLength = i - 1 - lineStart;
			}
		}
		else //Final line, don't remove extra character
			lineLength = i - lineStart;

		//Line must be non-blank
		if(lineLength > 0 && data[lineStart] != '\n')
		{
			//Get pointer to start of line
			const char* line = &data[lineStart];

			//Ignore comments
			if(line[0] != ';' && line [0] != '#')
			{
				//Section name
				if(line[0] == '[')
				{
					size_t j=1; //Start after '['
					//Read until closing '] or EOL
					while(j<lineLength && line[j] != ']')
					{
						if(!isalnum(line[j]) && line[j] != '_' && line[j] != ' ')
						{
							SetError("Header must contain alphanumeric, space, or _ only", lineNumber);
							return false;
						}
						j++;
					}
					
					//Reached end of line but no terminator
					if(j == lineLength)
					{
						SetError("Missing closing \']\'", lineNumber);
						return false;
					}

					//Copy section name to ZString
					ZString sectionName(line+1, j-1);

					//Check for existence
					if(StringSectionMap.ContainsKey(sectionName))
					{
						SetError("Section already exists", lineNumber);
						return false;
					}

					//Construct new table
					ZHashMap<ZString, ZString>* table = new(std::nothrow) ZHashMap<ZString, ZString>();
					if(table != NULL)
					{
						//Add mapping
						StringSectionMap.Put(sectionName, table);
						Sections.PushBack(table);
						SectionNames.PushBack(sectionName);

						//Save this as the current section
						currentSection = table;
					}
					else
					{
						SetError("Out of memory", lineNumber);
						return false;
					}
				}
				else if(!isspace(line[0])) //Not a space, not comment, not section -> KVP
				{
					//Did we find a section first?
					if(currentSection != NULL)
					{
						size_t j=0; //length of key

						//Scan until space, equals sign, or EOL
						while(j<lineLength && line[j] != '=' && !isspace(line[j]))
						{
							if(!isalnum(line[j]) && line[j] != '_')
							{
								SetError("Key must contain alphanumeric or '_' only", lineNumber);
								return false;
							}
							j++;
						}

						//Didn't find either a space or '='
						if(j == lineLength)
						{
							SetError("Missing '=' after key name", lineNumber);
							return false;
						}

						//Copy key to ZString
						ZString key(line, j);

						if(line[j] != '=')
						{
							//Skip remaining whitespace
							while(j<lineLength && isspace(line[j]))
								j++;

							if(j == lineLength || line[j] != '=')
							{
								SetError("Missing '=' after key name", lineNumber);
								return false;
							}
						}

						//Now we definitely point to a '=' character. Ensure there is data after it
						if(j+1 == lineLength)
						{
							SetError("Expected value after '='", lineNumber);
							return false;
						}
						j++;

						//Skip any whitespace after '='
						while(j<lineLength && isspace(line[j]))
							j++;

						//Special case: if we hit EOL, then we have an empty value
						if(j == lineLength)
						{
							currentSection->Put(key, "");							
						}
						else
						{
							ZString val(&line[j], lineLength-j);
							currentSection->Put(key, val);
						}
					}
					else
					{
						SetError("Values must be in a section", lineNumber);
						return false;
					}

				}
				else
				{
					SetError("Invalid line. Must be a section, comment, or value", lineNumber);
					return false;
				}
			}
		} //if(non-blank line)

		//Check for \r \n pattern
		if(c == '\r') //we expect a \n, but verify
		{
			if(i < length && data[i] == '\n')
				i++;
		}

		//Start on next line
		lineStart = i;
		lineNumber++;
	}

	return !Sections.Empty();
}

/*************************************************************************/

const ZHashMap<ZString, ZString>* ZIniReader::GetSection(const ZString& sectionName)
{
	ZHashMap<ZString, ZHashMap<ZString, ZString>* >::Iterator it = StringSectionMap.Find(sectionName);

	if(it.HasCurrent())
		return it.GetValue();

	return NULL;
}

/*************************************************************************/

void ZIniReader::SetError(const char* message, uint32_t line)
{
	ZStringAlgo::BuildPrintf(ErrorMessage, "Line %u: %s", line, message);

	//Clear all data
	ClearData();
}

/*************************************************************************/

void ZIniReader::ClearData()
{
	for(size_t i = 0; i < Sections.Size(); i++)
		delete Sections[i];

	Sections.Clear();
	SectionNames.Clear();
	StringSectionMap.Clear();
}

/*************************************************************************/

void ZIniReader::GetKVTree(ZKVTree& _kvtree)
{
	for(size_t i = 0; i < Sections.Size(); i++)
	{
		ZHashMap<ZString, ZString>* hmap = Sections[i];
		ZHashMap<ZString, ZString>::Iterator it = hmap->Begin();

		for(; it != hmap->End(); ++it)
		{
			ZString key = SectionNames[i]+"."+it.GetKey();
			ZString& value = it.GetValue();

			_kvtree.Put(key, value);
		}
	}
}
