/*==============================================================================================================/
|                   _         _                  _   ___ _   _ ___ ____                                         |
|                  | |    ___| | _____ _   _ ___( ) |_ _| \ | |_ _|  _ \ __ _ _ __ ___  ___ _ __                |
|                  | |   / _ \ |/ / __| | | / __|/   | ||  \| || || |_) / _` | '__/ __|/ _ \ '__|               |
|                  | |__|  __/   <\__ \ |_| \__ \    | || |\  || ||  __/ (_| | |  \__ \  __/ |                  |
|                  |_____\___|_|\_\___/\__, |___/   |___|_| \_|___|_|   \__,_|_|  |___/\___|_|                  |
|                                      |___/                                                                    |
/===============================================================================================================/
| All-in-one INI file parser                                                                                    |
| Provides convenient cross-platform class to load and save .INI files                                          |
| Extends classic INI-file format with                                                                          |
| -> arrays (comma (',') separated values: |val1, val2, val3|)                                                  |
| -> maps (declared as |key1:val1, key2:val2, ... , keyN:valN|)                                                 |
| -> nested sections (Section2 is considered child of Section1, if Section2 is defined as [Section1.Section2])  |
| -> file includes (use ";#include <file_path>" to include file with relative or full system path <file_path>)  |
| Please look in provided file ini-test/test1.ini for extended ini examples, in test_app.cpp for usage examples |
| Language: C++, STL used                                                                                       |
| Version:  1.1                                                                                                 |
/===============================================================================================================/
| Copyright (c) 2015 by Borovik Alexey
| MIT License
|
| Permission is hereby granted, free of charge, to any person obtaining a
| copy of this software and associated documentation files (the "Software"),
| to deal in the Software without restriction, including without limitation
| the rights to use, copy, modify, merge, publish, distribute, sublicense,
| and/or sell copies of the Software, and to permit persons to whom the
| Software is furnished to do so, subject to the following conditions:
|
| The above copyright notice and this permission notice shall be included in
| all copies or substantial portions of the Software.
|
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
| FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
| DEALINGS IN THE SOFTWARE.
/===============================================================================================================*/

#ifndef _INIPARSER_H
#define _INIPARSER_H
// NOLINTBEGIN

/*---------------------------------------------------------------------------------------------------------------/
/ Includes
/---------------------------------------------------------------------------------------------------------------*/
#include <string>     // for std::string
#include <vector>     // for std::vector
#include <map>        // for std::map
#include <sstream>    // for std::stringstream
#include <algorithm>  // for std::transform
#include <functional> // for std::not1, std::ptr_fun
#include <cctype>     // for std::isspace()
#include <ctype.h>    // for tolower() and toupper()
#include <fstream>    // for std::fstream
#include <string.h>   // for strlen()
#include <iomanip>    // for std::setprecision

#include <TNL/TypeInfo.h>   // for TNL::getType
/*---------------------------------------------------------------------------------------------------------------/
/ Defines & Settings
/---------------------------------------------------------------------------------------------------------------*/

// Error codes
#define INI_ERR_INVALID_FILENAME  -1   // Can't open file for reading or writing
#define INI_ERR_PARSING_ERROR     -2   // File parse error

// INI file syntax can be changed here
// NOTE: When saving INI files first characters of provided arrays are used

/// Each of the characters in this string opens section
#define INI_SECTION_OPEN_CHARS   "[{"
/// Each of the characters in this string closes section
#define INI_SECTION_CLOSE_CHARS  "]}"
/// Each of the characters in this string starts comment
#define INI_COMMENT_CHARS         ";#"
/// Each of the characters in this string separates entry's name from entry's value
#define INI_NAME_VALUE_SEP_CHARS "=:"
/// Each of the characters in this string when found last in input line tells that next line
/// should be considered as current line's continuation
#define INI_MULTILINE_CHARS         "\\/"

// Delimiter of values in array
#define INI_ARRAY_DELIMITER         ','
// Symbol, opening the segment of array (INI_ARRAY_DELIMITER in segment is not considered as delimiter)
#define INI_ARRAY_SEGMENT_OPEN      '{'
// Symbol, closing the segment of array
#define INI_ARRAY_SEGMENT_CLOSE     '}'
// Escape character in INI file (INI_ARRAY_SEGMENT_OPEN and INI_ARRAY_SEGMENT_CLOSE if found inside
// elements of array and maps should be escaped for proper parsing)
#define INI_ESCAPE_CHARACTER        '\\'
// Symbol, separating key from value in map
#define INI_MAP_KEY_VAL_DELIMETER   ':'
// Delimiter, separating parent name from child name in section full name
#define INI_SUBSECTION_DELIMETER    '.'
// Delimiter to separate section name from value name when getting value directly from file
#define INI_SECTION_VALUE_DELIMETER ':'

// Inclusion line
// Please note, that inclusion command must appear in comment section (i.e. ;INI_INCLUDE_SEQ)
#define INI_INCLUDE_SEQ "#include "

// Path delimiter
#ifdef WIN32
#    define SYSTEM_PATH_DELIM '\\'
#else
#    define SYSTEM_PATH_DELIM '/'
#endif

/*---------------------------------------------------------------------------------------------------------------/
/ Auxiliaries
/---------------------------------------------------------------------------------------------------------------*/

// All parser-related stuff is in INI namespace
namespace INI
{
    /// String to lower case
    static inline void string_to_lower(std::string& str)
    {
        std::transform(str.begin(), str.end(), str.begin(), ::tolower);
    }

    /// String to upper case
    static inline void string_to_upper(std::string& str)
    {
        std::transform(str.begin(), str.end(), str.begin(), ::toupper);
    }

    /// stream to be used for conversions
    class convstream: public std::stringstream
    {
    public:
        convstream() {*this << std::setprecision(17) << std::boolalpha;}
    };

    /// Convert anything (int, double etc) to string
    template<class T> std::string t_to_string(const T& i)
    {
        convstream ss;
        std::string s;
        ss << i;
        s = ss.str();
        return s;
    }
    /// Special case for string (to avoid overheat)
    template<> inline std::string t_to_string<std::string>(const std::string& i)
    { 
        return i; 
    }

