// config.cpp
//
// original from https://cppsecrets.com/users/41129711010797106994610011511264103109971051084699111109/C00-Config-Reader.php
//
// Code Specific Header File(s)
#include "config_reader.h"

// C++ Header File(s)
#include <fstream>
#include <algorithm>

using namespace std;

#define NULL_PTR 0

ConfigReader* ConfigReader::m_pInstance = NULL_PTR;

ConfigReader::ConfigReader()
{
    m_ConfigSettingMap.clear();
}

ConfigReader::~ConfigReader()
{
    m_ConfigSettingMap.clear();
}

ConfigReader* ConfigReader::getInstance()
{
    // No need to use double re-check lock mechanism here
    // because this getInstance() will call at the time of
    // initialization only and mostly, at the time of
    // initialization, there will be only one thread.

    if(NULL_PTR == m_pInstance)
    {
        m_pInstance = new ConfigReader;
    }
    return m_pInstance;
}

bool ConfigReader::getValue(std::string group, std::string tag, int& value)
{
    string _tag = group + "/" + tag;
    map<string, string>::iterator it ;
    it = m_ConfigSettingMap.find(_tag);
    if(it != m_ConfigSettingMap.end())
    {
        value = atoi((it->second).c_str());
        return true;
    }
    return false;
}

bool ConfigReader::getValue(std::string group, std::string tag, std::string& value)
{
    string _tag = group + "/" + tag;
    map<string, string>::iterator it ;
    it = m_ConfigSettingMap.find(_tag);
    if(it != m_ConfigSettingMap.end())
    {
        value = it->second;
        return true;
    }
    return false;
}

bool ConfigReader::parseFile(string fileName)
{
    ifstream inputFile;
    inputFile.open(fileName.c_str());
    string delimeter = "=";
    string group_label = "";
    int initPos = 0;

    if (inputFile.fail())
    {
        cout << "Unable to find defaultConfig file" << endl;
        return false;
    }

    string line;
    while(getline(inputFile, line))
    {
        // Remove comment Lines
        size_t found = line.find_first_of('#');
        string configData = line.substr(0, found);

        // Remove ^M from configData
        configData.erase(std::remove(configData.begin(), configData.end(), '\r'), configData.end());

        if(configData.empty())
            continue;

        size_t gr_start = configData.find("[");
        size_t gr_stop = configData.find("]");
        if ((gr_start != string::npos) && (gr_stop != string::npos)) {
            group_label = configData.substr(gr_start+1, gr_stop-1);
            // cout << "group_label=" << group_label << endl;
            continue;
        }
        size_t length = configData.find(delimeter);

        string tag, value;

        if (length != string::npos) {
            tag   = configData.substr(initPos, length);
            value = configData.substr(length+1);
        }

        // Trim white spaces
        tag   = reduce(tag);
        value = reduce(value);
        // cout << tag << "=" << value << endl;

        if(tag.empty() || value.empty())
            continue;

        // add group_name to tag
        tag = group_label + "/" + tag;
        
        // Check if any of the tags is repeated more than one times
        // it needs to pick the latest one instead of the old one.

        // Search, if the tag is already present or not
        // If it is already present, then delete an existing one

        std::map<std::string, std::string>::iterator itr = m_ConfigSettingMap.find(tag);
        if(itr != m_ConfigSettingMap.end())
        {
            m_ConfigSettingMap.erase(tag);
        }

        m_ConfigSettingMap.insert(std::pair<string, string>(tag, value));
    }
    return true;
}

std::string ConfigReader::trim(const std::string& str, const std::string& whitespace)
{
    size_t strBegin = str.find_first_not_of(whitespace);
    if (strBegin == std::string::npos)
        return "";

    size_t strEnd = str.find_last_not_of(whitespace);
    size_t strRange = strEnd - strBegin + 1;

    return str.substr(strBegin, strRange);
}

std::string ConfigReader::reduce(const std::string& str,
        const std::string& fill,
        const std::string& whitespace)
{
    // trim first
    string result = trim(str, whitespace);

    // replace sub ranges
    size_t beginSpace = result.find_first_of(whitespace);
    while (beginSpace != std::string::npos)
    {
        size_t endSpace = result.find_first_not_of(whitespace, beginSpace);
        size_t range = endSpace - beginSpace;

        result.replace(beginSpace, range, fill);

        size_t newStart = beginSpace + fill.length();
        beginSpace = result.find_first_of(whitespace, newStart);
    }

    return result;
}

void ConfigReader::dumpFileValues()
{
    map<string, string>::iterator it;
    for(it=m_ConfigSettingMap.begin(); it!=m_ConfigSettingMap.end(); ++it)
    {
        cout << it->first << " = " << it->second << endl;
    }
}

// int main()
// {
//     // Create object of the class ConfigReader
//     ConfigReader* p = ConfigReader::getInstance();

//     // parse the configuration file
//     p->parseFile();

//     // Dump map on the console after parsing it
//     p->dumpFileValues();

//     // Print divider on the console to understand the output properly
//     cout << endl << "=================================================" << endl << endl;

//     // Define variables to store the value
//     string name("");
//     string skill("");
//     string website("");
//     int age = 0;

//     // Update the variable by the value present in the configuration file.
//     p->getValue("Name", name);
//     p->getValue("Skill", skill);
//     p->getValue("Website", website);
//     p->getValue("Age", age);

//     // Variables has been updated. Now print it on the console.
//     cout << "Name = " << name << endl;
//     cout << "Skill = " << skill << endl;
//     cout << "Website = " << website << endl;
//     cout << "Age = " << age << endl;

//     // Return sucees to the caller
//     return 0;
// }