//! \~\file pivaluetree.h
//! \~\ingroup Types
//! \~\brief
//! \~english Attributed values tree
//! \~russian Дерево атрибутированных значений
/*
    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/>.
*/

#ifndef PIVALUETREE_H
#define PIVALUETREE_H

#include "pichunkstream.h"
#include "pivariant.h"


//! \~\ingroup Types
//! \~\brief
//! \~english The %PIValueTree class represents a tree structure of PIVariant values with attributes.
//! \~russian Класс %PIValueTree представляет собой древовидную структуру значений PIVariant с атрибутами.
class PIP_EXPORT PIValueTree {
	BINARY_STREAM_FRIEND(PIValueTree);
	friend PICout operator<<(PICout s, const PIValueTree & v);

public:
	//! \~\brief
	//! \~english Constructs an empty %PIValueTree object.
	//! \~russian Создает пустой объект PIValueTree.
	PIValueTree();

	//! \~\brief
	//! \~english Constructs a %PIValueTree object with only value.
	//! \~russian Создает объект %PIValueTree только со значением.
	PIValueTree(const PIVariant & v);

	//! \~\brief
	//! \~english Constructs a %PIValueTree object with a name, a value, and a set of attributes.
	//! \~russian Создает объект %PIValueTree с именем, значением и набором атрибутов.
	PIValueTree(const PIString & n, const PIVariant & v, const PIVariantMap & a = PIVariantMap());

	//! \~\brief
	//! \~english The Attribute struct contains constants for commonly used attribute names.
	//! \~russian Структура Attribute содержит константы для часто используемых имен атрибутов.
	struct PIP_EXPORT Attribute {
		// clang-format off
		static const char hidden       []; //!< \~english Attribute name for hiding a node. \~russian Имя атрибута для скрытия узла.
		static const char readOnly     []; //!< \~english Attribute name for marking a node as read-only. \~russian Имя атрибута для отметки узла как доступного только для чтения.
		static const char isLabel      []; //!< \~english Attribute name for marking a node as a label. \~russian Имя атрибута для отметки узла как метки.
		static const char grouping     []; //!< \~english Attribute name for grouping nodes. \~russian Имя атрибута для группировки узлов.
		static const char toolTip      []; //!< \~english Attribute name for tool tip text. \~russian Имя атрибута для всплывающей подсказки.

		static const char arrayType    []; //!< \~english Attribute name for marking a node as an array. \~russian Имя атрибута для отметки узла как массива.
		static const char arrayMinCount[]; //!< \~english Attribute name for setting the minimum number of elements in an array. \~russian Имя атрибута для установки минимального количества элементов в массиве.
		static const char arrayMaxCount[]; //!< \~english Attribute name for setting the maximum number of elements in an array. \~russian Имя атрибута для установки максимального количества элементов в массиве.
		static const char arrayReorder []; //!< \~english Attribute name for allowing reordering of elements in an array. \~russian Имя атрибута для разрешения изменения порядка элементов в массиве.
		static const char arrayResize  []; //!< \~english Attribute name for allowing resizing of an array. \~russian Имя атрибута для разрешения изменения размера массива.

		static const char minimum      []; //!< \~english Attribute name for setting the minimum value of a node. \~russian Имя атрибута для установки минимального значения узла.
		static const char maximum      []; //!< \~english Attribute name for setting the maximum value of a node. \~russian Имя атрибута для установки максимального значения узла.
		static const char singleStep   []; //!< \~english Attribute name for setting the single step value of a node. \~russian Имя атрибута для установки значения одного шага для узла.
		static const char decimals     []; //!< \~english Attribute name for setting the number of decimal places for a node. \~russian Имя атрибута для установки количества знаков после запятой для узла.
		static const char prefix       []; //!< \~english Attribute name for setting a prefix string for a node. \~russian Имя атрибута для установки префиксной строки для узла.
		static const char suffix       []; //!< \~english Attribute name for setting a suffix string for a node. \~russian Имя атрибута для установки суффиксной строки для узла.
		static const char style        []; //!< \~english Attribute name for setting a style string (CSS) for a node. \~russian Имя атрибута для установки строки стиля (CSS) для узла.
		static const char filter       []; //!< \~english Attribute name for setting a filter string for a node. \~russian Имя атрибута для установки строки фильтра для узла.
		static const char absolutePath []; //!< \~english Attribute name for marking a node as having an absolute path. \~russian Имя атрибута для отметки узла как имеющего абсолютный путь.
		static const char onlyExisting []; //!< \~english Attribute name for marking a node as only existing file. \~russian Имя атрибута для отметки узла как существующего файла.
		static const char widgetType   []; //!< \~english Attribute name for setting the widget type for a node. \~russian Имя атрибута для установки типа виджета для узла.
		static const char expression   []; //!< \~english Attribute name for setting the expression for a node. \~russian Имя атрибута для установки выражения для узла.
		// clang-format on
	};