    /// Convert string to anything (int, double, etc)
    template<class T> T string_to_t(const std::string& v)
    {
        convstream ss;
        T out;
        ss << v;
        ss >> out;
        if (ss.fail())
            throw std::runtime_error("Value '" + v + "' could not be converted to type " + TNL::getType<T>() + ".");
        return out;
    }
    /// Special case for string
    template<> inline std::string string_to_t<std::string>(const std::string& v)
    {
        return v;
    }
    /// Special case for boolean value
    /// std::boolalpha only covers 'true' or 'false', while we have more cases
    template<> inline bool string_to_t<bool>(const std::string& v)
    {
        if (!v.size())
            return false;
        if (v[0] == '1' || v[0] == 't' || v[0] == 'T' || v[0] == 'Y' || v[0] == 'y')
            return true;
        return false;
    }

    // Calling isspace with signed chars in Visual Studio causes assert failure in DEBUG builds
    // This hack is approved by MS itself
#if defined (_MSC_VER)
    static inline int __in_isspace(int ch) {return std::isspace((unsigned char)ch);}
#else
#    define __in_isspace std::isspace
#endif

    // Trim string from start
    static inline std::string &ltrim(std::string &s) {
        s.erase(s.begin(), std::find_if(s.begin(), s.end(), [] (int ch) { return ! __in_isspace(ch); }));
        return s;
    }

    // Trim string from end
    static inline std::string &rtrim(std::string &s) {
        s.erase(std::find_if(s.rbegin(), s.rend(), [] (int ch) { return ! __in_isspace(ch); }).base(), s.end());
        return s;
    }

    // Trim string from both ends
    static inline std::string &trim(std::string &s) {
        return ltrim(rtrim(s));
    }

    /// Split strings (and trim result) based on the provided separator
    /// Strings in the vector would be trimmed as well
    static inline std::vector<std::string> split_string(const std::string& str, const std::string& sep)
    {
        std::vector<std::string> lst;
        if (str.empty())
            return lst;
        size_t cur_pos = 0;
        size_t prev_pos = 0;
        for (; ; cur_pos+=sep.size(),prev_pos = cur_pos)
        {
            cur_pos = str.find(sep,cur_pos);
            if (cur_pos == std::string::npos)
                break;
            std::string out = str.substr(prev_pos,cur_pos-prev_pos);
            trim(out);
            lst.push_back(out);
        }
        std::string out = str.substr(prev_pos);
        trim(out);
        lst.push_back(out);
        return lst;
    }

    /// Join array of strings into one with specified separator
    static inline std::string join_string(const std::vector<std::string>& array, const std::string& sep)
    {
        std::string ret;
        if (!array.size())
            return ret;
        for (size_t i = 0; i < array.size()-1; i++)
            ret += array.at(i) + sep;
        ret += array.at(array.size()-1);
        return ret;
    }

    /// Test whether provided char is in the charset
    static inline bool char_is_one_of(int ch, const char* charset)
    {
        for (const char* it = charset; *it != '\0'; ++it)
            if (ch == *it)
                return true;
        return false;
    }

    // Replace every occurrence of @param what to @param ret in @param str
    static inline std::string& str_replace(std::string& str, const std::string& what, const std::string& rep)
    {
        int diff = rep.size() - what.size() + 1;
        for (size_t pos = 0; ;pos += diff)
        {
            pos = str.find(what.c_str(),pos);
            if (pos == std::string::npos)
                break;
            str.replace(pos,what.size(),rep);
        }
        return str;
    }

    // normalize path (for relative path to be system-independent)
    static inline void normalize_path(std::string& path)
    {
#ifdef WIN32
        str_replace(path,"/","\\");
#else
        str_replace(path,"\\","/");
#endif
    }

    // get file path (excluding file name)
    static inline std::string file_path(const std::string& file_fullname)
    {
        size_t pos = file_fullname.rfind(SYSTEM_PATH_DELIM);
        if (pos == std::string::npos)
            return std::string();
        return file_fullname.substr(0,pos);
    }

    // get file name (excluding file path)
    static inline std::string file_name(const std::string& file_fullname)
    {
        size_t pos = file_fullname.rfind(SYSTEM_PATH_DELIM);
        if (pos == std::string::npos)
            return file_fullname;
        return file_fullname.substr(pos+1);
    }

    // check whether path is absolute
    static inline bool path_is_absolute(const std::string& path)
    {
#ifdef WIN32
        if (path.size() < 2)
            return false;
        // absolute filenames should start with drive letter (i.e. c:) 
        // or network path (\\)
        if (path[1] == ':' || (path[0] == '\\' && path[1] == '\\'))
            return true;
        return false;
#else
        // everything is simple under POSIX: absolute path should start with /
        if (path.size() < 1)
            return false;
        if (path[0] == '/')
            return true;
        return false;
#endif
    }

    // check whether path is relative
    static inline bool path_is_relative(const std::string& path) {return !path_is_absolute(path);}

    /*---------------------------------------------------------------------------------------------------------------/
    / INI-related classes
    /---------------------------------------------------------------------------------------------------------------*/

    // forward declarations
    class Value;
    class Array;
    class Map;
    class Section;
    class File;
    typedef std::vector<Section*> SectionVector;
    
    /**
      * Reference-counting helper class
      * This should be used as a member in reference-counting classes Value, Array and Map
    **/
    template<class T> class RefCountPtr
    {
    public:
        RefCountPtr():_ptr(NULL){}
        RefCountPtr(const RefCountPtr& cp):_ptr(cp._ptr) {Increment();}
        RefCountPtr(const T& val):_ptr(NULL){_ptr = new refcont(val);}
        virtual ~RefCountPtr() {Decrement();}
        RefCountPtr& operator= (const RefCountPtr& rt)
        {
            Decrement();
            _ptr = rt._ptr;
            Increment();
            return *this;
        }
        bool operator== (const RefCountPtr& val) const
        {
            if (_ptr == val._ptr)
                return true;
            if (!_ptr || !val._ptr)
                return false;
            return _ptr->val == val._ptr->val;
        }
        bool operator!= (const Value& val) const { return !(*this == val);}
        bool IsValid() const {return (_ptr != NULL);}
        T* DataPtr() {return (_ptr==NULL?NULL:&_ptr->val);}
        const T* DataPtr() const { return (_ptr==NULL?NULL:&_ptr->val);}
        T& Data() {return _ptr->val;}
        const T& Data() const {return _ptr->val;}
        T DataObj(const T& defval = T()) const {return (_ptr==NULL?defval:_ptr->val);}
        T* operator->() {return DataPtr();}
        const T* operator->() const {return DataPtr();}
        void Copy()
        {
            if (!_ptr)
                _ptr = new refcont();
            else if (_ptr->count > 1)
            {
                refcont* cp = new refcont(_ptr->val);
                Decrement();
                _ptr = cp;
            }
        }
    private:
        void Decrement()
        {
            if (!_ptr)
                return;
            _ptr->count--;
            if (!_ptr->count)
            {
                delete(_ptr);
                _ptr = NULL;
            }
        }
        void Increment()
        {
            if (_ptr)
                _ptr->count++;
        }
        struct refcont
        {
            refcont() :count(1) {}
            refcont(const T& pval) :val(pval), count(1) {}
            T val;
            size_t count;
        };
        refcont* _ptr;
    };

