/*
    PIP - Platform Independent Primitives
    Config parser
    Copyright (C) 2014  Ivan Pelipenko peri4ko@gmail.com

    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 3 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "piconfig.h"

/*! \class PIConfig
 * \brief Configuration file
 * \details This class provide handle access to configuration file.
 * 
 * \section PIConfig_sec0 Synopsis
 * PIConfig reads configuration file and create internal dendritic
 * representation of all entries of this file. You can easily read
 * some values and write new.
 * \image html piconfig.png
 * 
 * %PIConfig supports also INI-style files with sections "[section]".
 * In this case line with section name interpret as prefix to the next
 * lines. For example, these configs are equal:
 * \code
 * ser.device = /dev/ttyS0
 * ser.speed = 115200
 * debug = true
 * \endcode
 * \code
 * [ser]
 * device = /dev/ttyS0
 * speed = 115200
 * []
 * debug = true
 * \endcode
 * 
 * \section PIConfig_sec1 Concepts
 * Each node of internal tree has type PIConfig::Entry. %PIConfig
 * has one root element \a rootEntry(). Any entry of configuration file is a
 * child of this element.
 * 
 */

/*! \class PIConfig::Entry
 * \brief %Entry of configuration file
 * \details This class is node of internal PIConfig tree.
 * %Entry provide access to elements of PIConfig. Each entry has
 * children or next properties:
 * * name
 * * value
 * * type
 * * comment
 * 
 * Each property is a PIString. These properties forms from text line with
 * format: \code{.cpp} <name> = <value> #<type> <comment> \endcode
 * Type and comment are optional fields. Type is a single letter immediately
 * after comment symbol "#". \n \n
 * %Entry has many implicit convertions to common types: boolean, integers,
 * float, double, PIString, PIStringList. \n \n
 * Generally there is no need to create instance of %PIConfig::Entry manually,
 * it returns by functions \a getValue() of \a PIConfig, \a PIConfig::Entry or
 * \a PIConfig::Branch. If  there is no suitable entry to return, reference to
 * internal instance of %PIConfig::Entry with "default" value will be returned.
 * \snippet piconfig.cpp PIConfig::Entry
 * 
*/

/*! \class PIConfig::Branch
 * \brief %Branch is a list of entries of configuration file
 * \details %Branch provides some features to get entries lists.
 * \snippet piconfig.cpp PIConfig::Branch
 * 
*/


PIConfig::Entry PIConfig::Branch::_empty;
PIConfig::Entry PIConfig::Entry::_empty;


PIConfig::Branch PIConfig::Branch::allLeaves() {
	Branch b;
	b.delim = delim;
	piForeach (Entry * i, *this) {
		if (i->isLeaf()) b << i;
		else allLeaves(b, i);
	}
	return b;
}


PIConfig::Entry & PIConfig::Branch::getValue(const PIString & vname, const PIString & def, bool * exist) {
	if (vname.isEmpty()) {
		_empty.clear();
		_empty.delim = delim;
		if (exist != 0) *exist = false;
		return _empty;
	}
	PIStringList tree = vname.split(delim);
	PIString name = tree.front();
	tree.pop_front();
	Entry * ce = 0;
	piForeach (Entry * i, *this)
		if (i->_name == name) {
			ce = i;
			break;
		}
	if (ce == 0) {
		_empty._name = vname;
		_empty._value = def;
		_empty.delim = delim;
		if (exist != 0) *exist = false;
		return _empty;
	}
	piForeach (PIString & i, tree) {
		ce = ce->findChild(i);
		if (ce == 0) {
			_empty._name = vname;
			_empty._value = def;
			_empty.delim = delim;
			if (exist != 0) *exist = false;
			return _empty;
		}
	}
	if (exist != 0) *exist = true;
	return *ce;
}


PIConfig::Branch PIConfig::Branch::getValues(const PIString & name) {
	Branch b;
	b.delim = delim;
	piForeach (Entry * i, *this) {
		if (i->isLeaf()) {
			if (i->_name.find(name) >= 0)
				b << i;
		} else {
			piForeach (Entry * j, i->_children)
				if (j->_name.find(name) >= 0)
					b << j;
		}
	}
	return b;
}


