#include "yml.h"

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>

#include <yaml.h>
#include <tllist.h>

#define UNUSED __attribute__((unused))

enum yml_error {
    YML_ERR_NONE,
    YML_ERR_DUPLICATE_KEY,
    YML_ERR_INVALID_ANCHOR,
    YML_ERR_UNKNOWN,
};

enum node_type {
    ROOT,
    SCALAR,
    DICT,
    LIST,
};

struct yml_node;

struct dict_pair {
    struct yml_node *key;
    struct yml_node *value;
};

struct anchor_map {
    char *anchor;
    const struct yml_node *node;
};

struct yml_node {
    enum node_type type;
    union {
        struct {
            struct yml_node *root;
            struct anchor_map anchors[100]; /* TODO: dynamic resize */
            size_t anchor_count;
        } root;
        struct {
            char *value;
        } scalar;
        struct {
            tll(struct dict_pair) pairs;
            bool next_is_value;
        } dict;
        struct {
            tll(struct yml_node *) values;
        } list;
    };

    size_t line;
    size_t column;

    struct yml_node *parent;
};

static struct yml_node *
clone_node(struct yml_node *parent, const struct yml_node *node)
{
    struct yml_node *clone = calloc(1, sizeof(*clone));
    clone->type = node->type;
    clone->line = node->line;
    clone->column = node->column;
    clone->parent = parent;

    switch (node->type) {
    case SCALAR:
        clone->scalar.value = strdup(node->scalar.value);
        break;

    case DICT:
        tll_foreach(node->dict.pairs, it) {
            struct dict_pair p = {
                .key = clone_node(clone, it->item.key),
                .value = clone_node(clone, it->item.value),
            };
            tll_push_back(clone->dict.pairs, p);
        }
        break;

    case LIST:
        tll_foreach(node->list.values, it)
            tll_push_back(clone->list.values, clone_node(clone, it->item));
        break;

    case ROOT:
        assert(false);
        break;
    }

    return clone;
}

static bool
node_equal(const struct yml_node *a, const struct yml_node *b)
{
    if (a->type != b->type)
        return false;

    if (a->type != SCALAR) {
        /* TODO... */
        return false;
    }

    return strcmp(a->scalar.value, b->scalar.value) == 0;
}

static bool
dict_has_key(const struct yml_node *node, const struct yml_node *key)
{
    assert(node->type == DICT);

    tll_foreach(node->dict.pairs, pair) {
        if (node_equal(pair->item.key, key))
            return true;
    }

    return false;
}

static enum yml_error
add_node(struct yml_node *parent, struct yml_node *new_node, yaml_mark_t loc)
{
    new_node->line = loc.line + 1;  /* yaml uses 0-based line numbers */
    new_node->column = loc.column;

    switch (parent->type) {
    case ROOT:
        assert(parent->root.root == NULL);
        parent->root.root = new_node;
        new_node->parent = parent;
        break;

    case DICT:
        if (!parent->dict.next_is_value) {
            if (dict_has_key(parent, new_node))
                return YML_ERR_DUPLICATE_KEY;

            tll_push_back(parent->dict.pairs, (struct dict_pair){.key = new_node});
            parent->dict.next_is_value = true;
        } else {
            tll_back(parent->dict.pairs).value = new_node;
            parent->dict.next_is_value = false;
        }
        new_node->parent = parent;
        break;

    case LIST:
        tll_push_back(parent->list.values, new_node);
        new_node->parent = parent;
        break;

    case SCALAR:
        assert(false);
        return YML_ERR_UNKNOWN;
    }

    return YML_ERR_NONE;
}

static void
add_anchor(struct yml_node *root, const char *anchor,
           const struct yml_node *node)
{
    assert(root->type == ROOT);

    struct anchor_map *map = &root->root.anchors[root->root.anchor_count];
    map->anchor = strdup(anchor);
    map->node = node;
    root->root.anchor_count++;
}

