#ifndef INI_READER_H_INCLUDED
#define INI_READER_H_INCLUDED

#include <string>
#include <sstream>
#include <map>
#include <vector>
#include <numeric>

#include "misc.h"

enum
{
    INIREADER_EXCEPTION_EMPTY = -5,
    INIREADER_EXCEPTION_DUPLICATE,
    INIREADER_EXCEPTION_OUTOFBOUND,
    INIREADER_EXCEPTION_NOTEXIST,
    INIREADER_EXCEPTION_NOTPARSED,
    INIREADER_EXCEPTION_NONE
};

typedef std::map<std::string, std::multimap<std::string, std::string>> ini_data_struct;
typedef std::multimap<std::string, std::string> string_multimap;
typedef std::vector<std::string> string_array;
typedef std::string::size_type string_size;

class INIReader
{
    /**
    *  @brief A simple INI reader which utilize map and vector
    *  to store sections and items, allowing access in logarithmic time.
    */
private:
    /**
    *  @brief Internal parsed flag.
    */
    bool parsed = false;
    std::string current_section;
    ini_data_struct ini_content;
    string_array exclude_sections, include_sections, direct_save_sections;
    string_array section_order;

    std::string cached_section;
    ini_data_struct::iterator cached_section_content;

    std::string isolated_items_section;

    //error flags
    int last_error = INIREADER_EXCEPTION_NONE;
    unsigned int last_error_index = 0;

    inline int __priv_save_error_and_return(int x)
    {
        last_error = x;
        return last_error;
    }

    inline bool __priv_chk_ignore(const std::string &section)
    {
        bool excluded = false, included = false;
        excluded = std::find(exclude_sections.cbegin(), exclude_sections.cend(), section) != exclude_sections.cend();
        if(include_sections.size())
            included = std::find(include_sections.cbegin(), include_sections.cend(), section) != include_sections.cend();
        else
            included = true;

        return excluded || !included;
    }

    inline bool __priv_chk_direct_save(const std::string &section)
    {
        return std::find(direct_save_sections.cbegin(), direct_save_sections.cend(), section) != direct_save_sections.cend();
    }

    inline std::string __priv_get_err_str(int error)
    {
        switch(error)
        {
        case INIREADER_EXCEPTION_EMPTY:
            return "Empty document";
        case INIREADER_EXCEPTION_DUPLICATE:
            return "Duplicate section";
        case INIREADER_EXCEPTION_NOTEXIST:
            return "Target does not exist";
        case INIREADER_EXCEPTION_OUTOFBOUND:
            return "Item exists outside of any section";
        case INIREADER_EXCEPTION_NOTPARSED:
            return "Parse error";
        default:
            return "Undefined";
        }
    }
public:
    /**
    *  @brief Set this flag to true to do a UTF8-To-GBK conversion before parsing data. Only useful in Windows.
    */
    bool do_utf8_to_gbk = false;

    /**
    *  @brief Set this flag to true so any line within the section will be stored even it doesn't follow the "name=value" format.
    * These lines will store as the name "{NONAME}".
    */
    bool store_any_line = false;

    /**
    *  @brief Save isolated items before any section definitions.
    */
    bool store_isolated_line = false;

    /**
    *  @brief Allow a section title to appear multiple times.
    */
    bool allow_dup_section_titles = false;

    /**
    *  @brief Keep an empty section while parsing.
    */
    bool keep_empty_section = true;

    /**
    *  @brief Initialize the reader.
    */
    INIReader()
    {
        parsed = false;
    }

    /**
    *  @brief Parse a file during initialization.
    */
    explicit INIReader(const std::string &filePath)
    {
        parsed = false;
        ParseFile(filePath);
    }

    ~INIReader() = default;

    INIReader& operator=(const INIReader& src)
    {
        //copy contents
        ini_content = src.ini_content;
        //copy status
        parsed = src.parsed;
        current_section = src.current_section;
        exclude_sections = src.exclude_sections;
        include_sections = src.include_sections;
        section_order = src.section_order;
        isolated_items_section = src.isolated_items_section;
        //copy preferences
        do_utf8_to_gbk = src.do_utf8_to_gbk;
        store_any_line = src.store_any_line;
        store_isolated_line = src.store_isolated_line;
        allow_dup_section_titles = src.allow_dup_section_titles;
        keep_empty_section = src.keep_empty_section;
        return *this;
    }