	//! \~\brief
	//! \~english Returns whether the current %PIValueTree object is null.
	//! \~russian Возвращает, является ли текущий объект %PIValueTree пустым.
	bool isNull() const { return _is_null; }

	//! \~\brief
	//! \~english Returns whether the current %PIValueTree object is valid.
	//! \~russian Возвращает, является ли текущий объект %PIValueTree допустимым.
	bool isValid() const { return _value.isValid(); }

	//! \~\brief
	//! \~english Returns whether the current %PIValueTree object is an array.
	//! \~russian Возвращает, является ли текущий объект %PIValueTree массивом.
	bool isArray() const { return _attributes.contains(Attribute::arrayType); }

	//! \~\brief
	//! \~english Returns whether the current %PIValueTree object has children.
	//! \~russian Возвращает, имеет ли текущий объект %PIValueTree дочерние элементы.
	bool hasChildren() const { return _children.isNotEmpty(); }

	//! \~\brief
	//! \~english Returns the name of the current %PIValueTree object.
	//! \~russian Возвращает имя текущего объекта %PIValueTree.
	const PIString & name() const { return _name; }

	//! \~\brief
	//! \~english Sets the name of the current %PIValueTree object.
	//! \~russian Устанавливает имя текущего объекта %PIValueTree.
	void setName(const PIString & n);

	//! \~\brief
	//! \~english Returns the comment for the current %PIValueTree object.
	//! \~russian Возвращает комментарий текущего объекта %PIValueTree.
	const PIString & comment() const { return _comment; }

	//! \~\brief
	//! \~english Sets the comment for the current %PIValueTree object.
	//! \~russian Устанавливает комментарий текущего объекта %PIValueTree.
	void setComment(const PIString & c);

	//! \~\brief
	//! \~english Returns the value of the current %PIValueTree object.
	//! \~russian Возвращает значение текущего объекта %PIValueTree.
	const PIVariant & value() const { return _value; }

	//! \~\brief
	//! \~english Sets the value of the current %PIValueTree object.
	//! \~russian Устанавливает значение текущего объекта %PIValueTree.
	void setValue(const PIVariant & v);

	//! \~\brief
	//! \~english Returns the set of attributes for the current %PIValueTree object.
	//! \~russian Возвращает набор атрибутов текущего объекта %PIValueTree.
	const PIVariantMap & attributes() const { return _attributes; }

	//! \~\brief
	//! \~english Returns a reference to the set of attributes for the current %PIValueTree object.
	//! \~russian Возвращает ссылку на набор атрибутов текущего объекта %PIValueTree.
	PIVariantMap & attributes() { return _attributes; }

	//! \~\brief
	//! \~english Returns the attribute value for a given attribute name, or "def" if the attribute is not found.
	//! \~russian Возвращает значение атрибута для данного имени атрибута или "def", если атрибут не найден.
	PIVariant attribute(const PIString & an, const PIVariant & def = PIVariant()) const { return _attributes.value(an, def); }

	//! \~\brief
	//! \~english Sets the attribute value for a given attribute name.
	//! \~russian Устанавливает значение атрибута для данного имени атрибута.
	void setAttribute(const PIString & n, const PIVariant & a);

	//! \~\brief
	//! \~english Merges a set of attributes into the current set of attributes for the %PIValueTree object.
	//! \~russian Объединяет набор атрибутов в текущий набор атрибутов объекта %PIValueTree.
	//! \~\details
	//! \~english Merges the provided attributes map into the current attributes, overwriting any existing attributes with the same name.
	//! \~russian Объединяет предоставленный набор атрибутов в текущие атрибуты, перезаписывая существующие атрибуты с тем же именем.
	void mergeAttributes(const PIVariantMap & a);

	//! \~\brief
	//! \~english Applies the values of a given %PIValueTree object to the current %PIValueTree object.
	//! \param root The %PIValueTree object whose values are to be applied.
	//! \param recursive Whether to apply values recursively to child nodes.
	//! \~russian Применяет значения данного объекта %PIValueTree к текущему объекту %PIValueTree.
	//! \param root Объект %PIValueTree, значения которого должны быть применены.
	//! \param recursive Если установлено в true, то значения будут применяться рекурсивно к дочерним узлам.
	void applyValues(const PIValueTree & root, bool recursive = true);