    /**
      * Value to be stored in INI-file
      * This is a simple reference-counting class, storing a pointer to original string
      * It has some functions for easy converting to\from other types
      * Value can contain array (in string representation) and be converted to\from it 
    **/
    class Value
    {
    public:
        Value(){}
        Value(const Value& cp):_val(cp._val){}
        template<class T> Value(const T& val){Set(val);}
        Value(const char* value){Set(value);}
        virtual ~Value(){}

        Value& operator= (const Value& rt) {_val = rt._val; return *this;}
        template<class T> Value& operator= (const T& value) { Set(value); return *this;}
        bool operator== (const Value& rgh) const { return _val == rgh._val;}
        bool operator!= (const Value& val) const { return !(*this == val);}
        bool operator< (const Value& rgh) const
        {
            if (!_val.IsValid())
                return true;
            if (!rgh._val.IsValid())
                return false;
            return (_val.Data() < rgh._val.Data());
        }

        /// Template function to convert value to any type
        template<class T> T Get() const { return string_to_t<T>(_val.DataObj());}
        void Set(const std::string& str) { _val = RefCountPtr<std::string>(str); }
        /// Template function to set value
        template<class T> void Set(const T& value) { Set(t_to_string(value)); }
        // const char* (as any pointer) is defaulting in template to int or bool, which is not what we want
        void Set(const char* value) { _val = RefCountPtr<std::string>(std::string(value)); }

        /// Converts Value to std::string
        std::string AsString() const { return _val.DataObj();}
        /// Converts Value to integer
        int AsInt() const {return Get<int>();}
        /// Converts Value to double
        double AsDouble() const {return Get<double>();}
        /// Converts Value to boolean
        bool AsBool() const {return Get<bool>();}
        /// Converts Value to Array
        Array AsArray() const;
        /// Converts Value to Map
        Map AsMap() const;
        /// Converts Value to specified type T
        template<class T> T AsT() const {return Get<T>();}

    private:
        RefCountPtr<std::string> _val;
    };

    /**
      * Array of Values
      * Reference-counting class
    **/
    class Array
    {
    public:
        Array(){}
        Array(const Array& cp) :_val(cp._val){}
        Array(const std::string& str, char sep = INI_ARRAY_DELIMITER, char seg_open = INI_ARRAY_SEGMENT_OPEN,
              char seg_close = INI_ARRAY_SEGMENT_CLOSE, char esc = INI_ESCAPE_CHARACTER)
        {
            FromString(str,sep,seg_open,seg_close,esc);
        }
        template<class T> Array(const std::vector<T>& vect){FromVector(vect);} 
        virtual ~Array() {}

        Array& operator= (const Array& rt) {_val = rt._val; return *this;}
        Array& operator<< (const Value & val) { return PushBack(val);}
        /// Returns reference to value on specified position @param pos
        /// Array is automatically widen (if needed) for that operation to always succeed
        Value& operator[] (size_t pos)
        {
            _val.Copy();
            if (pos >= _val->size())
                _val->insert(_val->end(),pos-_val->size()+1,Value());
            return _val.Data()[pos];
        }

        /// Gets value with specified position @param pos
        /// If @param pos is >= Size() returns @param def_val
        Value GetValue(size_t pos, const Value& def_val = Value()) const
        {
            if (!_val.IsValid())
                return def_val;
            if (pos >= _val->size())
                return def_val;
            return _val->at(pos);
        }
        /// Sets value @param value to specified position @param pos
        /// Array is automatically widen (if needed) to allow this operation 
        void SetValue(size_t pos, const Value& value)
        {
            _val.Copy();
            if (pos >= _val->size())
            {
                _val->insert(_val->end(),pos-_val->size(),Value());
                _val->push_back(value);
            }
            else
                _val->at(pos) = value;
        }
        /// Adds @param val to the end of the Array
        Array& PushBack(const Value& val) 
        {
            _val.Copy();
            _val->push_back(val); 
            return *this;
        }
        /// Returns array size
        size_t Size() const { if (!_val.IsValid()) return 0; return _val->size(); }
        
        /// Formats string with all values of this array represented as strings, separated by @param sep
        std::string ToString(char sep = INI_ARRAY_DELIMITER, char seg_open = INI_ARRAY_SEGMENT_OPEN,
                             char seg_close = INI_ARRAY_SEGMENT_CLOSE, char esc = INI_ESCAPE_CHARACTER) const
        {
            std::string ret;
            if (!_val.IsValid())
                return ret;
            for (size_t i = 0; i < _val->size(); i++)
            {
                std::string tmp = _val->at(i).AsString();
                std::string out;
                bool has_delim = false;
                for (size_t j = 0; j < tmp.size(); j++)
                {
                    if (tmp[j] == seg_open || tmp[j] == seg_close)
                        out.push_back(esc);
                    else if (tmp[j] == sep)
                        has_delim = true;
                    out.push_back(tmp[j]);
                }
                if (has_delim)
                    ret += seg_open + out + seg_close;
                else
                    ret += out;
                if (i != _val->size()-1)
                    ret += sep;
            }
            return ret;
        }
        /// Fills array with values from string @param str, separated by @param sep
        void FromString(const std::string& str, char sep = INI_ARRAY_DELIMITER, 
                        char seg_open  = INI_ARRAY_SEGMENT_OPEN,
                        char seg_close = INI_ARRAY_SEGMENT_CLOSE, char esc = INI_ESCAPE_CHARACTER)
        {
            _val.Copy();
            _val->clear();
            if (str.empty())
                return;
            std::string cur_str;
            int segm_cnt = 0;
            bool escaped = false;
            int preesc = 0;
            for (size_t i = 0; i <= str.size(); ++i)
            {
                if (escaped && i < str.size())
                {
                    cur_str.push_back(str[i]);
                    escaped = false;
                    if (str[i] == esc)
                        preesc = 2;
                }
                else if ((i == str.size()) || (str[i] == sep && !segm_cnt))
                {
                    trim(cur_str);
                    _val->push_back(cur_str);
                    cur_str.clear();
                }
                else if (str[i] == seg_open && !preesc)
                {
                    if (segm_cnt)
                        cur_str.push_back(str[i]);
                    segm_cnt++;
                }
                else if (str[i] == seg_close && !preesc)
                {
                    segm_cnt--;
                    if (segm_cnt < 0)
                        segm_cnt = 0;
                    if (segm_cnt)
                        cur_str.push_back(str[i]);
                }
                else if (str[i] == esc)
                    escaped = true;
                else
                    cur_str.push_back(str[i]);
                if (preesc)
                    preesc--;
            }
        }
        template<class T> std::vector<T> ToVector() const
        {
            std::vector<T> ret;
            if (!_val.IsValid())
                return ret;
            for (size_t i = 0; i < _val->size(); ++i)
                ret.push_back(_val->at(i).AsT<T>());
            return ret;
        }
        template<class T> void FromVector(const std::vector<T>& vect)
        {
            _val.Copy();
            _val->clear();
            for (size_t i = 0; i < vect.size(); ++i)
                _val->push_back(Value(vect.at(i)));
        }
        /// Converts Array to Value
        Value ToValue() const
        {
            return Value(ToString());
        }
        /// Gets Array from Value
        void FromValue(const Value& val)
        {
            FromString(val.AsString());
        }
    private:
        RefCountPtr<std::vector<Value> > _val;
    };

