/* vim: set noexpandtab shiftwidth=8 cino=:
 ***************************************************************************
 *            configfile.cc
 *
 *  Sun Oct  2 18:34:08 2005
 *  Copyright  2005  Joe Venzon
 *  joe@venzon.net
 ****************************************************************************/

/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Library General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
 
#include "configfile.h"
#include <stdexcept>

CONFIGFILE::CONFIGFILE()
{
	//vars = NULL;
	filename = "";
	SUPPRESS_ERROR = false;
}

CONFIGFILE::CONFIGFILE(string fname)
{
	//vars = NULL;
	
	SUPPRESS_ERROR = false;
	
	Load(fname);
}

CONFIGFILE::~CONFIGFILE()
{
	Clear();
}

bool CONFIGFILE::GetParam(string param, int & outvar)
{
	string::size_type ppos;
	ppos = param.find(".", 0);
	if (ppos < param.length())
	{
		if (param.substr(0, ppos).empty())
		{
			ppos++;
			param = param.substr(ppos, param.length() - ppos);
		}
	}
	
	CONFIGVARIABLE * v = variables.Get(param);
	
	if (!v)
		return false;
	
	outvar = v->val_i;
	
	return true;
}

bool CONFIGFILE::GetParam(string param, bool & outvar)
{
	string::size_type ppos;
	ppos = param.find(".", 0);
	if (ppos < param.length())
	{
		if (param.substr(0, ppos).empty())
		{
			ppos++;
			param = param.substr(ppos, param.length() - ppos);
		}
	}
	
	CONFIGVARIABLE * v = variables.Get(param);
	
	if (!v)
		return false;
	
	outvar = v->val_b;
	
	return true;
}

bool CONFIGFILE::GetParam(string param, float & outvar)
{
	string::size_type ppos;
	ppos = param.find(".", 0);
	if (ppos < param.length())
	{
		if (param.substr(0, ppos).empty())
		{
			ppos++;
			param = param.substr(ppos, param.length() - ppos);
		}
	}
	
	CONFIGVARIABLE * v = variables.Get(param);
	
	if (!v)
		return false;
	
	outvar = v->val_f;
	
	return true;
}

bool CONFIGFILE::GetParam(string param, float * outvar)
{
	string::size_type ppos;
	ppos = param.find(".", 0);
	if (ppos < param.length())
	{
		if (param.substr(0, ppos).empty())
		{
			ppos++;
			param = param.substr(ppos, param.length() - ppos);
		}
	}
	
	CONFIGVARIABLE * v = variables.Get(param);
	
	if (!v)
		return false;
	
	for (int i = 0; i < 3; i++)
		outvar[i] = v->val_v[i];
	
	return true;
}

bool CONFIGFILE::GetParam(string param, string & outvar)
{
	string::size_type ppos;
	ppos = param.find(".", 0);
	if (ppos < param.length())
	{
		if (param.substr(0, ppos).empty())
		{
			ppos++;
			param = param.substr(ppos, param.length() - ppos);
		}
	}
	
	CONFIGVARIABLE * v = variables.Get(param);
	
	if (!v)
		return false;
	
	outvar = v->val_s;
	
	return true;
}

void CONFIGFILE::Clear()
{
	variables.Clear();
}

void CONFIGFILE::Add(string & paramname, CONFIGVARIABLE & newvar)
{
	variables.Set(paramname, newvar);
}

CONFIGVARIABLE::CONFIGVARIABLE()
{
	val_s = "";
	val_i = 0;
	val_f = 0;
	val_b = false;
	int i;
	for (i = 0; i < 3; i++)
		val_v[i] = 0;
	
	next = NULL;
}

const string CONFIGVARIABLE::GetFullName() const
{
	string outstr = "";
	
	if (section != "")
		outstr = outstr + section + ".";
	outstr = outstr + name;
	
	return outstr;
}

void CONFIGVARIABLE::Set(string newval)
{
	newval = strTrim(newval);
	
	val_i = atoi(newval.c_str());
	val_f = atof(newval.c_str());
	val_s = newval;
	
	val_b = false;
	if (val_i == 0)
		val_b = false;
	if (val_i == 1)
		val_b = true;
	if (strLCase(newval) == "true")
		val_b = true;
	if (strLCase(newval) == "false")
		val_b = false;
	if (strLCase(newval) == "on")
		val_b = true;
	if (strLCase(newval) == "off")
		val_b = false;
	
	//now process as vector information
	int pos = 0;
	int arraypos = 0;
	string::size_type nextpos = newval.find(",", pos);
	string frag;
	
	while (nextpos < /*(int)*/ newval.length() && arraypos < 3)
	{
		frag = newval.substr(pos, nextpos - pos);
		val_v[arraypos] = atof(frag.c_str());
		
		pos = nextpos+1;
		arraypos++;
		nextpos = newval.find(",", pos);
	}
	
	//don't forget the very last one
	if (arraypos < 3)
	{
		frag = newval.substr(pos, newval.length() - pos);
		val_v[arraypos] = atof(frag.c_str());
	}
}

