module lunch.conf;

import toml;

import std.conv;
import std.traits;


private struct Name
{
    string str;
    string toString() { return str; }
}


struct AppConfig
{
    bool talkative;
    bool report_errors;
    string report_url;
    string config_name;
    bool load_config;
    bool auto_launch;
    bool auto_update;
    bool check_update;
}

struct UIConfig
{
    bool enabled;
    bool launch;
    bool update;
    bool config_dump;
    bool exit;
    string message;
    string launch_label;
    string update_label;
    string update_avail_label;
    string config_dump_label;
    string exit_label;
}

struct LaunchConfig
{
    string[] command;
    string workdir;
    bool use_script;
    string entry_file;
    string source_dir;

version (Windows):
    bool hide_when_running;
}

struct UpdateConfig
{
    string seedurl;
    string workdir;
    size_t parallel_downloads;
    string[] exclude;
}

struct Config
{
    @Name("application")
    AppConfig app;
    @Name("interface")
    UIConfig ui;
    LaunchConfig launcher;
    UpdateConfig updater;
}


shared Config config;


private string dash2minus(string str)
{
    string newStr = "";
    foreach (c; str)
    {
        if (c == '_')
            newStr ~= '-';
        else
            newStr ~= c;
    }
    return newStr;
}


void wrapTOML(T)(ref T config, TOMLValue[string] toml, bool ignore_missing = false) @safe
{
    static foreach (name; __traits(allMembers, T))
    {{
        alias member = __traits(getMember, config, name);
        alias type = typeof(__traits(getMember, T, name));
        type* field = &__traits(getMember, config, name);

        static if (hasUDA!(member, Name))
            enum key = getUDAs!(member, Name)[0].str;
        else
            enum key = name.dash2minus;

        TOMLValue* value = key in toml;
        if (!value)
        {
            if (!ignore_missing)
                throw new Exception("Missing field \""~key~"\" of type "~type.stringof);
        }
        else
        {
            static if (isSomeString!type)
            {
                *field = value.str.to!type;
            }
            else static if (is(type == string[]))
            {
                *field = [];
                foreach (_, entry; value.array)
                    *field ~= entry.str;
            }
            else static if (is(type == bool))
            {
                *field = value.boolean;
            }
            else static if (isIntegral!type)
            {
                *field = value.integer.to!type;
            }
            else static if (isFloatingPoint!type)
            {
                *field = value.floating.to!type;
            }
            else
            {
                wrapTOML(*field, value.table, ignore_missing);
            }
        }
    }}
}


Config parseConfig(string toml) @safe
{
    Config config;
    TOMLDocument doc = parseTOML(toml);
    wrapTOML(config, doc);
    return config;
}