PIConfig::Branch PIConfig::Branch::getLeaves() {
	Branch b;
	b.delim = delim;
	piForeach (Entry * i, *this)
		if (i->isLeaf())
			b << i;
	return b;
}


PIConfig::Branch PIConfig::Branch::getBranches() {
	Branch b;
	b.delim = delim;
	piForeach (Entry * i, *this)
		if (!i->isLeaf())
			b << i;
	return b;
}


PIConfig::Branch & PIConfig::Branch::filter(const PIString & f) {
	for (int i = 0; i < size_s(); ++i) {
		if (at(i)->_name.find(f) < 0) {
			remove(i);
			--i;
		}
	}
	return *this;
}


bool PIConfig::Branch::entryExists(const Entry * e, const PIString & name) const {
	if (e->_children.isEmpty()) {
		if  (e->_name == name) return true;
		else return false;
	}
	piForeachC (Entry * i, e->_children)
		if (entryExists(i, name)) return true;
	return false;
}


PIConfig::Entry & PIConfig::Entry::getValue(const PIString & vname, const PIString & def, bool * exist) {
	PIStringList tree = vname.split(delim);
	Entry * ce = this;
	piForeach (PIString & i, tree) {
		ce = ce->findChild(i);
		if (ce == 0) {
			_empty._name = vname;
			_empty._value = def;
			_empty.delim = delim;
			if (exist != 0) *exist = false;
			return _empty;
		}
	}
	if (exist != 0) *exist = true;
	return *ce;
}


PIConfig::Branch PIConfig::Entry::getValues(const PIString & vname) {
	Branch b;
	b.delim = delim;
	piForeach (Entry * i, _children)
		if (i->_name.find(vname) >= 0)
			b << i;
	return b;
};


bool PIConfig::Entry::entryExists(const Entry * e, const PIString & name) const {
	if (e->_children.isEmpty()) {
		if (e->_name == name) return true;
		else return false;
	}
	piForeachC (Entry * i, e->_children)
		if (entryExists(i, name)) return true;
	return false;
}


PIConfig::PIConfig(const PIString & path, PIIODevice::DeviceMode mode): PIFile(path, mode) {
	delim = ".";
	root.delim = delim;
	empty.delim = delim;
	empty._parent = 0;
	if (!isOpened())
		open(path, mode);
	parse();
}


PIConfig::Entry & PIConfig::getValue(const PIString & vname, const PIString & def, bool * exist) {
	PIStringList tree = vname.split(delim);
	Entry * ce = &root;
	piForeach (PIString & i, tree) {
		ce = ce->findChild(i);
		if (ce == 0) {
			if (exist != 0) *exist = false;
			empty._name = vname;
			empty._value = def;
			empty.delim = delim;
			return empty;
		}
	}
	if (exist != 0) *exist = true;
	return *ce;
}


PIConfig::Branch PIConfig::getValues(const PIString & vname) {
	Branch b;
	b.delim = delim;
	piForeach (Entry * i, root._children)
		if (i->_name.find(vname) >= 0)
			b << i;
	return b;
};


void PIConfig::addEntry(const PIString & name, const PIString & value, const PIString & type, bool write) {
	if (getValue(name)._parent != 0)
		return;
	bool toRoot = false;
	PIStringList tree = name.split(delim);
	PIString ename = tree.back();
	tree.pop_back();
	Entry * te, * ce, * entry = &root;
	if (tree.isEmpty()) toRoot = true;
	piForeach (PIString & i, tree) {
		te = entry->findChild(i);
		if (te == 0) {
			ce = new Entry();
			ce->delim = delim;
			ce->_tab = entry->_tab;
			ce->_line = entry->_line;
			ce->_name = i;
			ce->_parent = entry;
			entry->_children << ce;
			entry = ce;
		} else entry = te;
	}
	PIConfig::Branch ch = entry->_children;
	ch.sort(PIConfig::Entry::compare);
	te = (entry->isLeaf() ? 0 : ch.back());
	ce = new Entry();
	ce->delim = delim;
	ce->_name = ename;
	ce->_value = value;
	ce->_type = type;
	if (te == 0) {
		ce->_tab = entry->_tab;
		if (toRoot) ce->_line = other.size_s() - 1;
		else ce->_line = entry->_line;
	} else {
		ce->_tab = te->_tab;
		if (toRoot) ce->_line = other.size_s() - 1;
		else {
			ch = entry->_parent->_children;
			ch.sort(PIConfig::Entry::compare);
			ce->_line = ch.back()->_line + 1;
		}
	}
	ce->_parent = entry;
	entry->_children << ce;
	other.insert(ce->_line, "");
	Branch b = allLeaves();
	bool found = false;
	for (int i = 0; i < b.size_s(); ++i) {
		if (found) {
			b[i]->_line++;
			continue;
		}
		if (b[i] == ce) {
			found = true;
			if (i > 0)
				if (b[i - 1]->_line == b[i]->_line)
					b[i - 1]->_line++;
		}
	}
	if (write) writeAll();
}