void CONFIGVARIABLE::DebugPrint()
{
	if (section != "")
		cout << section << ".";
	cout << name << endl;
	cout << "string: " << val_s << endl;
	cout << "int: " << val_i << endl;
	cout << "float: " << val_f << endl;
	cout << "vector: (" << val_v[0] << "," << val_v[1] << "," << val_v[2] << ")" << endl;
	cout << "bool: " << val_b << endl;
	
	cout << endl;
}

string CONFIGVARIABLE::strLTrim(string instr)
{
	return instr.erase(0, instr.find_first_not_of(" \t"));
}

string CONFIGVARIABLE::strRTrim(string instr)
{
	try {
		return instr.erase(instr.find_last_not_of(" \t") + 1);
	} catch(std::out_of_range) {
		return instr;
	}
}

string CONFIGVARIABLE::strTrim(string instr)
{
	return strLTrim(strRTrim(instr));
}

void CONFIGFILE::Load(string fname)
{
	filename = fname;
	
	string cursection = "";
	
	//work string
	string ws;
	const int MAXIMUMCHAR = 1024;
	char trashchar[MAXIMUMCHAR];
	
	ifstream f;
	f.open(fname.c_str());
	
	if (!f && !SUPPRESS_ERROR)
	{
		cout << "CONFIGFILE.Load: Couldn't find file:  " << fname << endl;
	}
		
	while (f && !f.eof())
	{
		f.getline(trashchar, MAXIMUMCHAR, '\n');
		ProcessLine(cursection, trashchar);
	}
	
	f.close();
	
	//DebugPrint();
}

string CONFIGFILE::Trim(string instr)
{
	CONFIGVARIABLE trimmer;
	string outstr = trimmer.strTrim(instr);
	return outstr;
}

void CONFIGFILE::ProcessLine(string & cursection, string linestr)
{
	linestr = Trim(linestr);
	linestr = Strip(linestr, '\r');
	linestr = Strip(linestr, '\n');
	
	//remove comments
	string::size_type commentpos = linestr.find("#", 0);
	if (commentpos < /*(int)*/ linestr.length())
	{
		linestr = linestr.substr(0, commentpos);
	}
	
	linestr = Trim(linestr);
	
	//only continue if not a blank line or comment-only line
	if (linestr.length() > 0)
	{
		if (linestr.find("=", 0) < linestr.length())
		{
			//find the name part
			string::size_type equalpos = linestr.find("=", 0);
			string name = linestr.substr(0, equalpos);
			equalpos++;
			string val = linestr.substr(equalpos, linestr.length() - equalpos);
			name = Trim(name);
			val = Trim(val);
			
			//only continue if valid
			if (name.length() > 0 && val.length() > 0)
			{
				CONFIGVARIABLE newvar;
				//newvar = new CONFIGVARIABLE;
				newvar.section = cursection;
				newvar.name = name;
				newvar.Set(val);
				
				string paramname = name;
				if (!cursection.empty())
					paramname = cursection + "." + paramname;
				
				Add(paramname, newvar);
			}
		}
		else
		{
			//section header
			linestr = Strip(linestr, '[');
			linestr = Strip(linestr, ']');
			linestr = Trim(linestr);
			cursection = linestr;
		}
	}
}

string CONFIGFILE::Strip(string instr, char stripchar)
{
	string::size_type pos = 0;
	string outstr = "";
	
	while (pos < /*(int)*/ instr.length())
	{
		if (instr.c_str()[pos] != stripchar)
			outstr = outstr + instr.substr(pos, 1);
		
		pos++;
	}
	
	return outstr;
}

void CONFIGFILE::DebugPrint()
{
	/*CONFIGVARIABLE * cur = vars;
	cout << "*** " << filename << " ***" << endl << endl;
	while (cur != NULL)
	{
		cur->DebugPrint();
		
		cur = cur->next;
	}*/
	
	cout << "*** " << filename << " ***" << endl << endl;
	
	list <CONFIGVARIABLE> vlist;
	
	for (variables.IteratorReset(); variables.IteratorGet() != NULL; variables.IteratorIncrement())
	{
		//cout << incsuccess << endl;
		//variables.IteratorGet()->DebugPrint();
		vlist.push_back(*(variables.IteratorGet()));
	}
	
	vlist.sort();
	
	for (list <CONFIGVARIABLE>::iterator i = vlist.begin(); i != vlist.end(); i++)
	{
		i->DebugPrint();
	}
}

