#include <exception>
#include <sstream>
#include <fstream>
#include <Settings/Settings.hpp>
#include <Log/Log.hpp>
#include <Utilities/Utilities.hpp>
#include <VoxelverseSession/VoxelverseSession.hpp>

Settings settings;

void Settings::printComments(std::ofstream &os, const char *shortDesc, const char *longDesc)
{
    os << std::endl;
    os << "# " << shortDesc << "." << std::endl;

    if (longDesc != nullptr)
    {
        os << "#" << std::endl;

        const char *newlinePos;
        while ((newlinePos = strchr(longDesc, '\n')) != nullptr)
        {
            os << "# " << std::string(longDesc, newlinePos - longDesc) << std::endl;
            longDesc = newlinePos + 1;
        }

        os << "# " << longDesc << std::endl;
    }
}

Settings::Settings()
{
    #define DECL_COMMON_SETTING(type, name, defaultValue, ...) name = defaultValue;
    #include <Settings/SettingsList.hpp>
}

std::string Settings::set(std::string key, std::string value, bool preserveUnknown)
{
    bool isServer = sessionTypeName == "server";
    bool isClient = sessionTypeName == "client";

    try
    {
        if (false) {}
        #define DECL_COMMON_SETTING(type, name, defaultValue, shortDesc, longDesc, outputStmt, parse, ...)\
            else if (key == #name)\
            {\
                type& SETTING_VALUE = name;\
                parse\
            }
        #define DECL_SERVER_ONLY_SETTING(type, name, defaultValue, shortDesc, longDesc, outputStmt, parse, ...)\
            else if (isServer && key == #name)\
            {\
                type& SETTING_VALUE = name;\
                parse\
            }
        #define DECL_CLIENT_ONLY_SETTING(type, name, defaultValue, shortDesc, longDesc, outputStmt, parse, ...)\
            else if (isClient && key == #name)\
            {\
                type& SETTING_VALUE = name;\
                parse\
            }
        #include <Settings/SettingsList.hpp>
        else
        {
            if (preserveUnknown)
            {
                unknownSettings[key] = value;
            }

            std::stringstream ss;
            ss << "Invalid settings key `" << key << "`";
            return ss.str();
        }
    }
    catch (std::exception &e)
    {
        std::stringstream ss;
        ss << "Invalid value for `" << key << "`: " << value << ": " << e.what();
        return ss.str();
    }
}

void Settings::init()
{
    sessionTypeName = voxelverse->getSessionTypeName();

    std::ifstream ifs(formatString("voxelverse-$+.conf", sessionTypeName));
    if (!ifs.good())
    {
        Log::warn("Could not read settings file (it might not yet exist).");
    }

    std::string line;
    while (std::getline(ifs, line))
    {
        if (line.size() == 0)
        {
            // Empty lines are ignored.
            continue;
        }

        if (line[0] == '#')
        {
            // Comment lines are ignored.
            continue;
        }

        while (line[line.size()-1] == '\\')
        {
            line.resize(line.size()-1);

            std::string subline;
            if (!std::getline(ifs, subline))
            {
                Log::error(formatString("Unterminated trailing backlash on last line"));
                break;
            }

            line += subline;
        }

        size_t equalsPos = line.find('=');
        if (equalsPos == std::string::npos)
        {
            Log::error(formatString("Invalid config line: $+", line));
        }
        else
        {
            std::string key = line.substr(0, equalsPos);
            std::string value = line.substr(equalsPos+1);

            if (key.size() == 0)
            {
                Log::error(formatString("Config line with empty key: $+", key));
            }
            else
            {
                Log::debug(formatString("Setting key `$+` to value `$+`", key, value));
                std::string err = set(key, value, true);
                if (err.size() != 0)
                {
                    Log::error(formatString("Could not set `$+` to `$+`: $+", key, value, err));
                }
            }
        }
    }

    ifs.close();
    save();
}

void Settings::save()
{
    std::ofstream ofs(formatString("voxelverse-$+.conf", sessionTypeName));
    ofs << "# Voxelverse " << sessionTypeName << " configuration file." << std::endl;

    bool isServer = sessionTypeName == "server";
    bool isClient = sessionTypeName == "client";

    #define DECL_COMMON_SETTING(type, name, defaultValue, shortDesc, longDesc, outputStmt, ...) \
        {\
            printComments(ofs, shortDesc, longDesc);\
            ofs << #name << "=";\
            outputStmt\
            ofs << std::endl;\
        }
    #define DECL_SERVER_ONLY_SETTING(...) if (isServer) DECL_COMMON_SETTING(__VA_ARGS__)
    #define DECL_CLIENT_ONLY_SETTING(...) if (isClient) DECL_COMMON_SETTING(__VA_ARGS__)
    #include <Settings/SettingsList.hpp>

    if (unknownSettings.size() != 0)
    {
        ofs << std::endl;
        ofs << "# The following settings were found in this file but not recognised by the most recently" << std::endl;
        ofs << "# ran version of the game. They are kept in case they are used by another version." << std::endl;
        ofs << "# You can remove them if they aren't necessary anymore." << std::endl;

        for (const auto &[key, value] : unknownSettings)
        {
            ofs << key << "=" << value << std::endl;
        }
    }

    if (!ofs.good())
    {
        Log::error("Failed to write the settings file!");
    }

    ofs.close();
}