	//! \~\brief
	//! \~english Set or add the values of a given %PIValueTree object to the current %PIValueTree object.
	//! \param root The %PIValueTree object whose values are to be merged.
	//! \~russian Устанавливает или добавляет значения данного объекта %PIValueTree к текущему объекту %PIValueTree.
	//! \param root Объект %PIValueTree, значения которого должны быть добавлены.
	void merge(const PIValueTree & root);

	//! \~\brief
	//! \~english Returns the children of the current %PIValueTree object.
	//! \~russian Возвращает дочерние элементы текущего объекта %PIValueTree.
	const PIVector<PIValueTree> & children() const { return _children; }

	//! \~\brief
	//! \~english Clears the children of the current %PIValueTree object.
	//! \~russian Очищает дочерние элементы текущего объекта %PIValueTree.
	void clearChildren() { _children.clear(); }

	//! \~\brief
	//! \~english Returns the value of a child node with a given name.
	//! \param child_name The name of the child node.
	//! \param default_value The default value to be returned if the child node is not found.
	//! \param exists If not null, set to true if the child node exists, false otherwise.
	//! \~russian Возвращает значение дочернего элемента с заданным именем.
	//! \param child_name Имя дочернего узла.
	//! \param default_value Значение по умолчанию, которое будет возвращено, если дочерний узел не найден.
	//! \param exists Если не равно нулю, будет установлено в true, если дочерний узел существует, false в противном случае.
	PIVariant childValue(const PIString & child_name, const PIVariant & default_value = PIVariant(), bool * exists = nullptr) const;

	//! \~\brief
	//! \~english Returns the value of a child node with a given path.
	//! \param child_path The path of the child node.
	//! \param default_value The default value to be returned if the child node is not found.
	//! \param exists If not null, set to true if the child node exists, false otherwise.
	//! \~russian Возвращает значение дочернего элемента с заданным путем.
	//! \param child_path Путь дочернего узла.
	//! \param default_value Значение по умолчанию, которое будет возвращено, если дочерний узел не найден.
	//! \param exists Если не равно нулю, будет установлено в true, если дочерний узел существует, false в противном случае.
	PIVariant childValue(const PIStringList & child_path, const PIVariant & default_value = PIVariant(), bool * exists = nullptr) const;

	//! \~\brief
	//! \~english Reads the value of a child node with a given name into "read_to". Returns a reference to the current %PIValueTree object.
	//! \~russian Читает значение дочернего элемента с заданным именем в "read_to". Возвращает ссылку на текущий объект %PIValueTree.
	template<typename T>
	PIValueTree & readChildValue(const PIString & child_name, T & read_to) {
		if (contains(child_name)) read_to = child(child_name).value().value<T>();
		return *this;
	}

	//! \~\brief
	//! \~english Returns whether the current %PIValueTree object contains a child node with a given name.
	//! \~russian Возвращает, содержит ли текущий объект %PIValueTree дочерний элемент с заданным именем.
	bool contains(const PIString & name) const;

	//! \~\brief
	//! \~english Returns whether the current %PIValueTree object contains a child node with a given path.
	//! \~russian Возвращает, содержит ли текущий объект %PIValueTree дочерний элемент с заданным путем.
	bool contains(const PIStringList & path) const;

	//! \~\brief
	//! \~english Returns the index of the child node with a given name, or -1 if the node is not found.
	//! \~russian Возвращает индекс дочернего узла с заданным именем или -1, если узел не найден.
	int childIndex(const PIString & name) const;

	//! \~\brief
	//! \~english Returns a const reference to the child node with a given name, or null node if it doesn't exists.
	//! \~russian Возвращает константную ссылку на дочерний узел с заданным именем или пустой узел, если его нет.
	const PIValueTree & child(const PIString & name) const;

	//! \~\brief
	//! \~english Returns a const reference to the child node with a given path, or null node if it doesn't exists.
	//! \~russian Возвращает константную ссылку на дочерний узел с заданным путем или пустой узел, если его нет.
	const PIValueTree & child(const PIStringList & path) const;

	//! \~\brief
	//! \~english Returns a reference to the child node with a given name, or null node if it doesn't exists.
	//! \~russian Возвращает ссылку на дочерний узел с заданным именем или пустой узел, если его нет.
	PIValueTree & child(const PIString & name);

	//! \~\brief
	//! \~english Returns a reference to the child node with a given path, or null node if it doesn't exists.
	//! \~russian Возвращает ссылку на дочерний узел с заданным путем или пустой узел, если его нет.
	PIValueTree & child(const PIStringList & path);