    template<> inline void Value::Set<Array> (const Array& value)
    {
        Set(value.ToString());
    }
    template<> inline Array Value::Get<Array>() const
    {
        if (!_val.IsValid())
            return Array();
        return Array(_val.Data());
    }
    inline Array Value::AsArray() const {return Get<Array>();}

    /**
      * Map of Values
      * Reference-counting class
    **/
    class Map
    {
    public:
        Map(){}
        Map(const Map& cp) :_val(cp._val){}
        Map(const std::string& str, char sep = INI_ARRAY_DELIMITER, char seg_open = INI_ARRAY_SEGMENT_OPEN,
              char seg_close = INI_ARRAY_SEGMENT_CLOSE, char kval = INI_MAP_KEY_VAL_DELIMETER,
              char esc = INI_ESCAPE_CHARACTER)
        {
            FromString(str,sep,seg_open,seg_close,kval,esc);
        }
        template<class T, class M> Map(const std::map<T,M>& mp){FromMap(mp);} 
        virtual ~Map(){}
        Map& operator= (const Map& rt) {_val = rt._val; return *this;}
        /// Returns reference to value of the specified key @param key
        /// Map is automatically inserts entry for this operation to always succeed
        Value& operator[] (const Value& key)
        {
            _val.Copy();
            return _val.Data()[key];
        }

        /// Gets value for specified key @param key
        /// If there is no specified key - returns @param def_val
        Value GetValue(const Value& key, const Value& def_val = Value()) const
        {
            if (!_val.IsValid())
                return def_val;
            std::map<Value,Value>::const_iterator it = _val->find(key);
            if (it == _val->end())
                return def_val;
            return it->second;
        }
        /// Sets value @param value to specified key @param key
        void SetValue(const Value& key, const Value& value)
        {
            _val.Copy();
            std::pair<std::map<Value,Value>::iterator,bool> res = 
                _val->insert(std::pair<Value,Value>(key,value));
            if (!res.second)
                res.first->second = value;
        }
        /// Returns map size
        size_t Size() const { if (!_val.IsValid()) return 0; return _val->size();}


        /// Formats string with all values of this map
        std::string ToString(char sep = INI_ARRAY_DELIMITER, char seg_open = INI_ARRAY_SEGMENT_OPEN,
                             char seg_close = INI_ARRAY_SEGMENT_CLOSE, char kval = INI_MAP_KEY_VAL_DELIMETER, 
                             char esc = INI_ESCAPE_CHARACTER) const
        {
            std::string ret;
            if (!_val.IsValid())
                return ret;
            for (std::map<Value,Value>::const_iterator it = _val->begin(); it != _val->end(); ++it)
            {
                if (it != _val->begin())
                    ret += sep;
                std::string key = it->first.AsString();
                std::string out_key;
                std::string val = it->second.AsString();
                std::string out_val;
                // key
                bool has_delim = false;
                for (size_t j = 0; j < key.size(); j++)
                {
                    if (key[j] == seg_open || key[j] == seg_close)
                        out_key.push_back(esc);
                    else if (key[j] == kval || key[j] == sep)
                        has_delim = true;
                    out_key.push_back(key[j]);
                }
                if (has_delim)
                    out_key = seg_open + out_key + seg_close;
                // value
                has_delim = false;
                for (size_t j = 0; j < val.size(); j++)
                {
                    if (val[j] == seg_open || val[j] == seg_close)
                        out_val.push_back(esc);
                    else if (val[j] == kval || val[j] == sep)
                        has_delim = true;
                    out_val.push_back(val[j]);
                }
                if (has_delim)
                    out_val = seg_open + out_val + seg_close;
                // pair
                ret += out_key + kval + out_val;
            }
            return ret;
        }
        /// Fills map with values from string @param str
        void FromString(const std::string& str, char sep = INI_ARRAY_DELIMITER, 
                        char seg_open  = INI_ARRAY_SEGMENT_OPEN,
                        char seg_close = INI_ARRAY_SEGMENT_CLOSE, char kval = INI_MAP_KEY_VAL_DELIMETER,
                        char esc = INI_ESCAPE_CHARACTER)
        {
            _val.Copy();
            _val->clear();
            if (str.empty())
                return;
            std::string cur_str;
            std::string cur_key;
            int segm_cnt = 0;
            bool escaped = false;
            int preesc = 0;
            for (size_t i = 0; i <= str.size(); ++i)
            {
                if (escaped && i < str.size())
                {
                    cur_str.push_back(str[i]);
                    escaped = false;
                    if (str[i] == esc)
                        preesc = 2;
                }
                else if ((i==str.size()) || (str[i] == sep && !segm_cnt))
                {
                    trim(cur_str);
                    std::pair<std::map<Value,Value>::iterator,bool> res = 
                        _val->insert(std::pair<Value,Value>(Value(cur_key),Value(cur_str)));
                    if (!res.second)
                        res.first->second = cur_str;
                    cur_str.clear();
                    cur_key.clear();
                }
                else if (str[i] == kval && !segm_cnt)
                {
                    trim(cur_str);
                    cur_key = cur_str;
                    cur_str.clear();
                }
                else if (str[i] == seg_open && !preesc)
                {
                    if (segm_cnt)
                        cur_str.push_back(str[i]);
                    segm_cnt++;
                }
                else if (str[i] == seg_close && !preesc)
                {
                    segm_cnt--;
                    if (segm_cnt < 0)
                        segm_cnt = 0;
                    if (segm_cnt)
                        cur_str.push_back(str[i]);
                }
                else if (str[i] == esc)
                    escaped = true;
                else
                    cur_str.push_back(str[i]);
                if (preesc)
                    preesc--;
            }
        }
        template<class T, class M> std::map<T,M> ToMap() const
        {
            std::map<T,M> ret;
            if (!_val.IsValid())
                return ret;
            for (auto it = _val->begin(); it != _val->end(); ++it)
                ret.insert(std::pair<T,M>(it->first.AsT<T>(),it->second.AsT<M>()));
            return ret;
        }
        template<class T, class M> void FromMap(const std::map<T,M>& mp)
        {
            _val.Copy();
            _val->clear();
            for (typename std::map<T,M>::const_iterator it = mp.begin(); it != mp.end(); ++it)
                _val->insert(std::pair<Value,Value>(Value(it->first),Value(it->second)));
        }
        /// Converts Array to Value
        Value ToValue() const
        {
            return Value(ToString());
        }
        /// Gets Array from Value
        void FromValue(const Value& val)
        {
            FromString(val.AsString());
        }
    private:
        RefCountPtr<std::map<Value,Value> > _val;
    };