string CONFIGVARIABLE::strLCase(string instr)
{
	char tc[2];
	tc[1] = '\0';
	string outstr = "";
	
	string::size_type pos = 0;
	while (pos < /*(int)*/ instr.length())
	{
		if (instr.c_str()[pos] <= 90 && instr.c_str()[pos] >= 65)
		{
			tc[0] = instr.c_str()[pos] + 32;
			string tstr = tc;
			outstr = outstr + tc;
		}
		else
			outstr = outstr + instr.substr(pos, 1);
		
		pos++;
	}
	
	return outstr;
}

string CONFIGFILE::LCase(string instr)
{
	CONFIGVARIABLE lcaser;
	string outstr = lcaser.strLCase(instr);
	return outstr;
}

bool CONFIGFILE::SetParam(string param, int invar)
{
	char tc[256];
	
	sprintf(tc, "%i", invar);
	
	string tstr = tc;
	
	return SetParam(param, tstr);
}

bool CONFIGFILE::SetParam(string param, bool invar)
{
	//char tc[256];
	
	//sprintf(tc, "%i", invar);
	
	string tstr = "off";
	
	if (invar)
		tstr = "on";
	
	return SetParam(param, tstr);
}

bool CONFIGFILE::SetParam(string param, float invar)
{
	char tc[256];
	
	sprintf(tc, "%f", invar);
	
	string tstr = tc;
	
	return SetParam(param, tstr);
}

bool CONFIGFILE::SetParam(string param, float * invar)
{
	char tc[256];
	
	sprintf(tc, "%f,%f,%f", invar[0], invar[1], invar[2]);
	
	string tstr = tc;
	
	return SetParam(param, tstr);
}

bool CONFIGFILE::SetParam(string param, string invar)
{
	CONFIGVARIABLE newvar;
	
	newvar.name = param;
	newvar.section = "";
	string::size_type ppos;
	ppos = param.find(".", 0);
	if (ppos < param.length())
	{
		newvar.section = param.substr(0, ppos);
		ppos++;
		newvar.name = param.substr(ppos, param.length() - ppos);
	}
	
	newvar.Set(invar);
	
	Add(param, newvar);
	
	return true;
}

bool CONFIGFILE::Write(bool with_brackets)
{
	return Write(with_brackets, filename);
}

bool CONFIGFILE::Write(bool with_brackets, string save_as)
{
	ofstream f;
	f.open(save_as.c_str());
	
	if (f)
	{
		//clear out written flags
		/*CONFIGVARIABLE * cur = vars;
		while (cur != NULL)
		{
			cur->written = false;
			cur = cur->next;
		}
		
		//write non-section variables first
		bool nosection = false;
		cur = vars;
		while (cur != NULL)
		{
			if (cur->section == "")
			{
				f << cur->name << " = " << cur->val_s << endl;
				
				nosection = true;
				
				cur->written = true;
			}
			
			cur = cur->next;
		}
		
		if (nosection)
			f << endl;
		
		//write variables by section
		cur = vars;
		while (cur != NULL)
		{
			if (!cur->written)
			{
				if (with_brackets)
					f << "[ " << cur->section << " ]" << endl;
				else
					f << cur->section << endl;
				
				CONFIGVARIABLE * sub = vars;
				while (sub != NULL)
				{
					if (!sub->written && cur->section == sub->section)
					{
						f << sub->name << " = " << sub->val_s << endl;
						
						sub->written = true;
					}
					
					sub = sub->next;
				}
				
				f << endl;
				
				cur->written = true;
			}
			
			cur = cur->next;
		}
		*/
		
		list <CONFIGVARIABLE> vlist;
	
		for (variables.IteratorReset(); variables.IteratorGet() != NULL; variables.IteratorIncrement())
		{
			//cout << incsuccess << endl;
			//variables.IteratorGet()->DebugPrint();
			vlist.push_back(*(variables.IteratorGet()));
		}
		
		vlist.sort();
		
		string cursection = "";
		for (list <CONFIGVARIABLE>::iterator cur = vlist.begin(); cur != vlist.end(); cur++)
		{
			if (cur->section == "")
			{
				f << cur->name << " = " << cur->val_s << endl;
			}
			else
			{
				if (cur->section != cursection)
				{
					f << endl;
					cursection = cur->section;
					
					if (with_brackets)
						f << "[ " << cur->section << " ]" << endl;
					else
						f << cur->section << endl;
				}
				
				f << cur->name << " = " << cur->val_s << endl;
			}
		}
		
		f.close();
		return true;
	}
	else
		return false;
}

