#ifndef COMMONCPP_LITE
#include "config.hpp"
#include "utils.hpp"

#include <fstream>
#include <filesystem>



namespace common {
bool Configuration::parse_bool(const std::string &value) {
    if (value == "true")
        return true;
    if (value == "false")
        return false;
    throw Exception("Error: Failed to parse configuration file: Unknown bool (true/false): "+value);
}

std::string Configuration::parse_string(const std::string &value) {
    std::string fres;
    fres.reserve(value.size());
    bool escaped = false;
    for (const char c : value) {
        if (!escaped) {
            if (c == '\\') {
                escaped = true;
                continue;
            } else
                fres.push_back(c);
        } else {
            char nc;
            switch (c) {
            case 'n':
                nc = '\n';
                break;
            case 'r':
                nc = '\r';
                break;
            case 't':
                nc = '\t';
                break;
            case 'b':
                nc = '\b';
                break;
            case '\\':
                nc = '\\';
                break;
            default:
                throw Exception("Unknown escape sequence: \\"+std::string(1, c));
            }
            fres.push_back(nc);
            escaped = false;
        }
    }
    return fres;
}

bool Configuration::file_exists(std::string_view path) {
    // Make sure we don't respond to some file that is actually called "none"...
    if (path.empty() || path == "none") return false;
    return std::filesystem::exists(path);
}

Configuration::KeyValueMap Configuration::file_parser(const std::string& path) {
    std::unordered_map<std::string, std::string> fres;
    // Open file
    std::ifstream cfgf(path);
    if (!cfgf) {
        throw Exception("Failed to open configuration file: "+path);
    }
    // Read each entry
    for (std::string key; cfgf >> key;) {
        // Read value
        std::string value;
        std::getline(cfgf, value);
        // Ignore comment and empty lines
        if (key.empty() || key[0] == '#') continue;
        // Erase all leading spaces in value
        while (!value.empty() && (value[0] == ' ' || value[0] == '\t')) value.erase(0, 1);
        // Add to function result
        fres.emplace(std::move(key), std::move(value));
    }
    // Return final result
    return fres;
}


extern "C" char **environ;
Configuration::KeyValueMap Configuration::environment_parser() {
    std::unordered_map<std::string, std::string> fres;
    for (char **s = environ; *s; s++) {
        const auto pair = utils::str_split(*s, '=', 1);
        fres.emplace(pair[0], pair[1]);
    }
    return fres;
}

void Configuration::parse(const std::string &file) {
    const auto file_location = file.empty()?
                std::filesystem::current_path():
                std::filesystem::path(file).parent_path();
    if (!ignore_environment)
        fill(environment_parser());
    if (!file.empty())
        fill(file_parser(file), true);
}
}
#endif // COMMONCPP_LITE