//! \~\file piconfig.h
//! \~\ingroup IO
//! \~\brief
//! \~english Configuration files parser and writer
//! \~russian Разбор и запись конфигурационных файлов
/*
    PIP - Platform Independent Primitives
    Configuration parser and writer
    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 PICONFIG_H
#define PICONFIG_H

#include "piiodevice.h"
#include "piiostream.h"

// clang-format off
#define PICONFIG_GET_VALUE \
	Entry & getValue(const PIString & vname, const char * def, bool * exists = 0) {return getValue(vname, PIString(def), exists);} \
	Entry & getValue(const PIString & vname, const PIStringList & def, bool * exists = 0) {return getValue(vname, def.join("%|%"), exists);} \
	Entry & getValue(const PIString & vname, const bool def, bool * exists = 0) {return getValue(vname, PIString::fromBool(def), exists);} \
	Entry & getValue(const PIString & vname, const short def, bool * exists = 0) {return getValue(vname, PIString::fromNumber(def), exists);} \
	Entry & getValue(const PIString & vname, const int def, bool * exists = 0) {return getValue(vname, PIString::fromNumber(def), exists);} \
	Entry & getValue(const PIString & vname, const long def, bool * exists = 0) {return getValue(vname, PIString::fromNumber(def), exists);} \
	Entry & getValue(const PIString & vname, const uchar def, bool * exists = 0) {return getValue(vname, PIString::fromNumber(def), exists);} \
	Entry & getValue(const PIString & vname, const ushort def, bool * exists = 0) {return getValue(vname, PIString::fromNumber(def), exists);} \
	Entry & getValue(const PIString & vname, const uint def, bool * exists = 0) {return getValue(vname, PIString::fromNumber(def), exists);} \
	Entry & getValue(const PIString & vname, const ulong def, bool * exists = 0) {return getValue(vname, PIString::fromNumber(def), exists);} \
	Entry & getValue(const PIString & vname, const float def, bool * exists = 0) {return getValue(vname, PIString::fromNumber(def), exists);} \
	Entry & getValue(const PIString & vname, const double def, bool * exists = 0) {return getValue(vname, PIString::fromNumber(def), exists);} \
	\
	Entry & getValue(const PIString & vname, const char * def, bool * exists = 0) const {return getValue(vname, PIString(def), exists);} \
	Entry & getValue(const PIString & vname, const PIStringList & def, bool * exists = 0) const {return getValue(vname, def.join("%|%"), exists);} \
	Entry & getValue(const PIString & vname, const bool def, bool * exists = 0) const {return getValue(vname, PIString::fromBool(def), exists);} \
	Entry & getValue(const PIString & vname, const short def, bool * exists = 0) const {return getValue(vname, PIString::fromNumber(def), exists);} \
	Entry & getValue(const PIString & vname, const int def, bool * exists = 0) const {return getValue(vname, PIString::fromNumber(def), exists);} \
	Entry & getValue(const PIString & vname, const long def, bool * exists = 0) const {return getValue(vname, PIString::fromNumber(def), exists);} \
	Entry & getValue(const PIString & vname, const uchar def, bool * exists = 0) const {return getValue(vname, PIString::fromNumber(def), exists);} \
	Entry & getValue(const PIString & vname, const ushort def, bool * exists = 0) const {return getValue(vname, PIString::fromNumber(def), exists);} \
	Entry & getValue(const PIString & vname, const uint def, bool * exists = 0) const {return getValue(vname, PIString::fromNumber(def), exists);} \
	Entry & getValue(const PIString & vname, const ulong def, bool * exists = 0) const {return getValue(vname, PIString::fromNumber(def), exists);} \
	Entry & getValue(const PIString & vname, const float def, bool * exists = 0) const {return getValue(vname, PIString::fromNumber(def), exists);} \
	Entry & getValue(const PIString & vname, const double def, bool * exists = 0) const {return getValue(vname, PIString::fromNumber(def), exists);}
// clang-format on

//! \~\ingroup IO
//! \~\brief
//! \~english Parser and writer for configuration files with tree structure support
//! \~russian Разбор и запись конфигурационных файлов с поддержкой древовидной структуры
//! \details
//! \~english
//! PIConfig provides functionality to read, write and manipulate configuration files in a tree-like structure.
//! Supports dotted paths, INI-style section prefixes, multiline values and \c include entries resolved during parsing.
//! \~russian
//! PIConfig предоставляет функциональность для чтения, записи и управления конфигурационными файлами в древовидной структуре.
//! Поддерживает точечные пути, префиксы секций в стиле INI, многострочные значения и записи \c include, разрешаемые при разборе.
class PIP_EXPORT PIConfig {
	friend class Entry;
	friend class Branch;

public:
	NO_COPY_CLASS(PIConfig);

	//! \~english Opens and parses configuration file at "path".
	//! \~russian Открывает и разбирает файл конфигурации по пути "path".
	PIConfig(const PIString & path, PIIODevice::DeviceMode mode = PIIODevice::ReadWrite);

	//! \~english Opens and parses configuration stored in "string".
	//! \~russian Открывает и разбирает конфигурацию, хранящуюся в "string".
	PIConfig(PIString * string, PIIODevice::DeviceMode mode = PIIODevice::ReadWrite);

	//! \~english Opens and parses configuration from custom device "device".
	//! \~russian Открывает и разбирает конфигурацию из пользовательского устройства "device".
	PIConfig(PIIODevice * device = nullptr, PIIODevice::DeviceMode mode = PIIODevice::ReadWrite);

	//! \~english Destroys the parser and releases owned devices.
	//! \~russian Уничтожает парсер и освобождает принадлежащие ему устройства.
	~PIConfig();

	class Entry;


	//! \~\ingroup IO
	//! \~\brief
	//! \~english Branch class - container for Entry objects
	//! \~russian Класс Branch - контейнер для объектов Entry
	class PIP_EXPORT Branch: public PIVector<Entry *> {
		friend class PIConfig;
		friend class Entry;
#ifdef PIP_STD_IOSTREAM
		friend std::ostream & operator<<(std::ostream & s, const Branch & v);
#endif
		friend PICout operator<<(PICout s, const Branch & v);

	public:
		Branch() { ; }

		//! \~\brief
		//! \~english Get value from branch by name with default value
		//! \~russian Получить значение из ветки по имени со значением по умолчанию
		//! \~\details
		//! \~english If lookup fails, returns a shared default entry filled with "def" and sets \a exists to \b false when provided.
		//! \~russian Если поиск не удался, возвращает общий внутренний entry со значением "def" и устанавливает \a exists в \b false, если
		//! указатель передан.
		Entry & getValue(const PIString & vname, const PIString & def = PIString(), bool * exists = 0);
		Entry & getValue(const PIString & vname, const PIString & def = PIString(), bool * exists = 0) const {
			return const_cast<Branch *>(this)->getValue(vname, def, exists);
		}

		PICONFIG_GET_VALUE


		//! \fn Entry & getValue(const PIString & vname, const char * def, bool * exists = 0)
		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"

		//! \fn Entry & getValue(const PIString & vname, const PIStringList & def, bool * exists = 0)
		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"

		//! \fn Entry & getValue(const PIString & vname, const bool def, bool * exists = 0)
		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"

		//! \fn Entry & getValue(const PIString & vname, const short def, bool * exists = 0)
		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"

		//! \fn Entry & getValue(const PIString & vname, const int def, bool * exists = 0)
		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"

		//! \fn Entry & getValue(const PIString & vname, const long def, bool * exists = 0)
		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"

		//! \fn Entry & getValue(const PIString & vname, const uchar def, bool * exists = 0)
		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"

		//! \fn Entry & getValue(const PIString & vname, const ushort def, bool * exists = 0)
		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"

		//! \fn Entry & getValue(const PIString & vname, const uint def, bool * exists = 0)
		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"

		//! \fn Entry & getValue(const PIString & vname, const ulong def, bool * exists = 0)
		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"

		//! \fn Entry & getValue(const PIString & vname, const float def, bool * exists = 0)
		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"

		//! \fn Entry & getValue(const PIString & vname, const double def, bool * exists = 0)
		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"


		//! \~english Returns all leaf descendants reachable from this branch.
		//! \~russian Возвращает все листовые потомки, достижимые из этой ветви.
		Branch allLeaves();

		//! \~english Get all entries with name containing specified substring
		//! \~russian Получить все записи с именем, содержащим указанную подстроку
		Branch getValues(const PIString & name);

		//! \~english Returns only entries in this branch that have no children.
		//! \~russian Возвращает только записи этой ветви без дочерних элементов.
		Branch getLeaves();

		//! \~english Returns only entries in this branch that have children.
		//! \~russian Возвращает только записи этой ветви, имеющие дочерние элементы.
		Branch getBranches();

		//! \~english Removes entries whose names do not contain "f".
		//! \~russian Удаляет записи, чьи имена не содержат "f".
		Branch & filter(const PIString & f);

		//! \~english Returns \b true if any entry in this branch or its descendants has name "name".
		//! \~russian Возвращает \b true, если какая-либо запись этой ветви или ее потомков имеет имя "name".
		bool isEntryExists(const PIString & name) const {
			for (const auto * i: *this)
				if (entryExists(i, name)) return true;
			return false;
		}

		//! \~english Returns position of entry pointer "e" inside this branch, or -1.
		//! \~russian Возвращает позицию указателя на запись "e" в этой ветви или -1.
		int indexOf(const Entry * e) {
			for (int i = 0; i < size_s(); ++i)
				if (at(i) == e) return i;
			return -1;
		}

	private:
		bool entryExists(const Entry * e, const PIString & name) const;
		void allLeaves(Branch & b, Entry * e) {
			for (auto * i: e->_children) {
				if (i->isLeaf())
					b << i;
				else
					allLeaves(b, i);
			}
		}
#ifdef PIP_STD_IOSTREAM
		void coutt(std::ostream & s, const PIString & p) const {
			for (const auto * i: *this)
				i->coutt(s, p);
		}
#endif
		void piCoutt(PICout s, const PIString & p) const {
			for (const auto * i: *this)
				i->piCoutt(s, p);
		}

		static Entry _empty;
		PIString delim;
	};


	//! \~\ingroup IO
	//! \~\brief
	//! \~english Node of the parsed configuration tree.
	//! \~russian Узел разобранного дерева конфигурации.
	//! \~\details
	//! \~english Stores entry name, value, type mark, inline comment and child entries derived from dotted names.
	//! \~russian Хранит имя записи, значение, метку типа, встроенный комментарий и дочерние записи, полученные из точечных имен.
	class PIP_EXPORT Entry {
		friend class PIConfig;
		friend class Branch;

	public:
		//! \~english Constructs an empty detached entry.
		//! \~russian Создает пустую отсоединенную запись.
		Entry() {
			_parent = 0;
			_line   = -1;
		}

		//! \~english Returns parent entry, or \c 0 for the root and default placeholder entries.
		//! \~russian Возвращает родительскую запись или \c 0 для корня и внутренних placeholder-записей по умолчанию.
		Entry * parent() const { return _parent; }

		//! \~english Returns direct children count.
		//! \~russian Возвращает количество непосредственных дочерних записей.
		int childCount() const { return _children.size_s(); }

		//! \~english Returns direct children as \a PIConfig::Branch.
		//! \~russian Возвращает непосредственных потомков как \a PIConfig::Branch.
		Branch & children() const {
			_children.delim = delim;
			return _children;
		}

		//! \~english Returns direct child at position "index".
		//! \~russian Возвращает непосредственного потомка с позицией "index".
		Entry * child(const int index) const { return _children[index]; }

		//! \~english Returns first direct child named "name".
		//! \~russian Возвращает первого непосредственного потомка с именем "name".
		Entry * findChild(const PIString & name) {
			for (auto * i: _children)
				if (i->_name == name) return i;
			return 0;
		}

		//! \~english Returns first direct child named "name".
		//! \~russian Возвращает первого непосредственного потомка с именем "name".
		const Entry * findChild(const PIString & name) const {
			for (const auto * i: _children)
				if (i->_name == name) return i;
			return 0;
		}

		//! \~english Returns \b true when the entry has no children.
		//! \~russian Возвращает \b true, когда у записи нет дочерних элементов.
		bool isLeaf() const { return _children.isEmpty(); }


		//! \~english Returns local entry name without parent prefix.
		//! \~russian Возвращает локальное имя записи без родительского префикса.
		const PIString & name() const { return _name; }

		//! \~english Returns raw stored value.
		//! \~russian Возвращает исходное сохраненное значение.
		const PIString & value() const { return _value; }

		//! \~english Returns one-letter stored type mark.
		//! \~russian Возвращает сохраненную однобуквенную метку типа.
		const PIString & type() const { return _type; }

		//! \~english Returns inline comment stored after the type mark.
		//! \~russian Возвращает встроенный комментарий, сохраненный после метки типа.
		const PIString & comment() const { return _comment; }

		//! \~\brief
		//! \~english Returns full dotted name as it appears in the tree.
		//! \~russian Возвращает полное точечное имя в дереве.
		//! \~\details
		//! \~english Default placeholder entries always have empty full name.
		//! \~russian У placeholder-записей по умолчанию полное имя всегда пустое.
		//! \snippet piconfig.cpp fullName
		const PIString & fullName() const { return _full_name; }

		//! \~english Sets local name to "value" and returns this entry.
		//! \~russian Устанавливает локальное имя в "value" и возвращает эту запись.
		Entry & setName(const PIString & value) {
			_name = value;
			return *this;
		}

		//! \~english Sets stored type mark to "value" and returns this entry.
		//! \~russian Устанавливает сохраненную метку типа в "value" и возвращает эту запись.
		Entry & setType(const PIString & value) {
			_type = value;
			return *this;
		}

		//! \~english Sets inline comment to "value" and returns this entry.
		//! \~russian Устанавливает встроенный комментарий в "value" и возвращает эту запись.
		Entry & setComment(const PIString & value) {
			_comment = value;
			return *this;
		}

		//! \~english Sets raw stored value to "value" and returns this entry.
		//! \~russian Устанавливает исходное сохраненное значение в "value" и возвращает эту запись.
		Entry & setValue(const PIString & value) {
			_value = value;
			return *this;
		}

		//! \~english Stores string list value and marks entry type as "l".
		//! \~russian Сохраняет список строк и помечает тип записи как "l".
		Entry & setValue(const PIStringList & value) {
			setValue(value.join("%|%"));
			setType("l");
			return *this;
		}

		//! \~english Stores C-string value and marks entry type as "s".
		//! \~russian Сохраняет значение C-строки и помечает тип записи как "s".
		Entry & setValue(const char * value) {
			setValue(PIString(value));
			setType("s");
			return *this;
		}

		//! \~english Stores boolean value and marks entry type as "b".
		//! \~russian Сохраняет логическое значение и помечает тип записи как "b".
		Entry & setValue(const bool value) {
			setValue(PIString::fromBool(value));
			setType("b");
			return *this;
		}

		//! \~english Stores character value and marks entry type as "s".
		//! \~russian Сохраняет символьное значение и помечает тип записи как "s".
		Entry & setValue(const char value) {
			setValue(PIString(1, value));
			setType("s");
			return *this;
		}

		//! \~english Stores numeric value and marks entry type as "n".
		//! \~russian Сохраняет числовое значение и помечает тип записи как "n".
		Entry & setValue(const short value) {
			setValue(PIString::fromNumber(value));
			setType("n");
			return *this;
		}

		//! \~english Stores numeric value and marks entry type as "n".
		//! \~russian Сохраняет числовое значение и помечает тип записи как "n".
		Entry & setValue(const int value) {
			setValue(PIString::fromNumber(value));
			setType("n");
			return *this;
		}

		//! \~english Stores numeric value and marks entry type as "n".
		//! \~russian Сохраняет числовое значение и помечает тип записи как "n".
		Entry & setValue(const long value) {
			setValue(PIString::fromNumber(value));
			setType("n");
			return *this;
		}

		//! \~english Stores numeric value and marks entry type as "n".
		//! \~russian Сохраняет числовое значение и помечает тип записи как "n".
		Entry & setValue(const uchar value) {
			setValue(PIString::fromNumber(value));
			setType("n");
			return *this;
		}

		//! \~english Stores numeric value and marks entry type as "n".
		//! \~russian Сохраняет числовое значение и помечает тип записи как "n".
		Entry & setValue(const ushort value) {
			setValue(PIString::fromNumber(value));
			setType("n");
			return *this;
		}

		//! \~english Stores numeric value and marks entry type as "n".
		//! \~russian Сохраняет числовое значение и помечает тип записи как "n".
		Entry & setValue(const uint value) {
			setValue(PIString::fromNumber(value));
			setType("n");
			return *this;
		}

		//! \~english Stores numeric value and marks entry type as "n".
		//! \~russian Сохраняет числовое значение и помечает тип записи как "n".
		Entry & setValue(const ulong value) {
			setValue(PIString::fromNumber(value));
			setType("n");
			return *this;
		}

		//! \~english Stores floating-point value and marks entry type as "f".
		//! \~russian Сохраняет вещественное значение и помечает тип записи как "f".
		Entry & setValue(const float value) {
			setValue(PIString::fromNumber(value));
			setType("f");
			return *this;
		}

		//! \~english Stores floating-point value and marks entry type as "f".
		//! \~russian Сохраняет вещественное значение и помечает тип записи как "f".
		Entry & setValue(const double value) {
			setValue(PIString::fromNumber(value));
			setType("f");
			return *this;
		}


		//! \~\brief
		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"
		//! \~\details
		//! \~english If lookup fails, returns a shared default entry filled with "def" and sets \a exists to \b false when provided.
		//! \~russian Если поиск не удался, возвращает общий внутренний entry со значением "def" и устанавливает \a exists в \b false, если
		//! указатель передан.
		Entry & getValue(const PIString & vname, const PIString & def = PIString(), bool * exists = 0);
		Entry & getValue(const PIString & vname, const PIString & def = PIString(), bool * exists = 0) const {
			return const_cast<Entry *>(this)->getValue(vname, def, exists);
		}
		PICONFIG_GET_VALUE

		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"
		//! \fn Entry & getValue(const PIString & vname, const char * def, bool * exists = 0)

		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"
		//! \fn Entry & getValue(const PIString & vname, const PIStringList & def, bool * exists = 0)

		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"
		//! \fn Entry & getValue(const PIString & vname, const bool def, bool * exists = 0)

		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"
		//! \fn Entry & getValue(const PIString & vname, const short def, bool * exists = 0)

		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"
		//! \fn Entry & getValue(const PIString & vname, const int def, bool * exists = 0)

		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"
		//! \fn Entry & getValue(const PIString & vname, const long def, bool * exists = 0)

		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"
		//! \fn Entry & getValue(const PIString & vname, const uchar def, bool * exists = 0)

		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"
		//! \fn Entry & getValue(const PIString & vname, const ushort def, bool * exists = 0)

		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"
		//! \fn Entry & getValue(const PIString & vname, const uint def, bool * exists = 0)

		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"
		//! \fn Entry & getValue(const PIString & vname, const ulong def, bool * exists = 0)

		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"
		//! \fn Entry & getValue(const PIString & vname, const float def, bool * exists = 0)

		//! \~english Get entry with name "vname" and default value "def"
		//! \~russian Получить запись с именем "vname" и значением по умолчанию "def"
		//! \fn Entry & getValue(const PIString & vname, const double def, bool * exists = 0)


		//! \~english Returns direct children whose names contain substring "vname".
		//! \~russian Возвращает непосредственных потомков, чьи имена содержат подстроку "vname".
		Branch getValues(const PIString & vname);


		//! \~english Returns \b true if this entry or any descendant has name "name".
		//! \~russian Возвращает \b true, если эта запись или любой ее потомок имеет имя "name".
		bool isEntryExists(const PIString & name) const { return entryExists(this, name); }


		//! \~english Converts stored value to \c bool.
		//! \~russian Преобразует сохраненное значение в \c bool.
		bool toBool() const { return _value.toBool(); }

		//! \~english Converts stored value to \c char.
		//! \~russian Преобразует сохраненное значение в \c char.
		char toChar() const { return (_value.isEmpty() ? 0 : _value[0].toAscii()); }

		//! \~english Converts stored value to \c short.
		//! \~russian Преобразует сохраненное значение в \c short.
		short toShort() const { return _value.toShort(); }

		//! \~english Converts stored value to \c int.
		//! \~russian Преобразует сохраненное значение в \c int.
		int toInt() const { return _value.toInt(); }

		//! \~english Converts stored value to \c long.
		//! \~russian Преобразует сохраненное значение в \c long.
		long toLong() const { return _value.toLong(); }

		//! \~english Converts stored value to \c uchar.
		//! \~russian Преобразует сохраненное значение в \c uchar.
		uchar toUChar() const { return _value.toInt(); }

		//! \~english Converts stored value to \c ushort.
		//! \~russian Преобразует сохраненное значение в \c ushort.
		ushort toUShort() const { return _value.toShort(); }

		//! \~english Converts stored value to \c uint.
		//! \~russian Преобразует сохраненное значение в \c uint.
		uint toUInt() const { return _value.toInt(); }

		//! \~english Converts stored value to \c ulong.
		//! \~russian Преобразует сохраненное значение в \c ulong.
		ulong toULong() const { return _value.toLong(); }

		//! \~english Converts stored value to \c float.
		//! \~russian Преобразует сохраненное значение в \c float.
		float toFloat() const { return _value.toFloat(); }

		//! \~english Converts stored value to \c double.
		//! \~russian Преобразует сохраненное значение в \c double.
		double toDouble() const { return _value.toDouble(); }

		//! \~english Returns stored value as \a PIString.
		//! \~russian Возвращает сохраненное значение как \a PIString.
		PIString toString() const { return _value; }

		//! \~english Splits stored list value into \a PIStringList using internal list separator.
		//! \~russian Разбивает сохраненное списковое значение в \a PIStringList, используя внутренний разделитель списков.
		PIStringList toStringList() const { return _value.split("%|%"); }

	private:
		static bool compare(PIConfig::Entry * const & f, PIConfig::Entry * const & s) { return f->_line < s->_line; }
		bool entryExists(const Entry * e, const PIString & name) const;
		void buildLine() { _all = _tab + _full_name + " = " + _value + " #" + _type + " " + _comment; }
		void clear() {
			_children.clear();
			_name = _value = _type = _comment = _all = PIString();
			_line                                    = 0;
			_parent                                  = 0;
		}
#ifdef PIP_STD_IOSTREAM
		void coutt(std::ostream & s, const PIString & p) const;
#endif
		void piCoutt(PICout s, const PIString & p) const;
		void deleteBranch() {
			for (auto * i: _children) {
				i->deleteBranch();
				delete i;
			}
		}

		static Entry _empty;
		Entry * _parent;
		mutable Branch _children;
		PIString _tab;
		PIString _name;
		PIString _value;
		PIString _type;
		PIString _comment;
		PIString _all;
		PIString _full_name;
		PIString delim;
		int _line;
	};


	//! \~english Opens and parses configuration file at "path".
	//! \~russian Открывает и разбирает файл конфигурации по пути "path".
	bool open(const PIString & path, PIIODevice::DeviceMode mode = PIIODevice::ReadWrite);

	//! \~english Opens and parses configuration stored in "string".
	//! \~russian Открывает и разбирает конфигурацию, хранящуюся в "string".
	bool open(PIString * string, PIIODevice::DeviceMode mode = PIIODevice::ReadWrite);

	//! \~english Opens and parses configuration from custom device "device".
	//! \~russian Открывает и разбирает конфигурацию из пользовательского устройства "device".
	bool open(PIIODevice * device, PIIODevice::DeviceMode mode = PIIODevice::ReadWrite);

	//! \~english Returns whether a backing device is currently opened.
	//! \~russian Возвращает, открыто ли сейчас базовое устройство.
	bool isOpened() const;

	//! \~english Resolves top-level path "vname".
	//! \~russian Разрешает путь верхнего уровня "vname".
	//! \~\details
	//! \~english If lookup fails, returns a shared default entry filled with "def" and sets \a exists to \b false when provided.
	//! \~russian Если поиск не удался, возвращает общий внутренний entry со значением "def" и устанавливает \a exists в \b false, если
	//! указатель передан.
	Entry & getValue(const PIString & vname, const PIString & def = PIString(), bool * exists = 0);
	Entry & getValue(const PIString & vname, const PIString & def = PIString(), bool * exists = 0) const {
		return const_cast<PIConfig *>(this)->getValue(vname, def, exists);
	}

	PICONFIG_GET_VALUE

	//! \~english Get top-level entry with name "vname" and default value "def"
	//! \~russian Получить запись верхнего уровня с именем "vname" и значением по умолчанию "def"
	//! \fn Entry & getValue(const PIString & vname, const char * def, bool * exists = 0)

	//! \~english Get top-level entry with name "vname" and default value "def"
	//! \~russian Получить запись верхнего уровня с именем "vname" и значением по умолчанию "def"
	//! \fn Entry & getValue(const PIString & vname, const PIStringList & def, bool * exists = 0)

	//! \~english Get top-level entry with name "vname" and default value "def"
	//! \~russian Получить запись верхнего уровня с именем "vname" и значением по умолчанию "def"
	//! \fn Entry & getValue(const PIString & vname, const bool def, bool * exists = 0)

	//! \~english Get top-level entry with name "vname" and default value "def"
	//! \~russian Получить запись верхнего уровня с именем "vname" и значением по умолчанию "def"
	//! \fn Entry & getValue(const PIString & vname, const short def, bool * exists = 0)

	//! \~english Get top-level entry with name "vname" and default value "def"
	//! \~russian Получить запись верхнего уровня с именем "vname" и значением по умолчанию "def"
	//! \fn Entry & getValue(const PIString & vname, const int def, bool * exists = 0)

	//! \~english Get top-level entry with name "vname" and default value "def"
	//! \~russian Получить запись верхнего уровня с именем "vname" и значением по умолчанию "def"
	//! \fn Entry & getValue(const PIString & vname, const long def, bool * exists = 0)

	//! \~english Get top-level entry with name "vname" and default value "def"
	//! \~russian Получить запись верхнего уровня с именем "vname" и значением по умолчанию "def"
	//! \fn Entry & getValue(const PIString & vname, const uchar def, bool * exists = 0)

	//! \~english Get top-level entry with name "vname" and default value "def"
	//! \~russian Получить запись верхнего уровня с именем "vname" и значением по умолчанию "def"
	//! \fn Entry & getValue(const PIString & vname, const ushort def, bool * exists = 0)

	//! \~english Get top-level entry with name "vname" and default value "def"
	//! \~russian Получить запись верхнего уровня с именем "vname" и значением по умолчанию "def"
	//! \fn Entry & getValue(const PIString & vname, const uint def, bool * exists = 0)

	//! \~english Get top-level entry with name "vname" and default value "def"
	//! \~russian Получить запись верхнего уровня с именем "vname" и значением по умолчанию "def"
	//! \fn Entry & getValue(const PIString & vname, const ulong def, bool * exists = 0)

	//! \~english Get top-level entry with name "vname" and default value "def"
	//! \~russian Получить запись верхнего уровня с именем "vname" и значением по умолчанию "def"
	//! \fn Entry & getValue(const PIString & vname, const float def, bool * exists = 0)

	//! \~english Get top-level entry with name "vname" and default value "def"
	//! \~russian Получить запись верхнего уровня с именем "vname" и значением по умолчанию "def"
	//! \fn Entry & getValue(const PIString & vname, const double def, bool * exists = 0)

	//! \~english Returns top-level entries whose names contain substring "vname".
	//! \~russian Возвращает записи верхнего уровня, чьи имена содержат подстроку "vname".
	Branch getValues(const PIString & vname);


	//! \~english Sets or creates top-level path "name", stores "value", assigns type mark "type" and optionally writes changes immediately.
	//! \~russian Устанавливает или создает путь верхнего уровня "name", сохраняет "value", назначает метку типа "type" и при необходимости
	//! сразу записывает изменения.
	void setValue(const PIString & name, const PIString & value, const PIString & type = "s", bool write = true);

	//! \~english Stores string list and marks type as "l".
	//! \~russian Сохраняет список строк и помечает тип как "l".
	void setValue(const PIString & name, const PIStringList & value, bool write = true) { setValue(name, value.join("%|%"), "l", write); }

	//! \~english Stores C-string and marks type as "s".
	//! \~russian Сохраняет C-строку и помечает тип как "s".
	void setValue(const PIString & name, const char * value, bool write = true) { setValue(name, PIString(value), "s", write); }

	//! \~english Stores boolean value and marks type as "b".
	//! \~russian Сохраняет логическое значение и помечает тип как "b".
	void setValue(const PIString & name, const bool value, bool write = true) { setValue(name, PIString::fromBool(value), "b", write); }

	//! \~english Stores numeric value and marks type as "n".
	//! \~russian Сохраняет числовое значение и помечает тип как "n".
	void setValue(const PIString & name, const short value, bool write = true) { setValue(name, PIString::fromNumber(value), "n", write); }

	//! \~english Stores numeric value and marks type as "n".
	//! \~russian Сохраняет числовое значение и помечает тип как "n".
	void setValue(const PIString & name, const int value, bool write = true) { setValue(name, PIString::fromNumber(value), "n", write); }

	//! \~english Stores numeric value and marks type as "n".
	//! \~russian Сохраняет числовое значение и помечает тип как "n".
	void setValue(const PIString & name, const long value, bool write = true) { setValue(name, PIString::fromNumber(value), "n", write); }

	//! \~english Stores numeric value and marks type as "n".
	//! \~russian Сохраняет числовое значение и помечает тип как "n".
	void setValue(const PIString & name, const uchar value, bool write = true) { setValue(name, PIString::fromNumber(value), "n", write); }

	//! \~english Stores numeric value and marks type as "n".
	//! \~russian Сохраняет числовое значение и помечает тип как "n".
	void setValue(const PIString & name, const ushort value, bool write = true) { setValue(name, PIString::fromNumber(value), "n", write); }

	//! \~english Stores numeric value and marks type as "n".
	//! \~russian Сохраняет числовое значение и помечает тип как "n".
	void setValue(const PIString & name, const uint value, bool write = true) { setValue(name, PIString::fromNumber(value), "n", write); }

	//! \~english Stores numeric value and marks type as "n".
	//! \~russian Сохраняет числовое значение и помечает тип как "n".
	void setValue(const PIString & name, const ulong value, bool write = true) { setValue(name, PIString::fromNumber(value), "n", write); }

	//! \~english Stores floating-point value and marks type as "f".
	//! \~russian Сохраняет вещественное значение и помечает тип как "f".
	void setValue(const PIString & name, const float value, bool write = true) { setValue(name, PIString::fromNumber(value), "f", write); }

	//! \~english Stores floating-point value and marks type as "f".
	//! \~russian Сохраняет вещественное значение и помечает тип как "f".
	void setValue(const PIString & name, const double value, bool write = true) { setValue(name, PIString::fromNumber(value), "f", write); }

	//! \~english Returns root entry of the parsed tree.
	//! \~russian Возвращает корневую запись разобранного дерева.
	Entry & rootEntry() { return root; }

	//! \~english Returns total number of parsed entries below the root.
	//! \~russian Возвращает общее количество разобранных записей ниже корня.
	int entriesCount() const { return childCount(&root); }

	//! \~english Returns \b true if any parsed entry path contains name "name".
	//! \~russian Возвращает \b true, если среди разобранных путей есть запись с именем "name".
	bool isEntryExists(const PIString & name) const { return entryExists(&root, name); }

	//! \~english Returns all direct children of the root entry.
	//! \~russian Возвращает всех непосредственных потомков корневой записи.
	Branch allTree() {
		Branch b;
		for (auto * i: root._children)
			b << i;
		b.delim = delim;
		return b;
	}

	//! \~english Returns all stored leaves and valued branch entries sorted by source order.
	//! \~russian Возвращает все сохраненные листья и ветви со значением, отсортированные по порядку в источнике.
	Branch allLeaves() {
		Branch b;
		allLeaves(b, &root);
		b.sort(Entry::compare);
		b.delim = delim;
		return b;
	}

	//! \~english Returns index of path "name" inside \a allLeaves(), or -1.
	//! \~russian Возвращает индекс пути "name" внутри \a allLeaves() или -1.
	int entryIndex(const PIString & name);

	//! \~english Returns entry name by \a allLeaves() index.
	//! \~russian Возвращает имя записи по индексу в \a allLeaves().
	PIString getName(uint number) { return entryByIndex(number)._name; }

	//! \~english Returns entry value by \a allLeaves() index.
	//! \~russian Возвращает значение записи по индексу в \a allLeaves().
	PIString getValueByIndex(uint number) { return entryByIndex(number)._value; }

	//! \~english Returns entry type mark by \a allLeaves() index.
	//! \~russian Возвращает метку типа записи по индексу в \a allLeaves().
	PIChar getType(uint number) { return entryByIndex(number)._type[0]; }

	//! \~english Returns entry comment by \a allLeaves() index.
	//! \~russian Возвращает комментарий записи по индексу в \a allLeaves().
	PIString getComment(uint number) { return entryByIndex(number)._comment; }


	//! \~english Creates new path "name" when it does not already exist and optionally writes changes immediately.
	//! \~russian Создает новый путь "name", если он еще не существует, и при необходимости сразу записывает изменения.
	void addEntry(const PIString & name, const PIString & value, const PIString & type = "s", bool write = true);

	//! \~english Renames entry referenced by \a allLeaves() index "number".
	//! \~russian Переименовывает запись, на которую ссылается индекс "number" в \a allLeaves().
	void setName(uint number, const PIString & name, bool write = true);

	//! \~english Replaces stored value of entry referenced by \a allLeaves() index "number".
	//! \~russian Заменяет сохраненное значение записи, на которую ссылается индекс "number" в \a allLeaves().
	void setValue(uint number, const PIString & value, bool write = true);

	//! \~english Replaces type mark of entry referenced by \a allLeaves() index "number".
	//! \~russian Заменяет метку типа записи, на которую ссылается индекс "number" в \a allLeaves().
	void setType(uint number, const PIString & type, bool write = true);

	//! \~english Replaces comment of entry referenced by \a allLeaves() index "number".
	//! \~russian Заменяет комментарий записи, на которую ссылается индекс "number" в \a allLeaves().
	void setComment(uint number, const PIString & comment, bool write = true);


	//! \~english Removes entry path "name" and its subtree when needed.
	//! \~russian Удаляет путь записи "name" и при необходимости его поддерево.
	void removeEntry(const PIString & name, bool write = true);

	//! \~english Removes entry referenced by \a allLeaves() index "number".
	//! \~russian Удаляет запись, на которую ссылается индекс "number" в \a allLeaves().
	void removeEntry(uint number, bool write = true);

	//! \~english Removes all parsed entries and clears the backing device content.
	//! \~russian Удаляет все разобранные записи и очищает содержимое базового устройства.
	void clear();

	//! \~english Rebuilds internal tree from current device contents.
	//! \~russian Перестраивает внутреннее дерево из текущего содержимого устройства.
	void readAll();

	//! \~english Writes current tree back to the device and reparses it.
	//! \~russian Записывает текущее дерево обратно в устройство и разбирает его заново.
	void writeAll();

	//! \~english Returns current path delimiter, "." by default.
	//! \~russian Возвращает текущий разделитель путей, по умолчанию ".".
	const PIString & delimiter() const { return delim; }

	//! \~english Sets path delimiter for subsequent parsing and reparses the device.
	//! \~russian Устанавливает разделитель путей для последующего разбора и заново разбирает устройство.
	void setDelimiter(const PIString & d) {
		delim = d;
		setEntryDelim(&root, d);
		readAll();
	}

private:
	PIConfig(const PIString & path, PIStringList dirs);
	void _init();
	void _destroy();
	void _setupDev();
	void _clearDev();
	void _flushDev();
	bool _isEndDev();
	void _seekToBeginDev();
	PIString _readLineDev();
	void _writeDev(const PIString & l);
	int childCount(const Entry * e) const {
		int c = 0;
		for (const auto * i: e->_children)
			c += childCount(i);
		c += e->_children.size_s();
		return c;
	}
	bool entryExists(const Entry * e, const PIString & name) const;
	void buildFullNames(Entry * e) {
		for (auto * i: e->_children) {
			if (e != &root)
				i->_full_name = e->_full_name + delim + i->_name;
			else
				i->_full_name = i->_name;
			buildFullNames(i);
		}
	}
	void allLeaves(Branch & b, Entry * e) {
		for (auto * i: e->_children) {
			if ((!i->_value.isEmpty() && !i->isLeaf()) || i->isLeaf()) b << i;
			allLeaves(b, i);
		}
	}
	void setEntryDelim(Entry * e, const PIString & d) {
		for (auto * i: e->_children)
			setEntryDelim(i, d);
		e->delim = d;
	}
	Entry & entryByIndex(const int index) {
		Branch b = allLeaves();
		if (index < 0 || index >= b.size_s()) return empty;
		return *(b[index]);
	}
	void removeEntry(Branch & b, Entry * e);
	void deleteEntry(Entry * e) {
		for (auto * i: e->_children)
			deleteEntry(i);
		delete e;
	}
	PIString getPrefixFromLine(PIString line, bool * exists);
	void updateIncludes();
	PIString parseLine(PIString v);
	void parse();

	bool own_dev = false, internal = false;
	PIVector<PIConfig *> includes, inc_devs;
	Branch all_includes;
	PIIODevice * dev        = nullptr;
	PIIOTextStream * stream = nullptr;
	PIString delim;
	PIStringList incdirs;
	Entry root, empty;
	uint lines = 0;
	PIStringList other;
};


#ifdef PIP_STD_IOSTREAM

//! \~english Writes branch contents to \a std::ostream in tree form.
//! \~russian Выводит содержимое ветви в \a std::ostream в виде дерева.
PIP_EXPORT std::ostream & operator<<(std::ostream & s, const PIConfig::Branch & v);

//! \~english Writes entry value to \a std::ostream.
//! \~russian Выводит значение записи в \a std::ostream.
PIP_EXPORT std::ostream & operator<<(std::ostream & s, const PIConfig::Entry & v);

#endif

//! \~english Writes branch contents to \a PICout in tree form.
//! \~russian Выводит содержимое ветви в \a PICout в виде дерева.
inline PICout operator<<(PICout s, const PIConfig::Branch & v) {
	s.saveAndSetControls(0);
	v.piCoutt(s, "");
	s.restoreControls();
	return s;
}

//! \~english Writes entry value, type and comment to \a PICout.
//! \~russian Выводит значение, тип и комментарий записи в \a PICout.
inline PICout operator<<(PICout s, const PIConfig::Entry & v) {
	s << v.value() << "(" << v.type() << v.comment() << ")";
	return s;
}


//! \relatesalso PIConfig
//! \relatesalso PIIODevice
//! \~\brief
//! \~english Helper for reading device settings from configuration entries.
//! \~russian Вспомогательная функция для чтения настроек устройства из записей конфигурации.
//! \~\details
//! \~english
//! Tries to read "name" from parent section \a ep first, then from local section \a em, and falls back to "def" when neither exists.
//! \~russian
//! Сначала пытается прочитать "name" из родительской секции \a ep, затем из локальной секции \a em и возвращает "def", если запись не
//! найдена.
template<typename T>
T readDeviceSetting(const PIString & name, const T & def, const PIConfig::Entry * em, const PIConfig::Entry * ep) {
	PIVariant v = PIVariant::fromValue<T>(def);
	if (ep) {
		bool ex = false;
		v.setValueFromString(ep->getValue(name, def, &ex).toString());
		if (ex) return v.value<T>();
	}
	v.setValueFromString(em->getValue(name, def).toString());
	return v.value<T>();
}

#endif // PICONFIG_H