	//! \~\brief
	//! \~english Inserts a node at a given index. Returns a reference to the current %PIValueTree object.
	//! \~russian Вставляет узел в заданном индексе. Возвращает ссылку на текущий объект %PIValueTree.
	PIValueTree & insertChild(int index, const PIValueTree & n);

	//! \~\brief
	//! \~english Adds a node to the end of the list of children. Returns a reference to the current %PIValueTree object.
	//! \~russian Добавляет узел в конец списка дочерних элементов. Возвращает ссылку на текущий объект %PIValueTree.
	PIValueTree & addChild(const PIValueTree & n);

	//! \~\brief
	//! \~english Adds a vector of nodes to the end of the list of children. Returns a reference to the current %PIValueTree object.
	//! \~russian Добавляет вектор элементов в конец списка дочерних элементов. Возвращает ссылку на текущий объект %PIValueTree.
	PIValueTree & addChildren(const PIVector<PIValueTree> & n);

	//! \~\brief
	//! \~english Removes a child node with a given name. Returns a reference to the current %PIValueTree object.
	//! \~russian Удаляет дочерний элемент с заданным именем. Возвращает ссылку на текущий объект %PIValueTree.
	PIValueTree & remove(const PIString & name);

	//! \~\brief
	//! \~english Returns a reference to the child node with a given name, creating it if it does not exist.
	//! \~russian Возвращает ссылку на дочерний узел с заданным именем, создавая его, если он не существует.
	PIValueTree & operator[](const PIString & name);

	//! \~\brief
	//! \~english Returns a reference to the child node with a given path, creating it if it does not exist.
	//! \~russian Возвращает ссылку на дочерний узел с заданным путем, создавая его, если он не существует.
	PIValueTree & operator[](const PIStringList & path);

	//! \~\brief
	//! \~english Returns a const reference to the child node with a given name, or null node if it doesn`t exists.
	//! \~russian Возвращает константную ссылку на дочерний узел с заданным именем или пустой узел, если его нет.
	const PIValueTree & operator[](const PIString & name) const { return child(name); }

	//! \~\brief
	//! \~english Recursive call "func" for every child. "path" is a name with path, joined with ".".
	//! \~russian Рекурсивно выполняет "func" для каждого узла. "path" - это имя с путём, соединены ".".
	//! \~\details
	//! \~english Recursively calls the provided function for every node in the tree. The path parameter contains the full path to the node,
	//! with ancestor names joined by dots.
	//! \~russian Рекурсивно вызывает предоставленную функцию для каждого узла в дереве. Параметр path содержит полный путь к узлу, при этом
	//! имена предков соединены точками.
	void forEachRecursive(std::function<void(const PIValueTree & item, const PIString & path)> func);

	//! \~\brief
	//! \~english Returns a list of standard attribute names.
	//! \~russian Возвращает список стандартных имен атрибутов.
	static const PIStringList & standardAttributes();

private:
	static void print(PIString & s, const PIValueTree & v, PIString tab);
	static PIValueTree & nullValue();
	void forEachRecursiveInternal(std::function<void(const PIValueTree &, const PIString &)> func, PIString prefix = {});

	PIString _name;
	PIString _comment;
	PIVariantMap _attributes;
	PIVariant _value;
	PIVector<PIValueTree> _children;
	bool _is_null = false;
};


//! \relatesalso PIBinaryStream
//! \~english Store operator.
//! \~russian Оператор сохранения.
BINARY_STREAM_WRITE(PIValueTree) {
	PIChunkStream cs;
	cs.add(1, v._name).add(2, v._comment).add(3, v._attributes).add(4, v._value).add(5, v._children);
	s << cs.data();
	return s;
}

//! \relatesalso PIBinaryStream
//! \~english Restore operator.
//! \~russian Оператор извлечения.
BINARY_STREAM_READ(PIValueTree) {
	PIByteArray csba;
	s >> csba;
	PIChunkStream cs(csba);
	while (!cs.atEnd()) {
		switch (cs.read()) {
		case 1: cs.get(v._name); break;
		case 2: cs.get(v._comment); break;
		case 3: cs.get(v._attributes); break;
		case 4: cs.get(v._value); break;
		case 5: cs.get(v._children); break;
		}
	}
	return s;
}

//! \relatesalso PICout
//! \~english Output operator to \a PICout.
//! \~russian Оператор вывода в \a PICout.
inline PICout operator<<(PICout s, const PIValueTree & v) {
	s.space();
	s.saveAndSetControls(0);
	PIString str;
	PIValueTree::print(str, v, "");
	s << str;
	s.restoreControls();
	return s;
}


#endif // PIVALUETREE_H