    template<> inline void Value::Set<Map> (const Map& value)
    {
        Set(value.ToString());
    }
    template<> inline Map Value::Get<Map>() const
    {
        if (!_val.IsValid())
            return Map();
        return Map(_val.Data());
    }
    inline Map Value::AsMap() const {return Get<Map>();}

    /*---------------------------------------------------------------------------------------------------------------/
    / INI file creation and parsing
    /---------------------------------------------------------------------------------------------------------------*/

    /**
      * One section of the ini-file
      * This can be created by INIFile class only
    **/
    class Section
    {
        friend class File;
        typedef std::map<std::string, Value> EntryMap;
        typedef std::pair<std::string,Value> EntryPair;
        typedef std::map<std::string, std::string> CommentMap;
        typedef std::pair<std::string, std::string> CommentPair;
    /*-----------------------------------------------------------------------------------------------------------/
    / General functions & iterators
    /-----------------------------------------------------------------------------------------------------------*/
    public:
        typedef EntryMap::iterator values_iter;
        typedef EntryMap::const_iterator const_values_iter;
        values_iter ValuesBegin() {return _entries.begin();}
        const_values_iter ValuesBegin() const {return _entries.begin();}
        values_iter ValuesEnd() {return _entries.end();}
        const_values_iter ValuesEnd() const {return _entries.end();}
        size_t ValuesSize() const {return _entries.size();}

        /// Returns full section name (subsection name will contain '.', separating subsection part)
        const std::string& FullName() const { return _name;}
        /// Return section name (will be parsed to get individual section name)
        std::string Name() const
        {
            return _name.substr(_name.rfind(INI_SUBSECTION_DELIMETER)+1);
        }
        /// Get comment, associated with this section (written before the line with the section, or after it)
        std::string Comment() const {return _comment;}
        /// Return all keys in this section
        std::vector<std::string> GetSectionKeys() const
        {
            std::vector<std::string> keys;
            for (EntryMap::const_iterator it = _entries.begin(); it != _entries.end(); it++)
                keys.push_back(it->first);
            return keys;
        }
        /// Get Value
        Value GetValue(const std::string& key, const Value& def_value = Value()) const
        {
            EntryMap::const_iterator it = _entries.find(key);
            if (it == _entries.end())
                return def_value;
            return it->second;
        }
        /// Set Value
        void SetValue(const std::string& key, const Value& val, const std::string& comment = std::string())
        {
            _entries[key] = val;
            if (!comment.empty())
                _comments[key] = comment;
        }
        /// Set Value to array
        void SetArrayValue(const std::string& key, size_t pos, const Value& val)
        {
            Array ar = GetValue(key).AsArray();
            ar.SetValue(pos,val);
            SetValue(key,ar);
        }
        /// Remove value
        void RemoveValue(const std::string& key)
        {
            EntryMap::iterator it = _entries.find(key);
            if (it == _entries.end())
                return;
            _entries.erase(it);
        }
        /// Get comment, associated with provided value
        std::string GetComment(const std::string& key) const
        {
            CommentMap::const_iterator it = _comments.find(key);
            if (it == _comments.end())
                return std::string();
            return it->second;
        }
        /// Set comment for specified key
        void SetComment(const std::string& key, const std::string& comment)
        {
            _comments[key] = comment;
        }
        /// Save the section's contents to specified stream
        void Save(std::ostream& stream) const;
    /*-----------------------------------------------------------------------------------------------------------/
    / Parent & child parsing
    /-----------------------------------------------------------------------------------------------------------*/
    public:
        Section* FindParent() const;
        Section* GetParent();
        Section* FindSubSection(const std::string& name) const;
        Section* GetSubSection(const std::string& name);
        SectionVector FindSubSections() const;
    /*-----------------------------------------------------------------------------------------------------------/
    / Internal contents
    /-----------------------------------------------------------------------------------------------------------*/
    private:
        /// Only INI::File can create sections
        Section(File* file, const std::string& name, const std::string& comment = std::string()):
           _file(file),_name(name),_comment(comment){}
        /// Class should be copied only by File class to properly handle _file
        Section(const Section& cp):_file(cp._file),_name(cp._name),_comment(cp._comment),_entries(cp._entries),
            _comments(cp._comments){}
        File*       _file;                   // file, this section is associated with
        std::string _name;                 // name of the section
        std::string _comment;              // comment to the section
        EntryMap    _entries;              // all entries in the section
        CommentMap  _comments;             // all comments, associated with values in the section
    };