bool CONFIGFILE::Write()
{
	Write(true);
	return true;
}

bool CONFIGFILE::ClearParam(string param)
{
	/*CONFIGVARIABLE * cur = vars;
	while (cur != NULL)
	{
		CONFIGVARIABLE * todel = cur;
		cur = cur->next;
		
		delete todel;
	}
	
	vars = NULL;*/
	
	/*CONFIGVARIABLE * cur = vars;
	CONFIGVARIABLE * prev = vars;
	bool found = false;
	while (cur != NULL)
	{
		//if the var section/name matches the param name, set outvar
		
		string pname = param;
		string psec = "";
		string::size_type ppos;
		ppos = param.find(".", 0);
		if (ppos < param.length())
		{
			psec = param.substr(0, ppos);
			ppos++;
			pname = param.substr(ppos, param.length() - ppos);
		}
		
		if (pname == cur->name && psec == cur->section)
		{
			found = true;

			CONFIGVARIABLE * todel = cur;
			if (cur == vars)
			{
				vars = vars->next;
				prev = vars;
				cur = vars;
			}
			else
			{
				cur = cur->next;
				prev->next = cur;
			}
			
			delete todel;
		}
		else
		{		
			prev = cur;
			cur = cur->next;
		}
	}
	*/

	/*	
	if (!found && !SUPPRESS_ERROR)
	{
		cout << "CONFIGFILE::GetParam():  " << param << " not found in " << filename << endl;
	}*/
	
	CONFIGVARIABLE * cur = variables.Get(param);
	if (!cur)
		return false;
	
	variables.Erase(cur);
	
	return true;
}

void CONFIGFILE::GetSectionList(list <string> & sectionlistoutput)
{
	/*sectionlistoutput.clear();
	map <string, bool> templist;
	CONFIGVARIABLE * cur = vars;
	while (cur != NULL)
	{
		templist[cur->section] = true;
		
		cur = cur->next;
	}
	
	for (map <string, bool>::iterator i = templist.begin(); i != templist.end(); i++)
	{
		sectionlistoutput.push_back(i->first);
	}*/
	
	sectionlistoutput.clear();
	map <string, bool> templist;
	for (variables.IteratorReset(); variables.IteratorGet() != NULL; variables.IteratorIncrement())
	{
		templist[variables.IteratorGet()->section] = true;
	}
	
	for (map <string, bool>::iterator i = templist.begin(); i != templist.end(); i++)
	{
		sectionlistoutput.push_back(i->first);
	}
}

void CONFIGFILE::GetParamList(list <string> & paramlistoutput, string section)
{
	/*bool all = false;
	if (section == "")
		all = true;
	
	paramlistoutput.clear();
	map <string, bool> templist;
	CONFIGVARIABLE * cur = vars;
	while (cur != NULL)
	{
		if (all)
			templist[section+"."+cur->name] = true;
		else if (cur->section == section)
			templist[cur->name] = true;
		
		cur = cur->next;
	}
	
	for (map <string, bool>::iterator i = templist.begin(); i != templist.end(); i++)
	{
		paramlistoutput.push_back(i->first);
	}*/
	
	bool all = false;
	if (section == "")
		all = true;
	
	paramlistoutput.clear();
	map <string, bool> templist;
	for (variables.IteratorReset(); variables.IteratorGet() != NULL; variables.IteratorIncrement())
	{
		if (all)
			templist[section+"."+variables.IteratorGet()->name] = true;
		else if (variables.IteratorGet()->section == section)
			templist[variables.IteratorGet()->name] = true;
	}
	
	for (map <string, bool>::iterator i = templist.begin(); i != templist.end(); i++)
	{
		paramlistoutput.push_back(i->first);
	}
}

CONFIGVARIABLE & CONFIGVARIABLE::CopyFrom(const CONFIGVARIABLE & other)
{
	/*
	string section;
	string name;

	string val_s;
	int val_i;
	float val_f;
	float val_v[3];
	bool val_b;
	*/
	
	section = other.section;
	name = other.name;
	val_s = other.val_s;
	val_i = other.val_i;
	val_f = other.val_f;
	val_b = other.val_b;
	
	for (int i = 0; i < 3; i++)
		val_v[i] = other.val_v[i];
	
	return *this;
}

bool CONFIGVARIABLE::operator<(const CONFIGVARIABLE & other)
{
	//return GetFullName() < other.GetFullName();
	return (section + "." + name < other.section + "." + other.name);
}
