struct Var {
    String key;
    String value;
};

struct ConfigParseResult {
    Array<Var>    vars;
    Array<String> errors;
};

struct ReplaceVar {
    String replace;
    String with;
};
// @todo: move the config errors to config menu!!!

char SRTCommand[512]        = "\"C:\\Program Files\\VideoLAN\\VLC\\vlc.exe\" --start-time {time_in_seconds} {video}";
char PDFCommand[512]        = "C:/Users/Karol/AppData/Local/SumatraPDF/SumatraPDF.exe -page {page} {file}";
char TXTCommand[512]        = "notepad.exe {file}";
char ReadOnStart[512]       = "-";
bool ReadFoldersRecursively = false;

void SkipWhitespace(String *s) {
    while (s->len && IsWhitespace(s->data[0])) {
        *s = s->skip(1);
    }
}

String ParseWord(String *s) {
    String result = {};
    SkipWhitespace(s);
    if (s->len && IsAlphabetic(s->data[0])) {
        result = {s->data, 0};
        while (s->len && IsAlphanumeric(s->data[0])) {
            result.len += 1;
            *s = s->skip(1);
        }
    }
    return result;
}

bool MatchString(String *s, String expect) {
    SkipWhitespace(s);
    if (StartsWith(*s, expect)) {
        *s = s->skip(expect.len);
        return true;
    }
    return false;
}

ConfigParseResult ParseConfig(Allocator allocator, String string) {
    Scratch       scratch;
    Array<String> lines = Split(scratch, string, "\n");

    ConfigParseResult result = {};
    result.errors            = {allocator};
    result.vars              = {allocator};

    For(lines) {
        String s = Trim(it);
        if (s.len == 0) continue;

        String key = ParseWord(&s);
        if (key.len == 0) {
            result.errors.add(Format(allocator, "failed to parse config at line: %d, the key is invalid", (int)lines.get_index(it)));
            continue;
        }
        bool e = MatchString(&s, "=");
        if (!e) {
            result.errors.add(Format(allocator, "failed to parse config at line: %d, expected '=' assignment sign", (int)lines.get_index(it)));
            continue;
        }
        String value = Trim(s);
        if (value.len == 0) {
            result.errors.add(Format(allocator, "failed to parse config at line: %d, the value is invalid", (int)lines.get_index(it)));
            continue;
        }

        result.vars.add({key, value});
    }
    return result;
}

String GetValue(Array<Var> vars, String key) {
    For(vars) {
        if (it.key == key) return it.value;
    }
    return {};
}

String SerializeConfig(Allocator allocator) {
    String content = Format(allocator, "SRTCommand = %s\nPDFCommand = %s\nTXTCommand = %s\nReadOnStart = %s", SRTCommand, PDFCommand, TXTCommand, ReadOnStart);
    return content;
}

String CreateDefaultConfig(String path) {
    String content = SerializeConfig(Perm);
    WriteFile(path, content);
    return content;
}

void LoadConfig() {
    Scratch scratch;
    String  exe_dir     = GetExeDir(scratch);
    String  config_path = Format(scratch, "%.*s/transcript_browser.config", FmtString(exe_dir));
    String  config      = ReadFile(Perm, config_path);
    if (config.len == 0) {
        config = CreateDefaultConfig(config_path);
    }

    ConfigParseResult result = ParseConfig(Perm, config);
    For(result.errors) {
        XFileLoadResults.add({"", it});
    }

    String pdf_command = GetValue(result.vars, "PDFCommand");
    if (pdf_command.len) {
        int len = ClampTop((int)pdf_command.len, (int)sizeof(PDFCommand) - 1);
        memcpy(PDFCommand, pdf_command.data, len);
        PDFCommand[len] = 0;
    }
    String txt_command = GetValue(result.vars, "TXTCommand");
    if (txt_command.len) {
        int len = ClampTop((int)txt_command.len, (int)sizeof(TXTCommand) - 1);
        memcpy(TXTCommand, txt_command.data, len);
        TXTCommand[len] = 0;
    }
    String srt_command = GetValue(result.vars, "SRTCommand");
    if (srt_command.len) {
        int len = ClampTop((int)srt_command.len, (int)sizeof(SRTCommand) - 1);
        memcpy(SRTCommand, srt_command.data, len);
        SRTCommand[len] = 0;
    }
    String read_on_start = GetValue(result.vars, "ReadOnStart");
    if (read_on_start.len) {
        int len = ClampTop((int)read_on_start.len, (int)sizeof(ReadOnStart) - 1);
        memcpy(ReadOnStart, read_on_start.data, len);
        ReadOnStart[len] = 0;
    }
}

void SaveConfig() {
    Scratch scratch;
    String  exe_dir     = GetExeDir(scratch);
    String  config_path = Format(scratch, "%.*s/transcript_browser.config", FmtString(exe_dir));
    String  content     = SerializeConfig(scratch);
    WriteFile(config_path, content);
}

String ReplaceVars(Allocator allocator, Array<ReplaceVar> vars_to_replace, String string) {
    Array<char> sb = {allocator};
    for (int64_t i = 0; i < string.len; i += 1) {
        if (string[i] == '{') {

            // extract the variable
            i += 1;
            String var = {string.data + i, 0};
            for (; i < string.len && string[i] != '}'; i += 1)
                var.len += 1;

            // find the replacement
            String found = "<not found>";
            For(vars_to_replace) {
                if (it.replace == var) {
                    found = it.with;
                    break;
                }
            }

            For(found) sb.add(it);

        } else sb.add(string[i]);
    }

    sb.add('\0');
    String result = {sb.data, sb.len - 1};
    return result;
}

void TestReplaceVars() {
    Scratch           scratch;
    Array<ReplaceVar> vars_to_replace = {scratch};
    String            exe_folder      = GetExePath(scratch);
    vars_to_replace.add({"exe_folder", exe_folder});
    String data_folder = Format(scratch, "%.*s/data", FmtString(exe_folder));
    vars_to_replace.add({"data_folder", data_folder});

    {
        String r = ReplaceVars(scratch, vars_to_replace, "{exe_folder}");
        Assert(r == exe_folder);
    }

    {
        String r = ReplaceVars(scratch, vars_to_replace, "{exe_folder}{data_folder}");
        Assert(r == Format(scratch, "%.*s%.*s", FmtString(exe_folder), FmtString(data_folder)));
    }
    {
        String r = ReplaceVars(scratch, vars_to_replace, "..{exe_folder}..{data_folder}..{exe_folder}asd");
        Assert(r == Format(scratch, "..%.*s..%.*s..%.*sasd", FmtString(exe_folder), FmtString(data_folder), FmtString(exe_folder)));
    }
}

void TestConfig() {
    Scratch scratch;
    {
        ConfigParseResult result = ParseConfig(scratch, "\nPDFCommand = CoolCommand\nTXTCommand = ABC\nSRTCommand = DAS\n");
        Assert(result.errors.len == 0);
        Assert(result.vars.len == 3);
        Assert(result.vars[0].key == "PDFCommand");
        Assert(result.vars[1].value == "ABC");
        Assert(result.vars[2].value == "DAS");
        Assert(result.vars[2].key == "SRTCommand");
    }
    {
        ConfigParseResult result = ParseConfig(scratch, "\n= CoolCommand\nTXTCommand = ABC\nSRTCommand =\n");
        Assert(result.errors.len == 2);
        Assert(result.vars.len == 1);
    }
    LoadConfig();
}