#include "stdafx.h"
#include "Utilities/rPlatform.h"
#include "Utilities/simpleini/SimpleIni.h"

#include "Ini.h"
#include <cctype>
#include <regex>

#define DEF_CONFIG_NAME "./rpcs3.ini"

//TODO: make thread safe/remove static singleton
CSimpleIniCaseA *getIniFile()
{
	static bool inited = false;
	static CSimpleIniCaseA ini;
	if (inited == false)
	{
		ini.SetUnicode(true);
		ini.LoadFile(std::string(rPlatform::getConfigDir() + DEF_CONFIG_NAME).c_str());
		inited = true;
	}
	return &ini;
}

void saveIniFile()
{
	getIniFile()->SaveFile(std::string(rPlatform::getConfigDir() + DEF_CONFIG_NAME).c_str());
}

Inis Ini;

static bool StringToBool(const std::string& str)
{
	return std::regex_match(str.begin(), str.end(),
		std::regex("1|e|t|enable|true", std::regex_constants::icase));
}

static inline std::string BoolToString(const bool b)
{
	return b ? "true" : "false";
}

//takes a string of format "[number]x[number]" and returns a pair of ints
//example input would be "123x456" and the returned value would be {123,456}
static std::pair<int, int> StringToSize(const std::string& str)
{
	std::size_t start = 0, found;
	std::vector<int> vec;

	for (int i = 0; i < 2 && (found = str.find_first_of('x', start)); i++)
	{
		try
		{
			vec.push_back(std::stoi(str.substr(start, found == std::string::npos ? found : found - start)));
		}
		catch (const std::invalid_argument& e)
		{
			return std::make_pair(-1, -1);
		}
		if (found == std::string::npos)
			break;
		start = found + 1;
	}

	if (vec.size() < 2 || vec[0] < 0 || vec[1] < 0)
	{
		return std::make_pair(-1, -1);
	}

	return std::make_pair(vec[0], vec[1]);
}

static std::string SizeToString(const std::pair<int, int>& size)
{
	return fmt::format("%dx%d", size.first, size.second);
}

static WindowInfo StringToWindowInfo(const std::string& str)
{
	std::size_t start = 0, found;
	std::vector<int> vec;
	for (int i = 0; i < 4 && (found = str.find_first_of("x:", start)); i++) {
		try {
			vec.push_back(std::stoi(str.substr(start, found == std::string::npos ? found : found - start)));
		}
		catch (const std::invalid_argument& e) {
			return WindowInfo();
		}
		if (found == std::string::npos)
			break;
		start = found + 1;
	}
	if (vec.size() < 4 || vec[0] <= 0 || vec[1] <= 0 || vec[2] < 0 || vec[3] < 0)
		return WindowInfo();

	return WindowInfo(std::make_pair(vec[0], vec[1]), std::make_pair(vec[2], vec[3]));
}

static std::string WindowInfoToString(const WindowInfo& wind)
{
	const int px = wind.position.first < -wind.size.first ? -1 : wind.position.first;
	const int py = wind.position.second < -wind.size.second ? -1 : wind.position.second;
	return fmt::format("%dx%d:%dx%d", wind.size.first, wind.size.second, px, py);
}

//Ini
Ini::Ini()
{
	m_config = getIniFile();
}

Ini::~Ini()
{
	saveIniFile();
}

//TODO: saving the file after each change seems like overkill but that's how wx did it
void Ini::Save(const std::string& section, const std::string& key, int value)
{
	static_cast<CSimpleIniCaseA*>(m_config)->SetLongValue(section.c_str(), key.c_str(), value);
	saveIniFile();
}

void Ini::Save(const std::string& section, const std::string& key, unsigned int value)
{
	static_cast<CSimpleIniCaseA*>(m_config)->SetLongValue(section.c_str(), key.c_str(), value);
	saveIniFile();
}

void Ini::Save(const std::string& section, const std::string& key, bool value)
{
	static_cast<CSimpleIniCaseA*>(m_config)->SetBoolValue(section.c_str(), key.c_str(), value);
	saveIniFile();
}

void Ini::Save(const std::string& section, const std::string& key, std::pair<int, int> value)
{
	static_cast<CSimpleIniCaseA*>(m_config)->SetValue(section.c_str(), key.c_str(), SizeToString(value).c_str());
	saveIniFile();
}

void Ini::Save(const std::string& section, const std::string& key, const std::string& value)
{
	static_cast<CSimpleIniCaseA*>(m_config)->SetValue(section.c_str(), key.c_str(), value.c_str());
	saveIniFile();
}

void Ini::Save(const std::string& section, const std::string& key, WindowInfo value)
{
	static_cast<CSimpleIniCaseA*>(m_config)->SetValue(section.c_str(), key.c_str(), WindowInfoToString(value).c_str());
	saveIniFile();
}

int Ini::Load(const std::string& section, const std::string& key, const int def_value)
{
	return static_cast<CSimpleIniCaseA*>(m_config)->GetLongValue(section.c_str(), key.c_str(), def_value);
}

unsigned int Ini::Load(const std::string& section, const std::string& key, const unsigned int def_value)
{
	return static_cast<CSimpleIniCaseA*>(m_config)->GetLongValue(section.c_str(), key.c_str(), def_value);
}

bool Ini::Load(const std::string& section, const std::string& key, const bool def_value)
{
	return StringToBool(static_cast<CSimpleIniCaseA*>(m_config)->GetValue(section.c_str(), key.c_str(), BoolToString(def_value).c_str()));
}

std::pair<int, int> Ini::Load(const std::string& section, const std::string& key, const std::pair<int, int> def_value)
{
	return StringToSize(static_cast<CSimpleIniCaseA*>(m_config)->GetValue(section.c_str(), key.c_str(), SizeToString(def_value).c_str()));
}

std::string Ini::Load(const std::string& section, const std::string& key, const std::string& def_value)
{
	return std::string(static_cast<CSimpleIniCaseA*>(m_config)->GetValue(section.c_str(), key.c_str(), def_value.c_str()));
}

WindowInfo Ini::Load(const std::string& section, const std::string& key, const WindowInfo& def_value)
{
	return StringToWindowInfo(static_cast<CSimpleIniCaseA*>(m_config)->GetValue(section.c_str(), key.c_str(), WindowInfoToString(def_value).c_str()));
}