void PIConfig::setValue(const PIString & name, const PIString & value, const PIString & type, bool write) {
	Entry & e(getValue(name));
	if (&e == &empty) {
		addEntry(name, value, type, write);
		return;
	}
	e._value = value;
	e._type = type;
	if (write) writeAll();
}


int PIConfig::entryIndex(const PIString & name) {
	PIStringList tree = name.split(delim);
	Entry * ce = &root;
	piForeach (PIString & i, tree) {
		ce = ce->findChild(i);
		if (ce == 0)
			return -1;
	}
	Branch b = allLeaves();
	return allLeaves().indexOf(ce);
}


void PIConfig::setValue(uint number, const PIString & value, bool write) {
	Entry & e(entryByIndex(number));
	if (&e == &empty) return;
	e._value = value;
	if (write) writeAll();
}


void PIConfig::setName(uint number, const PIString & name, bool write) {
	Entry & e(entryByIndex(number));
	if (&e == &empty) return;
	e._name = name;
	if (write) writeAll();
}


void PIConfig::setType(uint number, const PIString & type, bool write) {
	Entry & e(entryByIndex(number));
	if (&e == &empty) return;
	e._type = type;
	if (write) writeAll();
}


void PIConfig::setComment(uint number, const PIString & comment, bool write) {
	Entry & e(entryByIndex(number));
	if (&e == &empty) return;
	e._comment = comment;
	if (write) writeAll();
}


void PIConfig::removeEntry(const PIString & name, bool write) {
	Entry & e(getValue(name));
	if (&e == &empty) return;
	Branch b = allLeaves();
	removeEntry(b, &e);
	if (write) writeAll();
}


void PIConfig::removeEntry(uint number, bool write) {
	Entry & e(entryByIndex(number));
	if (&e == &empty) return;
	Branch b = allLeaves();
	removeEntry(b, &e);
	if (write) writeAll();
}


void PIConfig::removeEntry(Branch & b, PIConfig::Entry * e) {
	bool leaf = true;
	if (e->isLeaf()) other.remove(e->_line);
	if (!e->isLeaf() && !e->_value.isEmpty()) {
		e->_value.clear();
		leaf = false;
	} else {
		int cc = e->_children.size_s();
		piForTimes (cc)
			removeEntry(b, e->_children.back());
	}
	bool found = false;
	for (int i = 0; i < b.size_s(); ++i) {
		if (found) {
			b[i]->_line--;
			continue;
		}
		if (b[i] == e) found = true;
	}
	if (!leaf) return;
	e->_parent->_children.removeOne(e);
	b.removeOne(e);
	delete e;
}


PIString PIConfig::getPrefixFromLine(PIString line, bool * exists) {
	line.trim();
	if (line.left(1) == "#") {if (exists) *exists = false; return PIString();}
	int ci = line.find("#");
	if (ci >= 0) line.cutRight(line.size() - ci);
	if (line.find("=") >= 0) {if (exists) *exists = false; return PIString();}
	if (line.find("[") >= 0 && line.find("]") >= 0) {
		if (exists) *exists = true;
		return line.takeRange('[', ']').trim();
	}
	if (exists) *exists = false;
	return PIString();
}


