// Read an INI file into easy-to-access name/value pairs.

// inih and INIReader are released under the New BSD license:
//
// Copyright (c) 2009, Ben Hoyt
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * 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.
//     * Neither the name of Ben Hoyt nor the names of its contributors
//       may be used to endorse or promote products derived from this software
//       without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY BEN HOYT ''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 BEN HOYT 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.
//
// Go to the project home page for more info: https://github.com/benhoyt/inih

#include <algorithm>
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <istream>
#include "ini.h"
#include "inireader.h"

using std::string;

INIReader::INIReader(const string& filename)
{
	_error = ini_parse(filename.c_str(), ValueHandler, this);
}

INIReader::INIReader(std::istream& filestream)
{
	_error = ini_parse_stream([](char* str, int num, void* stream) {
		std::istream* is = reinterpret_cast<std::istream*>(stream);
		if (num > 0) {
			// via https://stackoverflow.com/questions/6089231/
			std::string out;

			std::istream::sentry se(*is, true);
			std::streambuf* sb = is->rdbuf();

			bool loop = true;

			do {
				int c = sb->sbumpc();
				switch (c) {
					case '\n':
						loop = false;
						break;
					case '\r':
						if (sb->sgetc() == '\n') {
							sb->sbumpc();
						}
						loop = false;
						break;
					case EOF:
						// Also handle the case when the last line has no line ending
						if (out.empty()) {
							is->setstate(std::ios::eofbit);
						}
						loop = false;
						break;
					default:
						out += (char)c;
				}
			} while (loop);

			if (out.empty() && (is->fail() || is->eof())) {
				return (char*)nullptr;
			}

			strncpy(str, out.c_str(), num);
			str[num - 1] = '\0';

			return str;
		}

		return (char*)nullptr;
	}, &filestream, ValueHandler, this);
}

int INIReader::ParseError() const
{
	return _error;
}

string INIReader::Get(const string& section, const string& name, const string& default_value) const
{
	string key = MakeKey(section, name);
	// Use _values.find() here instead of _values.at() to support pre C++11 compilers
	return _values.count(key) ? _values.find(key)->second : default_value;
}

string INIReader::GetString(const string& section, const string& name, const string& default_value) const
{
	const string str = Get(section, name, "");
	return str.empty() ? default_value : str;
}

long INIReader::GetInteger(const string& section, const string& name, long default_value) const
{
	string valstr = Get(section, name, "");
	const char* value = valstr.c_str();
	char* end;
	// This parses "1234" (decimal) and also "0x4D2" (hex)
	long n = strtol(value, &end, 0);
	return end > value ? n : default_value;
}

double INIReader::GetReal(const string& section, const string& name, double default_value) const
{
	string valstr = Get(section, name, "");
	const char* value = valstr.c_str();
	char* end;
	double n = strtod(value, &end);
	return end > value ? n : default_value;
}

bool INIReader::GetBoolean(const string& section, const string& name, bool default_value) const
{
	string valstr = Get(section, name, "");
	// Convert to lower case to make string comparisons case-insensitive
	std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower);
	if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1")
		return true;
	else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0")
		return false;
	else
		return default_value;
}

bool INIReader::HasValue(const std::string& section, const std::string& name) const
{
	string key = MakeKey(section, name);
	return _values.count(key);
}

string INIReader::MakeKey(const string& section, const string& name)
{
	string key = section + "=" + name;
	// Convert to lower case to make section/name lookups case-insensitive
	std::transform(key.begin(), key.end(), key.begin(), ::tolower);
	return key;
}

int INIReader::ValueHandler(void* user, const char* section, const char* name,
							const char* value)
{
	INIReader* reader = static_cast<INIReader*>(user);
	string key = MakeKey(section, name);
	if (reader->_values[key].size() > 0)
		reader->_values[key] += "\n";
	reader->_values[key] += value;
	return 1;
}
