#include "iniconfig.h"

#include <algorithm>

std::string toLowerCase(const std::string& str)
{
	std::string lower = str;
	std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
	return lower;
}
 
std::string trim(const std::string& str, const std::string& chars = "\t\n\v\f\r ")
{
	std::string trimmed = str;
	trimmed.erase(trimmed.find_last_not_of(chars) + 1);
	trimmed.erase(0, trimmed.find_first_not_of(chars));
	return trimmed;
}

INIConfiguration::Section::Section (const std::string& sname) : m_Name (sname), m_PropertyMap()
{
}

bool INIConfiguration::Section::getStringProperty (const std::string& name, std::string& outPropStr) const
{
	try
	{
		outPropStr = m_PropertyMap.at(toLowerCase(name)).m_Value;
		return true;
	}
	catch (std::out_of_range& oorexcept)
	{
		return false;
	}
}

bool INIConfiguration::load (std::istream& is)
{
	if (!is.good())
	{
		return false;
	}

	std::string currSectionName;

	std::string line;
	std::getline (is, line);

	while (!is.eof() && !is.bad())
	{
		if (line[0] == '[')
		{
			currSectionName = line.substr (1, line.find_last_of (']') - 1);
		}
		else if (line[0] != '#' && line.length() > 2)
		{
			int crloc = line.length() - 1;

			if (crloc >= 0  && line[crloc] == '\r') //check for Windows-style newline
				line.resize (crloc);                //and correct

			size_t equalsPos = line.find_first_of ("=");

			if (equalsPos != std::string::npos)
			{
				std::string key = line.substr (0, equalsPos);
				std::string val = line.substr (equalsPos + 1);

				addProperty (currSectionName, key , val);
			}
		}

		std::getline (is, line);
	}

	if (is.bad())
	{
		return false;
	}

	return true;
}


std::string INIConfiguration::getStringProperty(const std::string& sname, const std::string& name, const std::string& def) const
{
	auto sectionIt = m_SectionMap.find(toLowerCase(sname));

	if (sectionIt != m_SectionMap.end())
	{
		std::string prop;

		if(sectionIt->second.getStringProperty(name, prop))
		{
			return prop;
		}
	}
	
	return def;
}

void INIConfiguration::addProperty (const std::string& sname, const std::string& name, const std::string& val)
{
	if (m_SectionMap.find (toLowerCase(sname)) == m_SectionMap.end())
	{
		m_SectionMap.emplace (toLowerCase(sname), Section (sname));
	}

	Section::Property p;
	p.m_Name = trim(name);
	p.m_Value = trim(val);

	m_SectionMap.at (toLowerCase(sname)).m_PropertyMap[toLowerCase(p.m_Name)] = p;
}