void PIConfig::writeAll() {
	//cout << this << " write < " << size() << endl;
	PIFile::clear();
	//*this << "1234567894132456798\n"; return;
	//writeEntry(&root);
	buildFullNames(&root);
	Branch b = allLeaves();
	PIString prefix, tprefix;
	bool isPrefix;
	//for (int i = 0; i < b.size_s(); ++i)
	//	cout << b[i]->_name << " = " << b[i]->_value << endl;
	int j = 0;
	for (int i = 0; i < other.size_s(); ++i) {
		//cout << j << endl;
		if (j >= 0 && j < b.size_s()) {
			if (b[j]->_line == i) {
				b[j]->buildLine();
				*this << (b[j]->_all).cutLeft(prefix.size()) << '\n';
	//cout << this << " " << b[j]->_all << endl;
				++j;
			} else {
				*this << other[i];
				tprefix = getPrefixFromLine(other[i], &isPrefix);
				if (isPrefix) {
					prefix = tprefix;
					if (!prefix.isEmpty())
						prefix += delim;
				}
				if (i < other.size_s() - 1) *this << '\n';
	//cout << this << " " << other[i] << endl;
			}
		} else {
			*this << other[i];
			tprefix = getPrefixFromLine(other[i], &isPrefix);
			if (isPrefix) {
				prefix = tprefix;
				if (!prefix.isEmpty())
					prefix += delim;
			}
			if (i < other.size_s() - 1) *this << '\n';
	//cout << this << " " << other[i] << endl;
		}
	}
	flush();
	readAll();
	//cout << this << " write > " << size() << endl;
}


void PIConfig::readAll() {
	root.deleteBranch();
	root.clear();
	parse();
}


bool PIConfig::entryExists(const Entry * e, const PIString & name) const {
	if (e->_children.isEmpty()) {
		if  (e->_name == name) return true;
		else return false;
	}
	piForeachC (Entry * i, e->_children)
		if (entryExists(i, name)) return true;
	return false;
}


void PIConfig::parse() {
	PIString src, str, tab, comm, all, name, type, prefix, tprefix;
	PIStringList tree;
	Entry * entry, * te, * ce;
	int ind, sind;
	bool isNew, isPrefix;
	if (!isOpened()) return;
	seekToBegin();
	other.clear();
	lines = centry = 0;
	while (!isEnd()) {
		other.push_back(PIString());
		src = str = readLine();
		tprefix = getPrefixFromLine(src, &isPrefix);
		if (isPrefix) {
			prefix = tprefix;
			if (!prefix.isEmpty())
				prefix += delim;
		}
		tab = str.left(str.find(str.trimmed().left(1)));
		str.trim();
		//cout << endl << str << endl << endl;
		all = str;
		ind = str.find('=');
		if ((ind > 0) && !(str[0] == '#')) {
			sind = str.find('#');
			if (sind > 0) {
				comm = str.right(str.length() - sind - 1).trimmed();
				if (comm.length() > 0) type = comm[0];
				else type = "s";
				comm = comm.right(comm.length() - 1).trimmed();
				str = str.left(sind);
			} else {
				type = "s";
				comm = "";
			}
			//name = str.left(ind).trimmed();
			tree = (prefix + str.left(ind).trimmed()).split(delim);
			name = tree.back();
			tree.pop_back();
			entry = &root;
			piForeachC (PIString & i, tree) {
				te = entry->findChild(i);
				if (te == 0) {
					ce = new Entry();
					ce->delim = delim;
					ce->_tab = tab;
					ce->_line = lines;
					ce->_name = i;
					ce->_parent = entry;
					entry->_children << ce;
					entry = ce;
				} else entry = te;
			}
			isNew = false;
			ce = entry->findChild(name);
			if (ce == 0) {
				ce = new Entry();
				isNew = true;
			}
			ce->delim = delim;
			ce->_tab = tab;
			ce->_name = name;
			ce->_value = str.right(str.length() - ind - 1).trimmed();
			ce->_type = type;
			ce->_comment = comm;
			ce->_line = lines;
			ce->_all = all;
			if (isNew) {
				ce->_parent = entry;
				entry->_children << ce;
			}
		} else other.back() = src;
		lines++;
	}
	setEntryDelim(&root, delim);
	buildFullNames(&root);
}