static bool
post_process(struct yml_node *node, char **error)
{
    switch (node->type) {
    case ROOT:
        if (node->root.root != NULL)
            if (!post_process(node->root.root, error))
                return false;
        break;

    case SCALAR:
        //assert(strcmp(node->scalar.value, "<<") != 0);
        break;

    case LIST:
        tll_foreach(node->list.values, it)
            if (!post_process(it->item, error))
                return false;
        break;

    case DICT:
        tll_foreach(node->dict.pairs, it) {
            if (!post_process(it->item.key, error) ||
                !post_process(it->item.value, error))
            {
                return false;
            }
        }

        tll_foreach(node->dict.pairs, it) {
            if (it->item.key->type != SCALAR)
                continue;

            if (strcmp(it->item.key->scalar.value, "<<") != 0)
                continue;

            if (it->item.value->type == LIST) {
                /*
                 * Merge value is a list (of dictionaries)
                 * e.g. <<: [*foo, *bar]
                 */
                tll_foreach(it->item.value->list.values, v_it) {
                    if (v_it->item->type != DICT) {
                        int cnt = snprintf(
                            NULL, 0, "%zu:%zu: cannot merge non-dictionary anchor",
                            v_it->item->line, v_it->item->column);
                        *error = malloc(cnt + 1);
                        snprintf(
                            *error, cnt + 1, "%zu:%zu: cannot merge non-dictionary anchor",
                            v_it->item->line, v_it->item->column);
                        return false;
                    }

                    tll_foreach(v_it->item->dict.pairs, vv_it) {
                        struct dict_pair p = {
                            .key = vv_it->item.key,
                            .value = vv_it->item.value,
                        };
                        /* TODO: handle this. Is it an error? Or
                         * should we replace the existing key/value
                         * pair */
                        assert(!dict_has_key(node, vv_it->item.key));

                        tll_push_back(node->dict.pairs, p);
                    }

                    /* Destroy list, but don't free (since its nodes
                     * have been moved to this node), *before*
                     * destroying the key/value nodes. This ensures
                     * the dict nodes aren't free:d in the
                     * yml_destroy() below). */
                    tll_free(v_it->item->dict.pairs);
                }
            } else {
                /*
                 * Merge value is a dictionary only
                 * e.g. <<: *foo
                 */
                if (it->item.value->type != DICT) {
                    int cnt = snprintf(
                        NULL, 0, "%zu:%zu: cannot merge non-dictionary anchor",
                        it->item.value->line, it->item.value->column);
                    *error = malloc(cnt + 1);
                    snprintf(
                        *error, cnt + 1, "%zu:%zu: cannot merge non-dictionary anchor",
                        it->item.value->line, it->item.value->column);
                    return false;
                }

                tll_foreach(it->item.value->dict.pairs, v_it) {
                    struct dict_pair p = {
                        .key = v_it->item.key,
                        .value = v_it->item.value,
                    };

                    /* TODO: handle this. Is it an error? Or should we
                     * replace the existing key/value pair */
                    assert(!dict_has_key(node, v_it->item.key));

                    tll_push_back(node->dict.pairs, p);
                }

                /* Destroy list here, *without* freeing nodes (since
                 * nodes have been moved to this node), *before*
                 * destroying the key/value nodes. This ensures the
                 * dict nodes aren't free:d in the yml_destroy()
                 * below */
                tll_free(it->item.value->dict.pairs);
            }

            yml_destroy(it->item.key);
            yml_destroy(it->item.value);

            tll_remove(node->dict.pairs, it);
        }
        break;
    }

    return true;
}

static const char *
format_error(enum yml_error err,
             const struct yml_node *parent,
             const struct yml_node *node,
             const char *anchor)
{
    static char err_str[512];

    switch (err) {
    case YML_ERR_NONE:
        assert(false);
        break;

    case YML_ERR_DUPLICATE_KEY: {
        /* Find parent's key (i.e its name) */
        if (parent->parent != NULL &&
            parent->parent->type == DICT &&
            node->type == SCALAR)
        {
            tll_foreach(parent->parent->dict.pairs, pair) {
                if (pair->item.value != parent)
                    continue;

                if (pair->item.key->type != SCALAR)
                    break;

                assert(pair->item.key->type == SCALAR);
                assert(node->type == SCALAR);

                snprintf(err_str, sizeof(err_str),
                         "%s: duplicate key: '%s'",
                         pair->item.key->scalar.value,
                         node->scalar.value);
                return err_str;
            }
        }

        if (node->type == SCALAR) {
            snprintf(err_str, sizeof(err_str),
                     "duplicate key: %s", node->scalar.value);
        } else
            snprintf(err_str, sizeof(err_str), "duplicate key");
        break;
    }

    case YML_ERR_INVALID_ANCHOR:
        if (parent->parent != NULL && parent->parent->type == DICT) {
            tll_foreach(parent->parent->dict.pairs, pair) {
                if (pair->item.value != parent)
                    continue;
                if (pair->item.key->type != SCALAR)
                    break;

                snprintf(err_str, sizeof(err_str),
                         "%s: invalid anchor: %s",
                         pair->item.key->scalar.value,
                         anchor != NULL ? anchor : "<unknown>");
                return err_str;
            }
        }

        snprintf(err_str, sizeof(err_str), "invalid anchor: %s",
                 anchor != NULL ? anchor : "<unknown>");
        break;

    case YML_ERR_UNKNOWN:
        snprintf(err_str, sizeof(err_str), "unknown error");
        break;
    }

    return err_str;
}