    INIReader(const INIReader &src) = default;

    std::string GetLastError()
    {
        if(parsed)
            return __priv_get_err_str(last_error);
        else
            return "line " + std::to_string(last_error_index) + ": " + __priv_get_err_str(last_error);
    }

    /**
    *  @brief Exclude a section with the given name.
    */
    void ExcludeSection(const std::string &section)
    {
        exclude_sections.emplace_back(section);
    }

    /**
    *  @brief Include a section with the given name.
    */
    void IncludeSection(const std::string &section)
    {
        include_sections.emplace_back(section);
    }

    /**
    *  @brief Add a section to the direct-save sections list.
    */
    void AddDirectSaveSection(const std::string &section)
    {
        direct_save_sections.emplace_back(section);
    }

    /**
    *  @brief Set isolated items to given section.
    */
    void SetIsolatedItemsSection(const std::string &section)
    {
        isolated_items_section = section;
    }

    /**
    *  @brief Parse INI content into mapped data structure.
    * If exclude sections are set, these sections will not be stored.
    * If include sections are set, only these sections will be stored.
    */
    int Parse(std::string content) //parse content into mapped data
    {
        if(!content.size()) //empty content
            return __priv_save_error_and_return(INIREADER_EXCEPTION_EMPTY);

        //remove UTF-8 BOM
        if(content.compare(0, 3, "\xEF\xBB\xBF") == 0)
            content.erase(0, 3);

        bool inExcludedSection = false, inDirectSaveSection = false, inIsolatedSection = false;
        std::string strLine, thisSection, curSection, itemName, itemVal;
        string_multimap itemGroup;
        string_array read_sections;
        std::stringstream strStrm;
        char delimiter = getLineBreak(content);

        EraseAll(); //first erase all data
        if(do_utf8_to_gbk && is_str_utf8(content))
            content = UTF8ToACP(content); //do conversion if flag is set

        if(store_isolated_line && isolated_items_section.size())
        {
            curSection = isolated_items_section; //items before any section define will be store in this section
            //check this section first
            inExcludedSection = __priv_chk_ignore(curSection); //check if this section is excluded
            inDirectSaveSection = __priv_chk_direct_save(curSection); //check if this section requires direct-save
            inIsolatedSection = true;
        }
        strStrm<<content;
        last_error_index = 0; //reset error index
        while(getline(strStrm, strLine, delimiter)) //get one line of content
        {
            last_error_index++;
            string_size lineSize = strLine.size(), pos_equal = strLine.find("=");
            if(lineSize && strLine[lineSize - 1] == '\r') //remove line break
                strLine.erase(--lineSize);
            if((!lineSize || strLine[0] == ';' || strLine[0] == '#' || (lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/')) && !inDirectSaveSection) //empty lines and comments are ignored
                continue;
            ProcessEscapeChar(strLine);
            if(strLine[0] == '[' && strLine[lineSize - 1] == ']') //is a section title
            {
                thisSection = strLine.substr(1, lineSize - 2); //save section title
                inExcludedSection = __priv_chk_ignore(thisSection); //check if this section is excluded
                inDirectSaveSection = __priv_chk_direct_save(thisSection); //check if this section requires direct-save

                if(curSection.size() && (keep_empty_section || itemGroup.size())) //just finished reading a section
                {
                    if(ini_content.find(curSection) != ini_content.end()) //a section with the same name has been inserted
                    {
                        if(allow_dup_section_titles || !ini_content.at(curSection).size())
                        {
                            auto iter = ini_content.at(curSection); //get the existing section
                            iter.merge(itemGroup); //move new items to this section
                        }
                        else if(ini_content.at(curSection).size())
                            return __priv_save_error_and_return(INIREADER_EXCEPTION_DUPLICATE); //not allowed, stop
                    }
                    else if(!inIsolatedSection || isolated_items_section != thisSection)
                    {
                        if(itemGroup.size())
                            read_sections.push_back(curSection); //add to read sections list
                        if(std::find(section_order.cbegin(), section_order.cend(), curSection) == section_order.cend())
                            section_order.emplace_back(curSection); //add to section order if not added before
                        ini_content.emplace(std::move(curSection), std::move(itemGroup)); //insert previous section to content map
                    }
                }
                inIsolatedSection = false;
                eraseElements(itemGroup); //reset section storage
                curSection = thisSection; //start a new section
            }
            else if(((store_any_line && pos_equal == strLine.npos) || inDirectSaveSection) && !inExcludedSection && curSection.size()) //store a line without name
            {
                itemGroup.emplace("{NONAME}", strLine);
            }
            else if(pos_equal != strLine.npos) //is an item
            {
                if(inExcludedSection) //this section is excluded
                    continue;
                if(!curSection.size()) //not in any section
                    return __priv_save_error_and_return(INIREADER_EXCEPTION_OUTOFBOUND);
                string_size pos_value = strLine.find_first_not_of(' ', pos_equal + 1);
                itemName = trim(strLine.substr(0, pos_equal));
                if(pos_value != strLine.npos) //not a key with empty value
                {
                    itemVal = strLine.substr(pos_value);
                    itemGroup.emplace(std::move(itemName), std::move(itemVal)); //insert to current section
                }
                else
                    itemGroup.emplace(std::move(itemName), std::string());
            }
            if(include_sections.size() && include_sections == read_sections) //all included sections has been read
                break; //exit now
        }
        if(curSection.size() && (keep_empty_section || itemGroup.size())) //final section
        {
            if(ini_content.find(curSection) != ini_content.end()) //a section with the same name has been inserted
            {
                if(allow_dup_section_titles || isolated_items_section == thisSection)
                {
                    auto &iter = ini_content.at(curSection); //get the existing section
                    iter.merge(itemGroup); //move new items to this section
                }
                else if(ini_content.at(curSection).size())
                    return __priv_save_error_and_return(INIREADER_EXCEPTION_DUPLICATE); //not allowed, stop
            }
            else if(!inIsolatedSection || isolated_items_section != thisSection)
            {
                if(itemGroup.size())
                    read_sections.emplace_back(curSection); //add to read sections list
                if(std::find(section_order.cbegin(), section_order.cend(), curSection) == section_order.cend())
                    section_order.emplace_back(curSection); //add to section order if not added before
                ini_content.emplace(std::move(curSection), std::move(itemGroup)); //insert this section to content map
            }
        }
        parsed = true;
        return __priv_save_error_and_return(INIREADER_EXCEPTION_NONE); //all done
    }

    /**
    *  @brief Parse an INI file into mapped data structure.
    */
    int ParseFile(const std::string &filePath)
    {
        if(!fileExist(filePath))
            return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTEXIST);
        return Parse(fileGet(filePath));
    }

    /**
    *  @brief Check whether a section exist.
    */
    bool SectionExist(const std::string &section)
    {
        return ini_content.find(section) != ini_content.end();
    }

    /**
    *  @brief Count of sections in the whole INI.
    */
    unsigned int SectionCount()
    {
        return ini_content.size();
    }

    /**
    *  @brief Return all section names inside INI.
    */
    string_array GetSections()
    {
        return section_order;
    }

    /**
    *  @brief Enter a section with the given name. Section name and data will be cached to speed up the following reading process.
    */
    int EnterSection(const std::string &section)
    {
        if(!SectionExist(section))
            return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTEXIST);
        current_section = cached_section = section;
        cached_section_content = ini_content.find(section);
        return __priv_save_error_and_return(INIREADER_EXCEPTION_NONE);
    }

    /**
    *  @brief Set current section.
    */
    void SetCurrentSection(const std::string &section)
    {
        current_section = section;
    }

    /**
    *  @brief Check whether an item exist in the given section. Return false if the section does not exist.
    */
    bool ItemExist(const std::string &section, const std::string &itemName)
    {
        if(!SectionExist(section))
            return false;

        if(section != cached_section)
        {
            cached_section = section;
            cached_section_content = ini_content.find(section);
        }
        auto &cache = cached_section_content->second;
        return cache.find(itemName) != cache.end();
    }

    /**
    *  @brief Check whether an item exist in current section. Return false if the section does not exist.
    */
    bool ItemExist(const std::string &itemName)
    {
        return current_section.size() ? ItemExist(current_section, itemName) : false;
    }

    /**
    *  @brief Check whether an item with the given name prefix exist in the given section. Return false if the section does not exist.
    */
    bool ItemPrefixExist(const std::string &section, const std::string &itemName)
    {
        if(!SectionExist(section))
            return false;

        if(section != cached_section)
        {
            cached_section = section;
            cached_section_content = ini_content.find(section);
        }

        for(auto &x : cached_section_content->second)
        {
            if(x.first.find(itemName) == 0)
                return true;
        }

        return false;
    }

    /**
    *  @brief Check whether an item with the given name prefix exist in current section. Return false if the section does not exist.
    */
    bool ItemPrefixExist(const std::string &itemName)
    {
        return current_section.size() ? ItemPrefixExist(current_section, itemName) : false;
    }

    /**
    *  @brief Count of items in the given section. Return 0 if the section does not exist.
    */
    unsigned int ItemCount(const std::string &section)
    {
        if(!parsed || !SectionExist(section))
            return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTPARSED);

        return ini_content.at(section).size();
    }