    /**
      * Main class of the parser
      * Provides way to load and save ini-files, as well as
      * setting specific values in them
    **/
    class File
    {
    public:
        /// Sections stores all values and comments inside them
        typedef std::map<std::string, Section*> SectionMap;
        typedef std::pair<std::string, Section*> SectionPair;
        typedef SectionMap::iterator          sections_iter;
        typedef SectionMap::const_iterator    const_sections_iter;
        /// Result of previous parse operation
        struct PResult
        {
            PResult():error_code(0),error_line_num(0){}
            void Set(int code, int line_num = 0, const std::string& line = std::string())
            {
                error_code = code;
                error_line_num = line_num;
                error_line = line;
            }
            void Invalidate() {error_code = 0; error_line_num = 0; error_line.clear();}
            std::string GetErrorDesc() const
            {
                if (!error_code)
                    return "No error";
                if (error_code == INI_ERR_INVALID_FILENAME)
                    return std::string("Failed to open file ") + file_name + "!";
                if (error_code == INI_ERR_PARSING_ERROR)
                    return std::string("Parse error in file ") + file_name + " on line " 
                    + t_to_string(error_line) + ": \"" + error_line + "\""; 
                return "Unknown error!";
            }
            int error_code;         // code of the error. 0 if no error
            int error_line_num;     // number of line with the error
            std::string error_line; // line with error
            std::string file_name;  // name of the file with error (will be empty for stream)
            operator bool() const {return !error_code;}
        };
    public:
        File(){}
        File(const std::string& fname) { Load(fname);}
        File(const File& lf) { CopyFrom(lf); }
        File& operator= (const File& lf) { Unload(); CopyFrom(lf); return *this; }
        void CopyFrom(const File& lf)
        {
            for (SectionMap::const_iterator it = lf._sections.begin(); it != lf._sections.end(); it++)
            {
                Section* sect = new Section(*it->second);
                sect->_file = this;
                _sections.insert(SectionPair(it->first,sect));
            }
            _result = lf._result;
        }
        virtual ~File() {Unload();}
    /*---------------------------------------------------------------------------------------------------------------/
    / Section & values manipulations
    /---------------------------------------------------------------------------------------------------------------*/
    public:
        /// A way to iterate through all sections
        /// Section pointer can be accesed as SectionMap::iterator::second, section name - as ::first
        size_t SectionsSize() const {return _sections.size();}
        sections_iter SectionsBegin() {return _sections.begin();}
        const_sections_iter SectionsBegin() const {return _sections.begin();}
        sections_iter SectionsEnd() {return _sections.end();}
        const_sections_iter SectionsEnd() const {return _sections.end();}

        /// Get value from the file
        /// Use INI_SECTION_VALUE_DELIMETER to separate section name from value name
        Value GetValue(const std::string& name, const Value& def_val = Value())
        {
            size_t pos = name.rfind(INI_SECTION_VALUE_DELIMETER);
            std::string nm;
            if (pos != std::string::npos)
                nm = name.substr(0,pos);
            SectionMap::iterator it = _sections.find(nm);
            if (it == _sections.end())
                return def_val;
            return it->second->GetValue(name.substr(pos+1),def_val);
        }

        /// Set value to the file
        /// Use INI_SECTION_VALUE_DELIMETER to separate section name from value name
        void SetValue(const std::string& name, const Value& value, const std::string& comment = std::string())
        {
            size_t pos = name.rfind(INI_SECTION_VALUE_DELIMETER);
            std::string nm;
            if (pos != std::string::npos)
                nm = name.substr(0,pos);
            Section* sect = GetSection(nm);
            sect->SetValue(name.substr(pos+1),value,comment);
        }

        /// Set array Value to array
        void SetArrayValue(const std::string& key, size_t pos, const Value& val)
        {
            Array ar = GetValue(key).AsArray();
            ar.SetValue(pos,val);
            SetValue(key,ar);
        }

        /// Returns pointer to section with specified name
        /// If section does not exists - creates it
        Section* GetSection(const std::string& name)
        {
            SectionMap::iterator it = _sections.find(name);
            if (it != _sections.end())
                return it->second;
            Section* sc = new Section(this,name);
            _sections.insert(SectionPair(name,sc));
            return sc;
        }

        /// Find existing section by name
        /// @return pointer to existing section or NULL
        Section* FindSection(const std::string& name) const
        {
            SectionMap::const_iterator it = _sections.find(name);
            if (it == _sections.end())
                return NULL;
            return it->second;
        }

        /// Deletes section with specified name
        void DeleteSection(const std::string& name)
        {
            SectionMap::iterator it = _sections.find(name);
            if (it != _sections.end())
                _sections.erase(it);
        }

        /// Get subsection of specified section with specified name
        Section* FindSubSection(const Section* sect, const std::string& name) const
        {
            return FindSection(sect->FullName() + INI_SUBSECTION_DELIMETER + name);
        }

        /// If subsection does not exists, creates it
        Section* GetSubSection(Section* sect, const std::string& name)
        {
            return GetSection(sect->FullName() + INI_SUBSECTION_DELIMETER + name);
        }

        /// Get parent section of the specified section
        Section* FindParentSection(const Section* sect) const
        {
            size_t pos = sect->FullName().rfind(INI_SUBSECTION_DELIMETER);
            std::string nm;
            if (pos != std::string::npos)
                nm = sect->FullName().substr(0,pos);
            return FindSection(nm);
        }
        /// Get parent section (created if needed)
        Section* GetParentSection(const Section* sect)
        {
            size_t pos = sect->FullName().rfind(INI_SUBSECTION_DELIMETER);
            std::string nm;
            if (pos != std::string::npos)
                nm = sect->FullName().substr(0,pos);
            return GetSection(nm);
        }
        /// Find subsections of the specified section
        SectionVector FindSubSections(const Section* sect) const
        {
            SectionVector ret;
            if (sect->_file != this)
                return ret;
            for (SectionMap::const_iterator it = _sections.begin(); it != _sections.end(); ++it)
            {
                if (it->second == sect)
                    continue;
                if (it->first.find(sect->FullName() + INI_SUBSECTION_DELIMETER) != std::string::npos)
                    ret.push_back(it->second);
            }
            return ret;
        }
        // Get top-level sections (not child of any other sections)
        SectionVector GetTopLevelSections() const
        {
            SectionVector ret;
            for (SectionMap::const_iterator it = _sections.begin(); it != _sections.end(); ++it)
            {
                if (it->first.find(INI_SUBSECTION_DELIMETER) == std::string::npos)
                    ret.push_back(it->second);
            }
            return ret;
        }
    /*---------------------------------------------------------------------------------------------------------------/
    / Load & Save functions
    /---------------------------------------------------------------------------------------------------------------*/
    public:
        /// Load file from input stream, that must be properly opened and prepared for reading
        /// Set @param unload_prev to true for unloading any stuff currently in memory before loading 
        /// Set @param rpath to be used as relative path when searching for inclusions in files
        /// @return 1 if load succeeds, 0 if not
        /// in case load was not succesfull you can access extended information by calling ParseResult()
        int Load(std::istream& stream, bool unload_prev = false, const std::string& rpath = std::string())
        {
            if (unload_prev)
                DeleteSections(_sections);
            return ParseStream(stream,"",rpath,_sections);
        }
        /// Load ini from file in system
        /// Set @param unload_prev to false for not unloading any stuff currently in memory before loading 
        int Load(const std::string& fname, bool unload_prev = true)
        {
            _result.file_name = fname;
            normalize_path(_result.file_name);
            std::ifstream file(_result.file_name.c_str(), std::ios::in);
            if (!file.is_open())
            {
                _result.Set(INI_ERR_INVALID_FILENAME);
                return 0;
            }
            return Load(file,unload_prev,file_path(_result.file_name));
        }