struct yml_node *
yml_load(FILE *yml, char **error)
{
    yaml_parser_t yaml;
    yaml_parser_initialize(&yaml);

    yaml_parser_set_input_file(&yaml, yml);

    bool done = false;
    int indent UNUSED = 0;

    struct yml_node *root = malloc(sizeof(*root));
    root->type = ROOT;
    root->root.root = NULL;
    root->root.anchor_count = 0;

    struct yml_node *n = root;

    const char *error_str = NULL;

    while (!done) {
        yaml_event_t event;
        if (!yaml_parser_parse(&yaml, &event)) {
            if (error != NULL) {
                int cnt = snprintf(
                    NULL, 0, "%zu:%zu: %s %s",
                    yaml.problem_mark.line + 1,
                    yaml.problem_mark.column,
                    yaml.problem,
                    yaml.context != NULL ? yaml.context : "");

                *error = malloc(cnt + 1);
                snprintf(*error, cnt + 1, "%zu:%zu: %s %s",
                    yaml.problem_mark.line + 1,
                    yaml.problem_mark.column,
                    yaml.problem,
                    yaml.context != NULL ? yaml.context : "");
            }

            goto err_no_error_formatting;
        }

        switch (event.type) {
        case YAML_NO_EVENT:
            break;

        case YAML_STREAM_START_EVENT:
            indent += 2;
            break;

        case YAML_STREAM_END_EVENT:
            indent -= 2;
            done = true;
            break;

        case YAML_DOCUMENT_START_EVENT:
            indent += 2;
            break;

        case YAML_DOCUMENT_END_EVENT:
            indent -= 2;
            break;

        case YAML_ALIAS_EVENT: {
            bool got_match = false;
            for (size_t i = 0; i < root->root.anchor_count; i++) {
                const struct anchor_map *map = &root->root.anchors[i];

                if (strcmp(map->anchor, (const char *)event.data.alias.anchor) != 0)
                    continue;

                struct yml_node *clone = clone_node(NULL, map->node);
                assert(clone != NULL);

                enum yml_error err = add_node(n, clone, event.start_mark);
                if (err != YML_ERR_NONE) {
                    error_str = format_error(err, n, clone, NULL);
                    yml_destroy(clone);
                    yaml_event_delete(&event);
                    goto err;
                }

                got_match = true;
                break;
            }

            if (!got_match) {
                error_str = format_error(
                    YML_ERR_INVALID_ANCHOR, n, NULL,
                    (const char *)event.data.alias.anchor);
                yaml_event_delete(&event);
                goto err;
            }
            break;
        }

        case YAML_SCALAR_EVENT: {
            struct yml_node *new_scalar = calloc(1, sizeof(*new_scalar));
            new_scalar->type = SCALAR;
            new_scalar->scalar.value = strndup(
                (const char*)event.data.scalar.value, event.data.scalar.length);

            enum yml_error err = add_node(n, new_scalar, event.start_mark);
            if (err != YML_ERR_NONE) {
                error_str = format_error(err, n, new_scalar, NULL);
                yml_destroy(new_scalar);
                yaml_event_delete(&event);
                goto err;
            }

            if (event.data.scalar.anchor != NULL) {
                const char *anchor = (const char *)event.data.scalar.anchor;
                add_anchor(root, anchor, new_scalar);
            }

            break;
        }

        case YAML_SEQUENCE_START_EVENT: {
            indent += 2;
            struct yml_node *new_list = calloc(1, sizeof(*new_list));
            new_list->type = LIST;

            enum yml_error err = add_node(n, new_list, event.start_mark);
            if (err != YML_ERR_NONE) {
                error_str = format_error(err, n, new_list, NULL);
                yml_destroy(new_list);
                yaml_event_delete(&event);
                goto err;
            }

            n = new_list;

            if (event.data.sequence_start.anchor != NULL) {
                const char *anchor = (const char *)event.data.sequence_start.anchor;
                add_anchor(root, anchor, new_list);
            }
            break;
        }

        case YAML_SEQUENCE_END_EVENT:
            indent -= 2;
            assert(n->parent != NULL);
            n = n->parent;
            break;

        case YAML_MAPPING_START_EVENT: {
            indent += 2;

            struct yml_node *new_dict = calloc(1, sizeof(*new_dict));
            new_dict->type = DICT;

            enum yml_error err = add_node(n, new_dict, event.start_mark);
            if (err != YML_ERR_NONE) {
                error_str = format_error(err, n, new_dict, NULL);
                yml_destroy(new_dict);
                yaml_event_delete(&event);
                goto err;
            }

            n = new_dict;

            if (event.data.mapping_start.anchor != NULL) {
                const char *anchor = (const char *)event.data.mapping_start.anchor;
                add_anchor(root, anchor, new_dict);
            }
            break;
        }

        case YAML_MAPPING_END_EVENT:
            assert(!n->dict.next_is_value);
            indent -= 2;
            assert(n->parent != NULL);
            n = n->parent;
            break;
        }

        yaml_event_delete(&event);
    }

    yaml_parser_delete(&yaml);

    if (!post_process(root, error)) {
        yml_destroy(root);
        return NULL;
    }
    return root;

err:
    if (error_str != NULL) {
        int cnt = snprintf(
            NULL, 0, "%zu:%zu: %s",
            yaml.mark.line + 1,
            yaml.mark.column,
            error_str);
        *error = malloc(cnt + 1);
        snprintf(
            *error, cnt + 1, "%zu:%zu: %s",
            yaml.mark.line + 1,
            yaml.mark.column,
            error_str);
    } else {
        int cnt = snprintf(NULL, 0, "%zu:%zu: unknown error",
                           yaml.mark.line + 1, yaml.mark.column);
        *error = malloc(cnt + 1);
        snprintf(*error, cnt + 1, "%zu:%zu: unknown error",
                 yaml.mark.line + 1, yaml.mark.column);
    }

err_no_error_formatting:

    yml_destroy(root);
    yaml_parser_delete(&yaml);
    return NULL;
}