    /**
    *  @brief Erase all data from the data structure and reset parser status.
    */
    void EraseAll()
    {
        eraseElements(ini_content);
        eraseElements(section_order);
        cached_section.clear();
        cached_section_content = ini_content.end();
        parsed = false;
    }

    ini_data_struct::iterator GetItemsRef(const std::string &section)
    {
        if(!parsed || !SectionExist(section))
            return ini_content.end();

        if(cached_section != section)
        {
            cached_section = section;
            cached_section_content = ini_content.find(section);
        }
        return cached_section_content;
    }

    /**
    *  @brief Retrieve all items in the given section.
    */
    int GetItems(const std::string &section, string_multimap &data)
    {
        auto section_ref = GetItemsRef(section);
        if(section_ref == ini_content.end())
            return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTEXIST);

        data = section_ref->second;
        return __priv_save_error_and_return(INIREADER_EXCEPTION_NONE);
    }

    /**
    *  @brief Retrieve all items in current section.
    */
    int GetItems(string_multimap &data)
    {
        return current_section.size() ? GetItems(current_section, data) : -1;
    }

    /**
    * @brief Retrieve item(s) with the same name prefix in the given section.
    */
    int GetAll(const std::string &section, const std::string &itemName, string_array &results) //retrieve item(s) with the same itemName prefix
    {
        if(!parsed)
            return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTPARSED);

        auto section_ref = GetItemsRef(section);
        if(section_ref == ini_content.end())
            return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTEXIST);

        for(auto &x : section_ref->second)
        {
            if(x.first.find(itemName) == 0)
                results.emplace_back(x.second);
        }

        return __priv_save_error_and_return(INIREADER_EXCEPTION_NONE);
    }

    /**
    * @brief Retrieve item(s) with the same name prefix in current section.
    */
    int GetAll(const std::string &itemName, string_array &results)
    {
        return current_section.size() ? GetAll(current_section, itemName, results) : -1;
    }

    /**
    * @brief Retrieve one item with the exact same name in the given section.
    */
    std::string Get(const std::string &section, const std::string &itemName) //retrieve one item with the exact same itemName
    {
        if(!parsed || !SectionExist(section))
            return std::string();

        if(cached_section != section)
        {
            cached_section = section;
            cached_section_content = ini_content.find(section);
        }

        auto &cache = cached_section_content->second;
        auto iter = std::find_if(cache.begin(), cache.end(), [&](auto x) { return x.first == itemName; });
        if(iter != cache.end())
            return iter->second;

        return std::string();
    }

    /**
    * @brief Retrieve one item with the exact same name in current section.
    */
    std::string Get(const std::string &itemName)
    {
        return current_section.size() ? Get(current_section, itemName) : std::string();
    }

    /**
    * @brief Retrieve one item with the exact same name in the given section, if exist.
    */
    int GetIfExist(const std::string &section, const std::string &itemName, std::string &target) //retrieve one item with the exact same itemName
    {
        if(!parsed)
            return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTPARSED);

        if(ItemExist(section, itemName))
        {
            target = Get(section, itemName);
            return __priv_save_error_and_return(INIREADER_EXCEPTION_NONE);
        }

        return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTEXIST);
    }

    /**
    * @brief Retrieve one item with the exact same name in current section, if exist.
    */
    int GetIfExist(const std::string &itemName, std::string &target)
    {
        return current_section.size() ? GetIfExist(current_section, itemName, target) : INIREADER_EXCEPTION_NOTEXIST;
    }

    /**
    * @brief Retrieve one boolean item value with the exact same name in the given section.
    */
    bool GetBool(const std::string &section, const std::string &itemName)
    {
        return Get(section, itemName) == "true";
    }

    /**
    * @brief Retrieve one boolean item value with the exact same name in current section.
    */
    bool GetBool(const std::string &itemName)
    {
        return current_section.size() ? Get(current_section, itemName) == "true" : false;
    }

    /**
    * @brief Retrieve one boolean item value with the exact same name in the given section.
    */
    int GetBoolIfExist(const std::string &section, const std::string &itemName, bool &target)
    {
        std::string result;
        int retval = GetIfExist(section, itemName, result);
        if(retval != INIREADER_EXCEPTION_NONE)
            return retval;
        if(result.size())
            target = result == "true";
        return INIREADER_EXCEPTION_NONE;
    }

    /**
    * @brief Retrieve one boolean item value with the exact same name in current section.
    */
    int GetBoolIfExist(const std::string &itemName, bool &target)
    {
        return current_section.size() ? GetBoolIfExist(current_section, itemName, target) : INIREADER_EXCEPTION_NOTEXIST;
    }

    /**
    * @brief Retrieve one number item value with the exact same name in the given section.
    */
    template <typename T> int GetNumberIfExist(const std::string &section, const std::string &itemName, T &target)
    {
        std::string result;
        int retval = GetIfExist(section, itemName, result);
        if(retval != INIREADER_EXCEPTION_NONE)
            return retval;
        if(result.size())
            target = to_number<T>(result, target);
        return INIREADER_EXCEPTION_NONE;
    }

    /**
    * @brief Retrieve one number item value with the exact same name in current section.
    */
    template <typename T> int GetNumberIfExist(const std::string &itemName, T &target)
    {
        return current_section.size() ? GetNumberIfExist(current_section, itemName, target) : INIREADER_EXCEPTION_NOTEXIST;
    }

    /**
    * @brief Retrieve one integer item value with the exact same name in the given section.
    */
    int GetIntIfExist(const std::string &section, const std::string &itemName, int &target)
    {
        return GetNumberIfExist<int>(section, itemName, target);
    }

    /**
    * @brief Retrieve one integer item value with the exact same name in current section.
    */
    int GetIntIfExist(const std::string &itemName, int &target)
    {
        return GetNumberIfExist<int>(itemName, target);
    }

    /**
    * @brief Retrieve one integer item value with the exact same name in the given section.
    */
    int GetInt(const std::string &section, const std::string &itemName)
    {
        return to_int(Get(section, itemName), 0);
    }

    /**
    * @brief Retrieve one integer item value with the exact same name in current section.
    */
    int GetInt(const std::string &itemName)
    {
        return GetInt(current_section, itemName);
    }

    /**
    * @brief Retrieve the first item found in the given section.
    */
    std::string GetFirst(const std::string &section, const std::string &itemName) //return the first item value found in section
    {
        if(!parsed)
            return std::string();
        string_array result;
        if(GetAll(section, itemName, result) != -1)
            return result[0];
        else
            return std::string();
    }

    /**
    * @brief Retrieve the first item found in current section.
    */
    std::string GetFirst(const std::string &itemName)
    {
        return current_section.size() ? GetFirst(current_section, itemName) : std::string();
    }

    /**
    * @brief Retrieve a string style array with specific separator and write into integer array.
    */
    template <typename T> void GetIntArray(const std::string &section, const std::string &itemName, const std::string &separator, T &Array)
    {
        string_array vArray;
        unsigned int index, UBound = sizeof(Array) / sizeof(Array[0]);
        vArray = split(Get(section, itemName), separator);
        for(index = 0; index < vArray.size() && index < UBound; index++)
            Array[index] = stoi(vArray[index]);
        for(; index < UBound; index++)
            Array[index] = 0;
    }

    /**
    * @brief Retrieve a string style array with specific separator and write into integer array.
    */
    template <typename T> void GetIntArray(const std::string &itemName, const std::string &separator, T &Array)
    {
        if(current_section.size())
            GetIntArray(current_section, itemName, separator, Array);
    }

    /**
    *  @brief Add a std::string value with given values.
    */
    int Set(const std::string &section, std::string itemName, std::string itemVal)
    {
        if(!section.size())
            return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTEXIST);

        if(!parsed)
            parsed = true;

        if(SectionExist(section))
        {
            string_multimap &mapTemp = ini_content.at(section);
            mapTemp.insert(std::pair<std::string, std::string>(std::move(itemName), std::move(itemVal)));
        }
        else
        {
            string_multimap mapTemp;
            mapTemp.insert(std::pair<std::string, std::string>(std::move(itemName), std::move(itemVal)));
            ini_content.insert(std::pair<std::string, std::multimap<std::string, std::string>>(section, std::move(mapTemp)));
            section_order.emplace_back(section);
        }

        return __priv_save_error_and_return(INIREADER_EXCEPTION_NONE);
    }

    /**
    *  @brief Add a string value with given values.
    */
    int Set(std::string itemName, std::string itemVal)
    {
        if(!current_section.size())
            return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTEXIST);
        return Set(current_section, std::move(itemName), std::move(itemVal));
    }

    /**
    *  @brief Add a boolean value with given values.
    */
    int SetBool(const std::string &section, std::string itemName, bool itemVal)
    {
        return Set(section, std::move(itemName), itemVal ? "true" : "false");
    }

    /**
    *  @brief Add a boolean value with given values.
    */
    int SetBool(std::string itemName, bool itemVal)
    {
        return SetBool(current_section, std::move(itemName), itemVal);
    }

    /**
    *  @brief Add a double value with given values.
    */
    int SetDouble(const std::string &section, std::string itemName, double itemVal)
    {
        return Set(section, std::move(itemName), std::to_string(itemVal));
    }

    /**
    *  @brief Add a double value with given values.
    */
    int SetDouble(std::string itemName, double itemVal)
    {
        return SetDouble(current_section, std::move(itemName), itemVal);
    }

    /**
    *  @brief Add a long value with given values.
    */
    int SetLong(const std::string &section, std::string itemName, long itemVal)
    {
        return Set(section, std::move(itemName), std::to_string(itemVal));
    }

    /**
    *  @brief Add a long value with given values.
    */
    int SetLong(std::string itemName, long itemVal)
    {
        return SetLong(current_section, std::move(itemName), itemVal);
    }

    /**
    *  @brief Add an array with the given separator.
    */
    template <typename T> int SetArray(const std::string &section, std::string itemName, const std::string &separator, T &Array)
    {
        std::string data;
        data = std::accumulate(std::begin(Array), std::end(Array), std::string(), [&](auto a, auto b) { return std::move(a) + std::to_string(b) + separator; });
        data.erase(data.size() - 1);
        return Set(section, std::move(itemName), data);
    }

    /**
    *  @brief Add an array with the given separator.
    */
    template <typename T> int SetArray(std::string itemName, const std::string &separator, T &Array)
    {
        return current_section.size() ? SetArray(current_section, std::move(itemName), separator, Array) : -1;
    }

    /**
    *  @brief Rename an existing section.
    */
    int RenameSection(const std::string &oldName, std::string newName)
    {
        if(!SectionExist(oldName) || SectionExist(newName))
            return __priv_save_error_and_return(INIREADER_EXCEPTION_DUPLICATE);
        auto nodeHandler = ini_content.extract(oldName);
        nodeHandler.key() = std::move(newName);
        ini_content.insert(std::move(nodeHandler));
        std::replace(section_order.begin(), section_order.end(), oldName, newName);
        return __priv_save_error_and_return(INIREADER_EXCEPTION_NONE);
    }

    /**
    *  @brief Erase all items with the given name.
    */
    int Erase(const std::string &section, const std::string &itemName)
    {
        int retVal;
        if(!SectionExist(section))
            return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTEXIST);

        retVal = ini_content.at(section).erase(itemName);
        if(retVal && cached_section == section)
        {
            cached_section_content = ini_content.find(section);
        }
        return retVal;
    }

    /**
    *  @brief Erase all items with the given name.
    */
    int Erase(const std::string &itemName)
    {
        return current_section.size() ? Erase(current_section, itemName) : -1;
    }

    /**
    *  @brief Erase the first item with the given name.
    */
    int EraseFirst(const std::string &section, const std::string &itemName)
    {
        string_multimap &mapTemp = ini_content.at(section);
        string_multimap::iterator iter = mapTemp.find(itemName);
        if(iter != mapTemp.end())
        {
            mapTemp.erase(iter);
            return __priv_save_error_and_return(INIREADER_EXCEPTION_NONE);
        }
        else
        {
            return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTEXIST);
        }
    }

    /**
    *  @brief Erase the first item with the given name.
    */
    int EraseFirst(const std::string &itemName)
    {
        return current_section.size() ? EraseFirst(current_section, itemName) : -1;
    }

    /**
    *  @brief Erase all items in the given section.
    */
    void EraseSection(const std::string &section)
    {
        if(ini_content.find(section) == ini_content.end())
            return;
        eraseElements(ini_content.at(section));
        if(cached_section == section)
        {
            cached_section_content = ini_content.end();
            cached_section.erase();
        }
    }

    /**
    *  @brief Erase all items in current section.
    */
    void EraseSection()
    {
        if(current_section.size())
            EraseSection(current_section);
    }

    /**
    *  @brief Remove a section from INI.
    */
    void RemoveSection(const std::string &section)
    {
        if(ini_content.find(section) == ini_content.end())
            return;
        ini_content.erase(section);
        if(cached_section == section)
        {
            cached_section.clear();
            cached_section_content = ini_content.end();
        }
        section_order.erase(std::find(section_order.begin(), section_order.end(), section));
    }

    /**
    *  @brief Remove current section from INI.
    */
    void RemoveSection()
    {
        if(current_section.size())
            RemoveSection(current_section);
    }

    /**
    *  @brief Export the whole INI data structure into a string.
    */
    std::string ToString()
    {
        std::string content, itemVal;

        if(!parsed)
            return std::string();

        for(auto &x : section_order)
        {
            string_size strsize = 0;
            content += "[" + x + "]\n";
            if(ini_content.find(x) != ini_content.end())
            {
                auto section = ini_content.at(x);
                if(section.empty())
                {
                    content += "\n";
                    continue;
                }
                for(auto iter = section.begin(); iter != section.end(); iter++)
                {
                    if(iter->first != "{NONAME}")
                        content += iter->first + "=";
                    itemVal = iter->second;
                    ProcessEscapeCharReverse(itemVal);
                    content += itemVal + "\n";
                    if(std::next(iter) == section.end())
                        strsize = itemVal.size();
                }
            }
            if(strsize)
                content += "\n";
        }
        return content;
    }

    /**
    *  @brief Export the whole INI data structure into a file.
    */
    int ToFile(const std::string &filePath)
    {
        return fileWrite(filePath, ToString(), true);
    }
};

#endif // INI_READER_H_INCLUDED
