/*
 * This file is part of smolbote. It's copyrighted by the contributors recorded
 * in the version control history of the file, available from its original
 * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote
 *
 * SPDX-License-Identifier: GPL-3.0
 */

#include "configuration.h"
#include <QStandardPaths>
#include <algorithm>
#include <fstream>
#include <iostream>
#include <sstream>
#include <stdexcept>

static std::unique_ptr<Configuration> s_conf;

inline void strip(std::string &value)
{
    value.erase(value.begin(), std::find_if(value.begin(), value.end(), std::bind1st(std::not_equal_to<char>(), ' ')));
    value.erase(std::find_if(value.rbegin(), value.rend(), std::bind1st(std::not_equal_to<char>(), ' ')).base(), value.end());
}

Configuration::Configuration()
    : use_global(true)
{
    if(!s_conf)
        throw new std::runtime_error("Trying to use default Configuration, but none has been set!");
}

Configuration::Configuration(std::initializer_list<std::pair<std::string, conf_value_t>> l) noexcept
    : m_homePath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation).toStdString())
{
    for(const auto &i : l) {
        insert_or_assign(i.first, i.second);
    }
}

void Configuration::read_file(const std::string &location)
{
    std::fstream fs(location, std::fstream::in);
    if(fs.is_open()) {
        read(fs);
        fs.close();
    }
}

void Configuration::read(std::basic_istream<char> &input)
{
    std::string line, section, key, value;
    std::istringstream is_line;

    while(std::getline(input, line)) {
        if(line.rfind("@@") == 0) {
            if(line.rfind("@@include ") == 0) {
                read_file(line.substr(10));
            }
            continue;
        }
        if(line[0] == '#' || line.length() == 0)
            continue;

        if(line.front() == '[' && line.back() == ']') {
            section = line.substr(1, line.length() - 2) + '/';
            continue;
        }

        is_line.clear();
        is_line.str(line);

        const auto pos = line.find_first_of('=');
        if(pos != std::string::npos) {
            key = section + line.substr(0, pos);
            value = line.substr(pos + 1);

            strip(key);
            strip(value);

            if(this->count(key) == 0) {
                // no type has been specified for this key, assuming std::string
                insert(std::make_pair(key, value));
                continue;
            }

            auto v = at(key);
            if(std::holds_alternative<std::string>(v)) {
                at(key) = value;
            } else if(std::holds_alternative<int>(v)) {
                at(key) = std::stoi(value);
            } else if(std::holds_alternative<bool>(v)) {
                at(key) = (value == "true");
            }
        }
    }
}

void Configuration::move_global(std::unique_ptr<Configuration> &&conf)
{
    s_conf = std::move(conf);
}

Configuration *Configuration::instance()
{
    return s_conf.get();
}

std::ostream &operator<<(std::ostream &out, const Configuration &obj)
{
    if(obj.use_global) {
        if(!s_conf) {
            throw new std::runtime_error("Trying to use default Configuration, but none has been set!");
        }

        out << *s_conf;
        return out;
    }

    // unordered_map is, well, unordered, so grab the keys and sort them before printing them
    std::vector<std::string> keys;
    for(const auto &pair : obj) {
        keys.emplace_back(pair.first);
    }
    std::sort(keys.begin(), keys.end());

    for(const auto &key : keys) {
        out << key << "=" << obj.value<std::string>(key.c_str()).value() << "\n";
    }

    return out;
}