#include "ConfigFile.h"

ConfigFile::ConfigFile(std::string filename, std::string delimiter,
		std::string comment, std::string sentry) :
	myDelimiter(delimiter), myComment(comment), mySentry(sentry) {
	// Construct a ConfigFile, getting keys and values from given file

	std::ifstream in(filename.c_str());

	if (!in)
		throw file_not_found(filename);

	in >> (*this);
}

ConfigFile::ConfigFile() :
	myDelimiter(std::string(1, '=')), myComment(std::string(1, '#')) {
	// Construct a ConfigFile without a file; empty
}

void ConfigFile::remove(const std::string& key) {
	// Remove key and its value
	myContents.erase(myContents.find(key));
	return;
}

bool ConfigFile::keyExists(const std::string& key) const {
	// Indicate whether key is found
	mapci p = myContents.find(key);
	return (p != myContents.end());
}

/* static */
void ConfigFile::trim(std::string& s) {
	// Remove leading and trailing whitespace
	static const char whitespace[] = " \n\t\v\r\f";
	s.erase(0, s.find_first_not_of(whitespace));
	s.erase(s.find_last_not_of(whitespace) + 1U);
}

std::ostream& operator<<(std::ostream& os, const ConfigFile& cf) {
	// Save a ConfigFile to os
	for (ConfigFile::mapci p = cf.myContents.begin(); p != cf.myContents.end(); ++p) {
		os << p->first << " " << cf.myDelimiter << " ";
		os << p->second << std::endl;
	}
	return os;
}

std::istream& operator>>(std::istream& is, ConfigFile& cf) {
	// Load a ConfigFile from is
	// Read in keys and values, keeping internal whitespace
	typedef std::string::size_type pos;
	const std::string& delim = cf.myDelimiter; // separator
	const std::string& comm = cf.myComment; // comment
	const std::string& sentry = cf.mySentry; // end of file sentry
	const pos skip = delim.length(); // length of separator

	std::string nextline = ""; // might need to read ahead to see where value ends

	while (is || nextline.length() > 0) {
		// Read an entire line at a time
		std::string line;
		if (nextline.length() > 0) {
			line = nextline; // we read ahead; use it now
			nextline = "";
		} else {
			std::getline(is, line);
		}

		// Ignore comments
		line = line.substr(0, line.find(comm));

		// Check for end of file sentry
		if (sentry != "" && line.find(sentry) != std::string::npos)
			return is;

		// Parse the line if it contains a delimiter
		pos delimPos = line.find(delim);
		if (delimPos < std::string::npos) {
			// Extract the key
			std::string key = line.substr(0, delimPos);
			line.replace(0, delimPos + skip, "");

			// See if value continues on the next line
			// Stop at blank line, next line with a key, end of stream,
			// or end of file sentry
			bool terminate = false;
			while (!terminate && is) {
				std::getline(is, nextline);
				terminate = true;

				std::string nlcopy = nextline;
				ConfigFile::trim(nlcopy);
				if (nlcopy == "")
					continue;

				nextline = nextline.substr(0, nextline.find(comm));
				if (nextline.find(delim) != std::string::npos)
					continue;
				if (sentry != "" && nextline.find(sentry) != std::string::npos)
					continue;

				nlcopy = nextline;
				ConfigFile::trim(nlcopy);
				if (nlcopy != "")
					line += "\n";
				line += nextline;
				terminate = false;
			}

			// Store key and value
			ConfigFile::trim(key);
			ConfigFile::trim(line);
			cf.myContents[key] = line; // overwrites if key is repeated
		}
	}

	return is;
}
