/* SPDX-License-Identifier: GPL-2.0-only */

#include "util/toml.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define CHECK(expr) do {                                            \
    if (!(expr)) {                                                  \
        fprintf(stderr, "FAIL: %s:%d: %s\n", __FILE__, __LINE__, #expr); \
        return 1;                                                   \
    }                                                               \
} while (0)

static const char SAMPLE[] =
    "# top-level comment\n"
    "title = \"ppm\"\n"
    "literal = 'no \\escapes\\ here'\n"
    "\n"
    "[package]\n"
    "name    = \"hello\"\n"
    "version = \"1.0.0\"\n"
    "authors = [\"Alice\", \"Bob\"]\n"
    "\n"
    "[package.nested]\n"
    "deep = \"yes\"\n"
    "\n"
    "[dependencies]\n"
    "libfoo = \"^1.0\"  # trailing comment\n"
    "libbar = \">=2.0, <3.0\"\n";

int main(void) {
    ppm_toml_node_t *root = NULL;
    char *err = NULL;
    CHECK(ppm_toml_parse(SAMPLE, &root, &err) == PPM_OK);
    CHECK(err == NULL);
    CHECK(root != NULL);

    CHECK(strcmp(ppm_toml_get_str(root, "title"), "ppm") == 0);
    CHECK(strcmp(ppm_toml_get_str(root, "literal"), "no \\escapes\\ here") == 0);

    const ppm_toml_node_t *pkg = ppm_toml_get_table(root, "package");
    CHECK(pkg != NULL);
    CHECK(strcmp(ppm_toml_get_str(pkg, "name"), "hello") == 0);
    CHECK(strcmp(ppm_toml_get_str(pkg, "version"), "1.0.0") == 0);

    const ppm_toml_node_t *authors = ppm_toml_get_array(pkg, "authors");
    CHECK(authors != NULL && authors->u.arr.len == 2);
    CHECK(strcmp(authors->u.arr.items[0]->u.str, "Alice") == 0);
    CHECK(strcmp(authors->u.arr.items[1]->u.str, "Bob")   == 0);

    const ppm_toml_node_t *nested = ppm_toml_get_table(pkg, "nested");
    CHECK(nested != NULL);
    CHECK(strcmp(ppm_toml_get_str(nested, "deep"), "yes") == 0);

    const ppm_toml_node_t *deps = ppm_toml_get_table(root, "dependencies");
    CHECK(deps != NULL && deps->u.tbl.len == 2);
    CHECK(strcmp(ppm_toml_get_str(deps, "libfoo"), "^1.0") == 0);
    CHECK(strcmp(ppm_toml_get_str(deps, "libbar"), ">=2.0, <3.0") == 0);

    ppm_toml_free(root);

    /* --- Error cases ----------------------------------------------------- */
    static const char DUP[] = "a = \"1\"\na = \"2\"\n";
    root = NULL; err = NULL;
    CHECK(ppm_toml_parse(DUP, &root, &err) == PPM_ERR_PARSE);
    CHECK(err && strstr(err, "duplicate") != NULL);
    free(err);

    static const char UNTERMINATED[] = "x = \"open\n";
    root = NULL; err = NULL;
    CHECK(ppm_toml_parse(UNTERMINATED, &root, &err) == PPM_ERR_PARSE);
    CHECK(err != NULL);
    free(err);

    static const char BAD_VALUE[] = "n = 42\n"; /* integers not supported */
    root = NULL; err = NULL;
    CHECK(ppm_toml_parse(BAD_VALUE, &root, &err) == PPM_ERR_PARSE);
    free(err);

    static const char HETEROGENEOUS[] = "xs = [\"a\", \"b\", [\"c\"]]\n";
    root = NULL; err = NULL;
    CHECK(ppm_toml_parse(HETEROGENEOUS, &root, &err) == PPM_ERR_PARSE);
    free(err);

    static const char EMPTY[] = "\n# nothing here\n\n";
    root = NULL; err = NULL;
    CHECK(ppm_toml_parse(EMPTY, &root, &err) == PPM_OK);
    CHECK(root && root->kind == PPM_TOML_TABLE && root->u.tbl.len == 0);
    ppm_toml_free(root);

    /* --- multi-line basic strings """...""" ----------------------------- */
    /* The newline immediately after the opening delimiter is stripped;
     * interior and trailing newlines are preserved. */
    static const char ML_BASIC[] = "ml = \"\"\"\nline1\nline2\n\"\"\"\n";
    root = NULL; err = NULL;
    CHECK(ppm_toml_parse(ML_BASIC, &root, &err) == PPM_OK);
    CHECK(err == NULL);
    CHECK(strcmp(ppm_toml_get_str(root, "ml"), "line1\nline2\n") == 0);
    ppm_toml_free(root);

    /* Line continuation: backslash + (optional whitespace) + newline +
     * leading whitespace on the next line is all elided. "a\<nl>   b" -> "ab". */
    static const char ML_CONT[] = "ml = \"\"\"a\\\n   b\"\"\"\n";
    root = NULL; err = NULL;
    CHECK(ppm_toml_parse(ML_CONT, &root, &err) == PPM_OK);
    CHECK(strcmp(ppm_toml_get_str(root, "ml"), "ab") == 0);
    ppm_toml_free(root);

    /* Standard escape sequences (\t \n \" \\) work inside a multi-line string. */
    static const char ML_ESC[] =
        "ml = \"\"\"tab\\there\\nnext\\\"q\\\\bs\"\"\"\n";
    root = NULL; err = NULL;
    CHECK(ppm_toml_parse(ML_ESC, &root, &err) == PPM_OK);
    CHECK(strcmp(ppm_toml_get_str(root, "ml"), "tab\there\nnext\"q\\bs") == 0);
    ppm_toml_free(root);

    /* Up to two quotes may appear in the body immediately before the
     * closing delimiter: """x""""" -> x"". */
    static const char ML_TRAILQ[] = "ml = \"\"\"x\"\"\"\"\"\n";
    root = NULL; err = NULL;
    CHECK(ppm_toml_parse(ML_TRAILQ, &root, &err) == PPM_OK);
    CHECK(strcmp(ppm_toml_get_str(root, "ml"), "x\"\"") == 0);
    ppm_toml_free(root);

    /* An unterminated multi-line string is a parse error. */
    static const char ML_UNTERM[] = "ml = \"\"\"oops\nstill going\n";
    root = NULL; err = NULL;
    CHECK(ppm_toml_parse(ML_UNTERM, &root, &err) == PPM_ERR_PARSE);
    CHECK(err != NULL);
    free(err);

    printf("ok\n");
    return 0;
}
