/*
 * Copyright (C) 2022 Alexander Borisov
 *
 * Author: Alexander Borisov <borisov@lexbor.com>
 */

#include <lexbor/grammar/test.h>
#include <lexbor/core/fs.h>


static lxb_char_t *
convert_string(const lxb_char_t *value, size_t len, size_t *out,
               lxb_status_t *status);


typedef struct {
    FILE   *f;

    char   *dir;
    size_t length;
    size_t size;

    size_t count;
}
my_context_t;

lxb_status_t
begin(const lxb_char_t *data, size_t len, void *ctx)
{
    int err;
    my_context_t *my = ctx;

    if (my->length + len + 6 >= my->size) {
        return LXB_STATUS_ERROR_SMALL_BUFFER;
    }

    my->dir[my->length] = '/';
    memcpy(&my->dir[my->length + 1], data, len);
    memcpy(&my->dir[my->length + 1 + len], ".ton\0", 5);

    printf ("Tests for '%.*s' property saved to %s\n", (int) len,
            (const char *) data, my->dir);

    my->f = fopen((const char *) my->dir, "wb");
    if (my->f == NULL) {
        return LXB_STATUS_ERROR;
    }

    err = fprintf(my->f, "[\n    "
                  "/* Do not change this file. Generated by /utils/grammar. */\n");
    if (err < 0) {
        return LXB_STATUS_ERROR;
    }

    return LXB_STATUS_OK;
}

lxb_status_t
callback(const lxb_char_t *name, size_t name_len,
         const lxb_char_t *value, size_t value_len,
         const lxb_char_t *ordered, size_t ordered_len,
         bool last, bool bad, void *ctx)
{
    int err;
    const lxb_char_t *c_value, *c_ordered;
    size_t c_length;
    lxb_status_t status;
    my_context_t *my = ctx;

    my->count++;

    c_value = convert_string(value, value_len, &c_length, &status);

    if (c_value == NULL) {
        if (status != LXB_STATUS_OK) {
            return status;
        }

        c_value = value;
        c_ordered = ordered;
    }
    else {
        value_len = c_length;

        c_ordered = convert_string(ordered, ordered_len, &c_length, &status);
        if (c_ordered == NULL) {
            return LXB_STATUS_ERROR;
        }

        ordered_len = c_length;
    }

    err = fprintf(my->f,
                  "    /* "LEXBOR_FORMAT_Z" */\n"
                  "    {\n"
                  "        \"data\": \"%.*s: %.*s\",\n"
                  "        \"results\": [\n"
                  "            {\"type\": \"%s\", \"name\": \"%.*s\", \"value\": \"%.*s\", \"important\": false}\n"
                  "        ]\n"
                  "    }%s",
                  my->count,
                  (int) name_len, (const char *) name,
                  (int) value_len, (const char *) c_value,
                  ((bad) ? "custom" : "property"),
                  (int) name_len, (const char *) name,
                  (int) ordered_len, (const char *) c_ordered,
                  ((last) ? "\n" : ",\n"));

    if (c_value != value) {
        lexbor_free((lxb_char_t *) c_value);
        lexbor_free((lxb_char_t *) c_ordered);
    }

    if (err < 0) {
        return LXB_STATUS_ERROR;
    }

    return LXB_STATUS_OK;
}

lxb_status_t
end(const lxb_char_t *data, size_t len, void *ctx)
{
    int err;
    my_context_t *my = ctx;

    err = fprintf(my->f, "]\n");
    if (err < 0) {
        return LXB_STATUS_ERROR;
    }

    fclose(my->f);
    my->f = NULL;
    my->count = 0;

    return LXB_STATUS_OK;
}

static lxb_char_t *
convert_string(const lxb_char_t *value, size_t len, size_t *out,
               lxb_status_t *status)
{
    size_t i = len, count = 0, begin;
    lxb_char_t *data, *p;

    while (i != 0) {
        i--;

        if (value[i] == '"') {
            count += 1;
        }
    }

    if (count == 0) {
        *status = LXB_STATUS_OK;
        return NULL;
    }

    *out = len + count;

    data = lexbor_malloc(*out + 1);
    if (data == NULL) {
        *status = LXB_STATUS_ERROR;
        return NULL;
    }

    i = 0;
    p = data;
    begin = 0;

    while (i < len) {
        if (value[i] == '"') {
            memcpy(p, &value[begin], i - begin);

            p += i - begin;
            *p++ = '\\';
            *p++ = '"';

            begin = i + 1;
        }

        i += 1;
    }

    if (begin < i) {
        memcpy(p, &value[begin], i - begin);

        p += i - begin;
    }

    *p = '\0';

    return data;
}

int
main(int argc, const char *argv[])
{
    size_t length;
    lxb_char_t *grammar;
    lxb_status_t status;
    my_context_t my;
    const char *gfile, *save_to;

    if (argc != 3) {
        printf("Usage:\n\tgrammar <grammar file> <directory to save>\n");
        return EXIT_FAILURE;
    }

    gfile = argv[1];
    save_to = argv[2];

    grammar = lexbor_fs_file_easy_read((const lxb_char_t *) gfile, &length);
    if (grammar == NULL) {
        fprintf(stderr, "Failed to read grammar file: %s\n", gfile);
        return EXIT_FAILURE;
    }

    my.f = NULL;
    my.count = 0;
    my.length = strlen(save_to);
    my.size = my.length + 1024;
    my.dir = malloc(my.size);

    if (my.dir == NULL) {
        lexbor_free(grammar);
        return EXIT_FAILURE;
    }

    memcpy(my.dir, save_to, my.length);
    my.dir[my.length] = '\0';

    status = utils_lxb_grammar_test(grammar, length, begin, callback, end, &my);

    if (my.f != NULL) {
        fclose(my.f);
    }

    free(my.dir);
    lexbor_free(grammar);

    return (status == LXB_STATUS_OK) ? EXIT_SUCCESS : EXIT_FAILURE;
}