        /// Save ini file to stream
        void Save(std::ostream& stream) const
        {
            SaveStream(stream,_sections); 
        }

        /// Save ini file to file
        int Save(const std::string& fname)
        {
            _result.file_name = fname;
            normalize_path(_result.file_name);
            std::ofstream file(_result.file_name.c_str(), std::ios::out);
            if (!file.is_open())
            {
                _result.Set(INI_ERR_INVALID_FILENAME);
                return 0;
            }
            SaveStream(file,_sections);
            return 1;
        }

        /// Save only one section to specifed stream
        void Save(std::ostream& stream, const Section* sect) const
        {
            SectionMap mp;
            mp.insert(SectionPair(sect->FullName(),const_cast<Section*>(sect)));
            SaveStream(stream,mp);
        }

        /// Adds comment line to provided stream
        /// For it to be not associated with anything you should add newline after it
        static void AddCommentToStream(std::ostream& stream, const std::string& str)
        {
            std::vector<std::string> ar = split_string(str,"\n");
            for (size_t i = 0; i < ar.size(); i++)
                stream << INI_COMMENT_CHARS[0] << ar.at(i) << std::endl;
        }

        /// Adds inclusion line to stream
        static void AddIncludeToStream(std::ostream& stream, const std::string& path)
        {
            stream << INI_COMMENT_CHARS[0] << INI_INCLUDE_SEQ << path << std::endl;
        }

        /// Unload memory
        void Unload()
        {
            DeleteSections(_sections);
        }
        /// Return last operation result
        const PResult& LastResult() {return _result;}
    /*---------------------------------------------------------------------------------------------------------------/
    / Parsing & Saving internals
    /---------------------------------------------------------------------------------------------------------------*/
    protected:
        enum LineType
        {
            EMPTY,
            SECTION,
            ENTRY,
            COMMENT,
            ERROR
        };

        /**
          * Parse input line
          * Line must be concatenated (if needed) and trimmed before passing to this function
          * @param input_line - Line to parse
          * @param section - will contain section name in case SECTION return
          * @param key - will contain key in case ENTRY return
          * @param value - will contain value in case ENTRY return
          * @param comment - will contain comment in case of return != EMPTY and != ERROR
          * @return type of the parsed line
        **/
        LineType ParseLine(const std::string& input_line, std::string& section, 
                           std::string& key, std::string& value, std::string& comment) const
        {
            LineType ret = EMPTY;
            size_t last_pos = input_line.npos;
            if (input_line.empty())
                return ret;
            // comment parsing
            if (char_is_one_of(input_line.at(0),INI_COMMENT_CHARS))
            {
                ret = COMMENT;
                // Can't do it that way, cause of 'initialization of non-const reference of type from a temporary of type'
                // stupid GCC rule
                //comment = trim(input_line.substr(1));
                comment = input_line.substr(1);
                trim(comment);
                return ret;
            }
            // section parsing
            else if (char_is_one_of(input_line.at(0),INI_SECTION_OPEN_CHARS))
            {
                last_pos = input_line.find_first_of(INI_SECTION_CLOSE_CHARS);
                if (last_pos == input_line.npos)
                    return ERROR;
                ret = SECTION;
                section = input_line.substr(1,last_pos-1);
                trim(section);
                last_pos++;
            }
            // key-value pair parsing
            else
            {
                size_t pos = input_line.find_first_of(INI_NAME_VALUE_SEP_CHARS);
                // not section, not comment, not empty - error
                if (pos == input_line.npos || pos == input_line.size()-1)
                    return ERROR;
                ret = ENTRY;
                key = input_line.substr(0,pos);
                trim(key);
                last_pos = input_line.find_first_of(INI_COMMENT_CHARS,pos+1);
                value = input_line.substr(pos+1,last_pos-pos-1);
                trim(value);
                if (last_pos != input_line.npos)
                    last_pos--;
            }
            // get associated comment
            last_pos = input_line.find_first_of(INI_COMMENT_CHARS,last_pos);
            if (last_pos != input_line.npos)
            {
                comment = input_line.substr(last_pos+1);
                trim(comment);
            }
            return ret;
        }