void
yml_destroy(struct yml_node *node)
{
    if (node == NULL)
        return;

    switch (node->type) {
    case ROOT:
        yml_destroy(node->root.root);
        for (size_t i = 0; i < node->root.anchor_count; i++)
            free(node->root.anchors[i].anchor);
        break;

    case SCALAR:
        free(node->scalar.value);
        break;

    case LIST:
        tll_free_and_free(node->list.values, yml_destroy);
        break;

    case DICT:
        tll_foreach(node->dict.pairs, it) {
            yml_destroy(it->item.key);
            yml_destroy(it->item.value);
        }
        tll_free(node->dict.pairs);
        break;
    }

    free(node);
}

bool
yml_is_scalar(const struct yml_node *node)
{
    return node->type == SCALAR;
}

bool
yml_is_dict(const struct yml_node *node)
{
    return node->type == DICT;
}

bool
yml_is_list(const struct yml_node *node)
{
    return node->type == LIST;
}

static struct yml_node const *
yml_get_(struct yml_node const *node, char const *_path, bool value)
{
    /* value: true for value, false for key */

    if (node != NULL && node->type == ROOT)
        node = node->root.root;

    if (node == NULL)
        return NULL;

    char *path = strdup(_path);

    for (const char *part = strtok(path, "."), *next_part = strtok(NULL, ".");
         part != NULL;
         part = next_part, next_part = strtok(NULL, "."))
    {
        assert(yml_is_dict(node));

        tll_foreach(node->dict.pairs, it) {
            assert(yml_is_scalar(it->item.key));
            if (strcmp(it->item.key->scalar.value, part) == 0) {
                if (next_part == NULL) {
                    free(path);

                    if (value)
                        return it->item.value;
                    else
                        return it->item.key;
                }

                node = it->item.value;
                break;
            }
        }
    }

    free(path);
    return NULL;
}

const struct yml_node *
yml_get_value(const struct yml_node *node, const char *_path)
{
    return yml_get_(node, _path, true);
}

struct yml_node const *
yml_get_key(struct yml_node const *node, char const *_path) {
    return yml_get_(node, _path, false);
}

struct yml_list_iter
yml_list_iter(const struct yml_node *list)
{
    assert(yml_is_list(list));
    tll_foreach(list->list.values, it) {
        return (struct yml_list_iter){
            .node = it->item,
            .private = it,
        };
    }

    return (struct yml_list_iter){
        .node = NULL,
        .private = NULL,
    };
}

