/*
    PIP - Platform Independent Primitives
    Attributed values tree
    Ivan Pelipenko peri4ko@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser 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 Lesser General Public License for more details.

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

#include "pivaluetree.h"

//! \class PIValueTree pivaluetree.h
//! \~\details
//! \~english
//! This class allows for easy manipulation of complex hierarchical data structures
//! represented as PIVariant values. Each %PIValueTree node has a name, a value, and
//! a set of named attributes. %PIValueTree nodes can have child nodes, which allows
//! for the creation of a tree structure.
//!
//! \~russian
//! Этот класс позволяет работать с иерархическими структурами данных,
//! представленными в виде значений PIVariant. Каждый узел %PIValueTree имеет имя,
//! значение и набор именованных атрибутов. Узлы %PIValueTree могут иметь дочерние узлы,
//! что позволяет создавать древовидную структуру.


// clang-format off
const char PIValueTree::Attribute::hidden       [] = "hidden"       ;
const char PIValueTree::Attribute::readOnly     [] = "readOnly"     ;
const char PIValueTree::Attribute::isLabel      [] = "label"        ;
const char PIValueTree::Attribute::grouping     [] = "grouping"     ;
const char PIValueTree::Attribute::toolTip      [] = "toolTip"      ;

const char PIValueTree::Attribute::arrayType    [] = "arrayType"    ;
const char PIValueTree::Attribute::arrayMinCount[] = "arrayMinCount";
const char PIValueTree::Attribute::arrayMaxCount[] = "arrayMaxCount";
const char PIValueTree::Attribute::arrayReorder [] = "arrayReorder" ;
const char PIValueTree::Attribute::arrayResize  [] = "arrayResize"  ;

const char PIValueTree::Attribute::minimum      [] = "minimum"      ;
const char PIValueTree::Attribute::maximum      [] = "maximum"      ;
const char PIValueTree::Attribute::singleStep   [] = "singleStep"   ;
const char PIValueTree::Attribute::decimals     [] = "decimals"     ;
const char PIValueTree::Attribute::prefix       [] = "prefix"       ;
const char PIValueTree::Attribute::suffix       [] = "suffix"       ;
const char PIValueTree::Attribute::style        [] = "style"        ;
const char PIValueTree::Attribute::filter       [] = "filter"       ;
const char PIValueTree::Attribute::absolutePath [] = "absolutePath" ;
const char PIValueTree::Attribute::onlyExisting [] = "onlyExisting" ;
const char PIValueTree::Attribute::widgetType   [] = "widgetType"   ;
const char PIValueTree::Attribute::expression   [] = "expression"   ;
// clang-format on


PIValueTree::PIValueTree() {}


PIValueTree::PIValueTree(const PIVariant & v) {
	setValue(v);
}


PIValueTree::PIValueTree(const PIString & n, const PIVariant & v, const PIVariantMap & a) {
	setName(n);
	setValue(v);
	_attributes = a;
}


void PIValueTree::setName(const PIString & n) {
	if (_is_null) return;
	_name = n;
}


void PIValueTree::setComment(const PIString & c) {
	if (_is_null) return;
	_comment = c;
}


void PIValueTree::setValue(const PIVariant & v) {
	if (_is_null) return;
	_value = v;
}


void PIValueTree::setAttribute(const PIString & n, const PIVariant & a) {
	if (_is_null) return;
	_attributes[n] = a;
}


void PIValueTree::mergeAttributes(const PIVariantMap & a) {
	if (_is_null) return;
	auto it = a.makeIterator();
	while (it.next()) {
		_attributes[it.key()] = it.value();
	}
}


void PIValueTree::applyValues(const PIValueTree & root, bool recursive) {
	if (_is_null) return;
	for (const auto & c: root._children) {
		for (auto & i: _children) {
			if (c.name() == i.name()) {
				i._value.setValueFromString(c.value().toString());
				if (recursive) i.applyValues(c, recursive);
				break;
			}
		}
	}
}


void PIValueTree::merge(const PIValueTree & root) {
	if (_is_null) return;
	for (const auto & c: root._children) {
		bool found = false;
		for (auto & i: _children) {
			if (c.name() == i.name()) {
				if (c.isValid()) i._value = c._value;
				i.merge(c);
				found = true;
				break;
			}
		}
		if (!found) _children << c;
	}
}


PIVariant PIValueTree::childValue(const PIString & child_name, const PIVariant & default_value, bool * exists) const {
	const PIValueTree & node = child(child_name);
	if (node.isNull()) {
		if (exists) *exists = false;
		return default_value;
	}
	if (exists) *exists = true;
	return node.value();
}


PIVariant PIValueTree::childValue(const PIStringList & child_path, const PIVariant & default_value, bool * exists) const {
	const PIValueTree & node = child(child_path);
	if (node.isNull()) {
		if (exists) *exists = false;
		return default_value;
	}
	if (exists) *exists = true;
	return node.value();
}


bool PIValueTree::contains(const PIString & name) const {
	if (_is_null) return false;
	for (const auto & c: _children)
		if (c.name() == name) return true;
	return false;
}


bool PIValueTree::contains(const PIStringList & path) const {
	if (_is_null || path.isEmpty()) return false;
	const PIValueTree * ret = &(child(path[0]));
	for (int i = 1; i < path.size_s(); ++i)
		ret = &(ret->child(path[i]));
	return !ret->isNull();
}


int PIValueTree::childIndex(const PIString & name) const {
	if (_is_null) return -1;
	for (int i = 0; i < _children.size_s(); ++i)
		if (_children[i].name() == name) return i;
	return -1;
}


const PIValueTree & PIValueTree::child(const PIString & name) const {
	if (_is_null) return *this;
	for (const auto & c: _children)
		if (c.name() == name) return c;
	return nullValue();
}


const PIValueTree & PIValueTree::child(const PIStringList & path) const {
	if (_is_null || path.isEmpty()) return *this;
	const PIValueTree * ret = &child(path[0]);
	for (int i = 1; i < path.size_s(); ++i)
		ret = &(ret->child(path[i]));
	return *ret;
}


PIValueTree & PIValueTree::child(const PIString & name) {
	if (_is_null) return *this;
	for (auto & c: _children)
		if (c.name() == name) return c;
	return nullValue();
}


PIValueTree & PIValueTree::child(const PIStringList & path) {
	if (_is_null || path.isEmpty()) return *this;
	PIValueTree * ret = &child(path[0]);
	for (int i = 1; i < path.size_s(); ++i)
		ret = &(ret->child(path[i]));
	return *ret;
}


PIValueTree & PIValueTree::insertChild(int index, const PIValueTree & n) {
	if (_is_null) return *this;
	for (auto & c: _children)
		if (c.name() == n.name()) {
			c = n;
			return *this;
		}
	_children.insert(piClampi(index, 0, _children.size_s()), n);
	return *this;
}


PIValueTree & PIValueTree::addChild(const PIValueTree & n) {
	if (_is_null) return *this;
	for (auto & c: _children)
		if (c.name() == n.name()) {
			c = n;
			return *this;
		}
	_children << n;
	return *this;
}


PIValueTree & PIValueTree::addChildren(const PIVector<PIValueTree> & n) {
	if (_is_null) return *this;
	for (const auto & c: n)
		addChild(c);
	return *this;
}


PIValueTree & PIValueTree::remove(const PIString & name) {
	if (_is_null) return *this;
	_children.removeWhere([name](const PIValueTree & i) { return i.name() == name; });
	return *this;
}


void PIValueTree::forEachRecursive(std::function<void(const PIValueTree &, const PIString &)> func) {
	forEachRecursiveInternal(std::move(func));
}


const PIStringList & PIValueTree::standardAttributes() {
	static PIStringList ret = {
		// clang-format off
		Attribute::hidden       ,
		Attribute::readOnly     ,
		Attribute::isLabel      ,
		Attribute::grouping     ,
		Attribute::toolTip      ,

		Attribute::arrayType    ,
		Attribute::arrayMinCount,
		Attribute::arrayMaxCount,
		Attribute::arrayReorder ,
		Attribute::arrayResize  ,

		Attribute::minimum      ,
		Attribute::maximum      ,
		Attribute::singleStep   ,
		Attribute::decimals     ,
		Attribute::prefix       ,
		Attribute::suffix       ,
		Attribute::style        ,
		Attribute::filter       ,
		Attribute::absolutePath ,
		Attribute::onlyExisting ,
		Attribute::widgetType   ,
		Attribute::expression
		// clang-format on
	};
	return ret;
}


PIValueTree & PIValueTree::operator[](const PIString & name) {
	if (_is_null) return *this;
	for (auto & c: _children)
		if (c.name() == name) return c;
	_children << PIValueTree(name, PIVariant());
	return _children.back();
}


PIValueTree & PIValueTree::operator[](const PIStringList & path) {
	if (_is_null || path.isEmpty()) return *this;
	PIValueTree * ret = &((*this)[path[0]]);
	for (int i = 1; i < path.size_s(); ++i)
		ret = &((*ret)[path[i]]);
	return *ret;
}


void PIValueTree::print(PIString & s, const PIValueTree & v, PIString tab) {
	PIString ntab = tab + "  ", nntab = ntab + "  ";
	s += tab + v.name() + " {\n";
	if (v.value().isValid()) s += ntab + "value: " + v.value().toString() + "\n";
	if (v.comment().isNotEmpty()) s += ntab + "comment: " + v.comment() + "\n";
	if (v.attributes().isNotEmpty()) {
		s += ntab + "attributes: [\n";
		auto ait = v.attributes().makeIterator();
		while (ait.next())
			s += nntab + ait.key() + ": " + ait.value().toString() + "\n";
		s += ntab + "]\n";
	}
	if (v.hasChildren()) {
		s += ntab + "children: [\n";
		for (const auto & c: v.children()) {
			print(s, c, nntab);
		}
		s += ntab + "]\n";
	}
	s += tab + "}\n";
}


PIValueTree & PIValueTree::nullValue() {
	static PIValueTree ret;
	ret._is_null = true;
	return ret;
}


void PIValueTree::forEachRecursiveInternal(std::function<void(const PIValueTree &, const PIString &)> func, PIString prefix) {
	for (auto & c: _children) {
		func(c, prefix + c.name());
		c.forEachRecursiveInternal(func, c.name() + ".");
	}
}