        /// Parse provided input stream to specified section map
        /// Comments separated from closest section or key-value pair by 1 or more empty strings
        /// would be ignored
        /// Default section will be created if needed
        int ParseStream(std::istream& stream, const std::string& def_section, 
                        const std::string& rpath, SectionMap& pmap)
        {
            Section* cur_sect = NULL;
            std::string pcomment;
            std::string prev_line;
            _result.Invalidate();

            // Find whether default section already exists in provided map
            // if not - it will be created later if needed
            SectionMap::iterator it = pmap.find(def_section);
            if (it != pmap.end())
                cur_sect = it->second;

            for (int lnc = 1; !stream.eof(); lnc++)
            {
                std::string line;
                std::getline(stream,line);
                trim(line);
                if (line.empty())
                {
                    pcomment.clear();
                    continue;
                }
                // Handle multiline strings
                if (char_is_one_of(line.at(line.size()-1),INI_MULTILINE_CHARS))
                {
                    prev_line = prev_line + line.substr(0,line.size()-1);
                    continue;
                }
                else if (!prev_line.empty())
                {
                    line = prev_line + line;
                    prev_line.clear();
                }
                std::string section_key, value, comment;
                LineType lt = ParseLine(line,section_key,section_key,value,comment);
                if (lt == EMPTY)
                {
                    pcomment.clear();
                    continue;
                }
                else if (lt == ERROR)
                {
                    _result.Set(INI_ERR_PARSING_ERROR,lnc,line);
                    return 0;
                }
                // Handle inclusion
                else if (lt == COMMENT && comment.substr(0,strlen(INI_INCLUDE_SEQ)) == INI_INCLUDE_SEQ)
                {
                    std::string incname = comment.substr(strlen(INI_INCLUDE_SEQ));
                    trim(incname);
                    normalize_path(incname);
                    // try to open file
                    std::string fpath;
                    if (path_is_relative(incname) && !rpath.empty())
                        fpath = rpath + SYSTEM_PATH_DELIM + incname;
                    else
                        fpath = incname;
                    std::ifstream file(fpath.c_str(), std::ios::in);
                    std::string prevfn = _result.file_name;
                    _result.file_name = fpath;
                    if (!file.is_open())
                    {
                        _result.Set(INI_ERR_INVALID_FILENAME,lnc,line);
                        return 0;
                    }
                    std::string scname; 
                    if (cur_sect)
                        scname = cur_sect->FullName();
                    else
                        scname = def_section;
                    if (!ParseStream(file,scname,file_path(fpath),pmap))
                        return 0;
                    _result.file_name = prevfn;
                    continue;
                }
                // Add comment (it can be set with any string type, other than EMPTY and ERROR)
                pcomment += comment;
                // Add section (or modify comment of existing one if needed)
                if (lt == SECTION)
                {
                    SectionMap::iterator it = pmap.find(section_key);
                    if (it == pmap.end())
                    {
                        cur_sect = new Section(this,section_key,pcomment);
                        pmap.insert(SectionPair(section_key, cur_sect));
                    }
                    else
                    {
                        cur_sect = it->second;
                        if (!pcomment.empty())
                        {
                            if (!it->second->_comment.empty())
                                it->second->_comment += stream.widen('\n') + pcomment;
                            else
                                it->second->_comment = pcomment;
                        }
                    }
                    pcomment.clear();
                }
                else if (lt == ENTRY)
                {
                    // Try to create default section if it is not already in the array
                    if (!cur_sect)
                    {
                        cur_sect = new Section(this,def_section);
                        pmap.insert(SectionPair(def_section, cur_sect));
                    }
                    cur_sect->SetValue(section_key,value,pcomment);
                    pcomment.clear();
                }
            }
            // Clears eof flag for future usage of stream
            stream.clear();
            return 1;
        }
        /// Save provided section map to stream
        void SaveStream(std::ostream& stream, const SectionMap& pmap) const
        {
            for (SectionMap::const_iterator it = pmap.begin(); it != pmap.end(); ++it)
            {
                if (it->second->ValuesSize() == 0)
                    continue;
                if (!it->second->Comment().empty())
                    AddCommentToStream(stream,it->second->Comment());
                if (!it->first.empty())
                    stream << INI_SECTION_OPEN_CHARS[0] << it->first << INI_SECTION_CLOSE_CHARS[0] << std::endl;
                for (Section::values_iter vit = it->second->ValuesBegin(); vit != it->second->ValuesEnd(); vit++)
                {
                    stream << vit->first << " " << INI_NAME_VALUE_SEP_CHARS[0] << " " << vit->second.AsString();
                    std::string cmn = it->second->GetComment(vit->first);
                    if (!cmn.empty())
                        stream << " " << INI_COMMENT_CHARS[0] << cmn;
                    stream << std::endl;
                }
                stream << std::endl;
            }
        }
    private:
        void DeleteSections(SectionMap& mp)
        {
            for (SectionMap::iterator it = mp.begin(); it != mp.end(); ++it)
                delete(it->second);
            mp.clear();
        }
        // All sections (including subsections) in one map
        SectionMap        _sections;
        PResult       _result;
    };

    /*-----------------------------------------------------------------------------------------------------------/
    / Some functions left unimplemented
    /-----------------------------------------------------------------------------------------------------------*/
    inline Section* Section::GetParent() {return _file->GetParentSection(this);}
    inline Section* Section::FindParent() const {return _file->FindParentSection(this);}
    inline Section* Section::GetSubSection(const std::string& name) {return _file->GetSubSection(this,name);}
    inline Section* Section::FindSubSection(const std::string& name) const {return _file->FindSubSection(this,name);}
    inline SectionVector Section::FindSubSections() const {return _file->FindSubSections(this);}
    inline void Section::Save(std::ostream& stream) const {return _file->Save(stream,this);}
}
/*---------------------------------------------------------------------------------------------------------------/
/ Stream operators
/---------------------------------------------------------------------------------------------------------------*/
static inline std::ostream& operator<< (std::ostream& stream, const INI::File& file)
{
    file.Save(stream);
    return stream;
}
static inline std::ostream& operator<< (std::ostream& stream, const INI::Section* sect)
{
    sect->Save(stream);
    return stream;
}
static inline std::ostream& operator<< (std::ostream& stream, const INI::Array& ar)
{
    stream << ar.ToValue().AsString();
    return stream;
}
static inline std::istream& operator>> (std::istream& stream, INI::File& file)
{
    file.Load(stream,false);
    return stream;
}

/*---------------------------------------------------------------------------------------------------------------/
/ Little tidying up
/---------------------------------------------------------------------------------------------------------------*/
#undef INI_SECTION_OPEN_CHARS
#undef INI_SECTION_CLOSE_CHARS
#undef INI_COMMENT_CHARS
#undef INI_NAME_VALUE_SEP_CHARS
#undef INI_MULTILINE_CHARS
#undef INI_ARRAY_DELIMITER
#undef INI_ARRAY_SEGMENT_OPEN
#undef INI_ARRAY_SEGMENT_CLOSE
#undef INI_ESCAPE_CHARACTER
#undef INI_MAP_KEY_VAL_DELIMETER
#undef INI_SUBSECTION_DELIMETER
#undef INI_SECTION_VALUE_DELIMETER
#undef INI_INCLUDE_SEQ
#undef SYSTEM_PATH_DELIM
// Note: error definitions are left

// NOLINTEND
#endif