void
yml_list_next(struct yml_list_iter *iter)
{
    if (iter->private == NULL)
        return;

    const struct yml_node *d = (const void *)(uintptr_t)0xdeadbeef;
    __typeof__(d->list.values.head) it = (__typeof__(d->list.values.head))iter->private;
    __typeof__(d->list.values.head) next = it->next;

    iter->node = next != NULL ? next->item : NULL;
    iter->private = next;
}

size_t
yml_list_length(const struct yml_node *list)
{
    assert(yml_is_list(list));

    size_t length = 0;
    for (struct yml_list_iter it = yml_list_iter(list);
         it.node != NULL;
         yml_list_next(&it), length++)
        ;

    return length;
}

struct yml_dict_iter
yml_dict_iter(const struct yml_node *dict)
{
    assert(yml_is_dict(dict));

    tll_foreach(dict->dict.pairs, it) {
        return (struct yml_dict_iter){
            .key = it->item.key,
            .value = it->item.value,
            .private1 = it,
        };
    }

    return (struct yml_dict_iter) {
        .key = NULL,
        .value = NULL,
        .private1 = NULL,
    };
}

void
yml_dict_next(struct yml_dict_iter *iter)
{
    const struct yml_node *d = (const void *)(uintptr_t)0xdeadbeef;
    __typeof__(d->dict.pairs.head) it = (__typeof__(d->dict.pairs.head))iter->private1;

    if (it == NULL)
        return;

    __typeof__(d->dict.pairs.head) next = it->next;
    iter->key = next != NULL ? next->item.key : NULL;
    iter->value = next != NULL ? next->item.value : NULL;
    iter->private1 = next;
}

size_t
yml_dict_length(const struct yml_node *dict)
{
    assert(yml_is_dict(dict));
    return tll_length(dict->dict.pairs);
}

const char *
yml_value_as_string(const struct yml_node *value)
{
    if (!yml_is_scalar(value))
        return NULL;
    return value->scalar.value;
}

static bool
_as_int(const struct yml_node *value, long *ret)
{
    const char *s = yml_value_as_string(value);
    if (s == NULL)
        return false;

    int cnt;
    int res = sscanf(s, "%ld%n", ret, &cnt);
    return res == 1 && strlen(s) == (size_t)cnt;
}

bool
yml_value_is_int(const struct yml_node *value)
{
    long dummy;
    return _as_int(value, &dummy);
}

long
yml_value_as_int(const struct yml_node *value)
{
    long ret = -1;
    _as_int(value, &ret);
    return ret;
}

static bool
_as_bool(const struct yml_node *value, bool *ret)
{
    if (!yml_is_scalar(value))
        return false;

    const char *v = yml_value_as_string(value);
    if (strcasecmp(v, "y") == 0 ||
        strcasecmp(v, "yes") == 0 ||
        strcasecmp(v, "true") == 0 ||
        strcasecmp(v, "on") == 0)
    {
        *ret = true;
        return true;
    } else if (strcasecmp(v, "n") == 0 ||
               strcasecmp(v, "no") == 0 ||
               strcasecmp(v, "false") == 0 ||
               strcasecmp(v, "off") == 0)
    {
        *ret = false;
        return true;
    }

    return false;
}

bool
yml_value_is_bool(const struct yml_node *value)
{
    bool dummy;
    return _as_bool(value, &dummy);
}

bool
yml_value_as_bool(const struct yml_node *value)
{
    bool ret = false;
    _as_bool(value, &ret);
    return ret;
}

size_t
yml_source_line(const struct yml_node *node)
{
    return node->line;
}

size_t
yml_source_column(const struct yml_node *node)
{
    return node->column;
}

static void
_print_node(const struct yml_node *n, int indent)
{
    if (n == NULL)
        return;

    switch (n->type) {
    case ROOT:
        _print_node(n->root.root, indent);
        break;

    case DICT:
        tll_foreach(n->dict.pairs, it) {
            _print_node(it->item.key, indent);
            printf(": ");

            if (it->item.value->type != SCALAR) {
                printf("\n");
                _print_node(it->item.value, indent + 2);
            } else {
                _print_node(it->item.value, 0);
                printf("\n");
            }
        }
        break;

    case LIST:
        tll_foreach(n->list.values, it) {
            printf("%*s- ", indent, "");
            if (it->item->type != SCALAR) {
                printf("\n");
                _print_node(it->item, indent + 2);
            } else {
                _print_node(it->item, 0);
            }
        }
        break;

    case SCALAR:
        printf("%*s%s", indent, "", n->scalar.value);
        break;
    }
}

void
print_node(const struct yml_node *n)
{
    _print_node(n, 0);
}
