
#include <ast/ast.h>
#include <stdio.h>
#include <sys/log.h>
#include <assert.h>
#include <mem/cache.h>

struct AST_Node_t *AST_new_node(TokenLocation location, enum AST_SyntaxElement_t kind, const char* value) {
    DEBUG("creating new AST node: %d \"%s\"", kind, value);
    assert(kind < AST_ELEMENT_COUNT);

    struct AST_Node_t *node = mem_alloc(MemoryNamespaceAst, sizeof(struct AST_Node_t));

    if (node == NULL) {
        PANIC("failed to allocate AST node");
    }

    assert(node != NULL);

    // init to discrete state
    node->parent = NULL;
    node->children = mem_new_g_array(MemoryNamespaceAst, sizeof(AST_NODE_PTR));
    node->kind = kind;
    node->value = value;
    node->location = location;

    return node;
}

static const char* lookup_table[AST_ELEMENT_COUNT] = { "__UNINIT__" };

void AST_init() {
    DEBUG("initializing global syntax tree...");

    INFO("filling lookup table...");
    lookup_table[AST_Stmt] = "stmt";
    lookup_table[AST_Module] = "module";
    lookup_table[AST_Expr] = "expr";

    lookup_table[AST_Add] = "+";
    lookup_table[AST_Sub] = "-";
    lookup_table[AST_Mul] = "*";
    lookup_table[AST_Div] = "/";

    lookup_table[AST_BitAnd] = "&";
    lookup_table[AST_BitOr] = "|";
    lookup_table[AST_BitXor] = "^";
    lookup_table[AST_BitNot] = "!";

    lookup_table[AST_Eq] = "==";
    lookup_table[AST_Less] = "<";
    lookup_table[AST_Greater] = ">";

    lookup_table[AST_BoolAnd] = "&&";
    lookup_table[AST_BoolOr] = "||";
    lookup_table[AST_BoolXor] = "^^";
    lookup_table[AST_BoolNot] = "!!";

    lookup_table[AST_While] = "while";
    lookup_table[AST_If] = "if";
    lookup_table[AST_IfElse] = "else if";
    lookup_table[AST_Else] = "else";

    lookup_table[AST_Decl] = "decl";
    lookup_table[AST_Assign] = "assign";
    lookup_table[AST_Def] = "def";

    lookup_table[AST_Typedef] = "typedef";
    lookup_table[AST_Box] = "box";
    lookup_table[AST_Fun] = "fun";

    lookup_table[AST_Call] = "funcall";
    lookup_table[AST_Typecast] = "typecast";
    lookup_table[AST_Transmute] = "transmute";
    lookup_table[AST_Condition] = "condition";
    lookup_table[AST_List] = "list";
    lookup_table[AST_ExprList] = "expr list";
    lookup_table[AST_ArgList] = "arg list";
    lookup_table[AST_ParamList] = "param list";
    lookup_table[AST_StmtList] = "stmt list";
    lookup_table[AST_IdentList] = "ident list";
    lookup_table[AST_Type] = "type";
    lookup_table[AST_Negate] = "-";
    lookup_table[AST_Parameter] = "parameter";
    lookup_table[AST_ParamDecl] = "parameter-declaration";
    lookup_table[AST_AddressOf] = "address of";
    lookup_table[AST_Dereference] = "deref";
    lookup_table[AST_Reference] = "ref";
    lookup_table[AST_Reference] = "ret";
}

const char* AST_node_to_string(const struct AST_Node_t* node) {
    DEBUG("converting AST node to string: %p", node);
    assert(node != NULL);

    const char* string;

    switch(node->kind) {
        case AST_Int:
        case AST_Char:
        case AST_Float:
        case AST_String:
        case AST_Ident:
        case AST_Macro:
        case AST_Import:
        case AST_Include:
        case AST_Storage:
        case AST_Typekind:
        case AST_Sign:
        case AST_Scale:
        case AST_Qualifyier:
            string = node->value;
            break;
        default:
            string = lookup_table[node->kind];
    }

    assert(string != NULL);

    return string;
}

static inline unsigned long int min(unsigned long int a, unsigned long int b) {
    return a > b ? b : a;
}

static inline unsigned long int max(unsigned long int a, unsigned long int b) {
    return a > b ? a : b;
}

void AST_push_node(struct AST_Node_t *owner, struct AST_Node_t *child) {
    DEBUG("Adding new node %p to %p", child, owner);
    assert(owner != NULL);
    assert(child != NULL);

    if (owner->children == NULL) {
        PANIC("failed to allocate children array of AST node");
    }

    owner->location.col_end = max(owner->location.col_end, child->location.col_end);
    owner->location.line_end = max(owner->location.line_end, child->location.line_end);

    owner->location.col_start = min(owner->location.col_start, child->location.col_start);
    owner->location.line_start = min(owner->location.line_start, child->location.line_start);

    if (owner->location.file == NULL) {
        owner->location.file = child->location.file;
    }

    assert(owner->children != NULL);

    g_array_append_val(owner->children, child);
}

struct AST_Node_t *AST_get_node(struct AST_Node_t *owner, const size_t idx) {
    DEBUG("retrvieng node %d from %p", idx, owner);
    assert(owner != NULL);
    assert(owner->children != NULL);
    assert(idx < owner->children->len);

    if (owner->children == NULL) {
        PANIC("AST owner node has no children");
    }

    AST_NODE_PTR child = g_array_index(owner->children, AST_NODE_PTR, idx);

    if (child == NULL) {
        PANIC("child node is NULL");
    }

    return child;
}

struct AST_Node_t* AST_remove_child(struct AST_Node_t* owner, const size_t idx) {
    assert(owner != NULL);
    assert(owner->children != NULL);
    assert(idx < owner->children->len);

    AST_NODE_PTR child = g_array_index(owner->children, AST_NODE_PTR, idx);
    g_array_remove_index(owner->children, idx);

    child->parent = NULL;

    return child;
}

struct AST_Node_t* AST_detach_child(struct AST_Node_t* owner, const struct AST_Node_t* child) {
    assert(owner != NULL);
    assert(child != NULL);
    assert(owner->children != NULL);

    for (size_t i = 0; i < owner->children->len; i++) {
        if (g_array_index(owner->children, AST_NODE_PTR, i) == child) {
            return AST_remove_child(owner, i);
        }
    }

    PANIC("Child to detach not a child of parent");
}

void AST_delete_node(struct AST_Node_t *node) {
    assert(node != NULL);

    DEBUG("Deleting AST node: %p", node);

    if (node->parent != NULL) {
        [[maybe_unused]]
        const struct AST_Node_t* child = AST_detach_child(node->parent, node);
        assert(child == node);
    }

    if (node->children != NULL) {
        for (size_t i = 0; i < AST_get_child_count(node); i++) {
            // prevent detach of children node
            AST_get_node(node, i)->parent = NULL;
            AST_delete_node(AST_get_node(node, i));
        }
        mem_free(node->children);
    }

    mem_free(node);
}

static void AST_visit_nodes_recurse2(struct AST_Node_t *root,
                                     void (*for_each)(struct AST_Node_t *node,
                                                      size_t depth),
                                     const size_t depth) {
    DEBUG("Recursive visit of %p at %d with %p", root, depth, for_each);

    assert(root != NULL);

    (for_each)(root, depth);

    for (size_t i = 0; i < root->children->len; i++) {
        AST_visit_nodes_recurse2(g_array_index(root->children, AST_NODE_PTR, i), for_each, depth + 1);
    }
}

void AST_visit_nodes_recurse(struct AST_Node_t *root,
                             void (*for_each)(struct AST_Node_t *node,
                                              size_t depth)) {
    DEBUG("Starting recursive visit of %p with %p", root, for_each);

    assert(root != NULL);
    assert(for_each != NULL);

    AST_visit_nodes_recurse2(root, for_each, 0);
}

static void AST_fprint_graphviz_node_definition(FILE* stream, const struct AST_Node_t* node) {
    DEBUG("Printing graphviz definition of %p", node);

    assert(stream != NULL);
    assert(node != NULL);

    fprintf(stream, "\tnode%p [label=\"%s\"]\n", (void*) node, AST_node_to_string(node));

    if (node->children == NULL) {
        return;
    }

    for (size_t i = 0; i < node->children->len; i++) {
        AST_fprint_graphviz_node_definition(stream, g_array_index(node->children, AST_NODE_PTR, i));
    }
}

static void AST_fprint_graphviz_node_connection(FILE* stream, const struct AST_Node_t* node) {
    DEBUG("Printing graphviz connection of %p", node);

    assert(stream != NULL);
    assert(node != NULL);

    if (node->children == NULL) {
        return;
    }

    for (size_t i = 0; i < node->children->len; i++) {
        AST_NODE_PTR child = g_array_index(node->children, AST_NODE_PTR, i);
        fprintf(stream, "\tnode%p -- node%p\n", (void*) node, (void*) child);
        AST_fprint_graphviz_node_connection(stream, child);
    }
}

void AST_fprint_graphviz(FILE* stream, const struct AST_Node_t* root) {
    DEBUG("Starting print of graphviz graph of %p", root);

    assert(stream != NULL);
    assert(root != NULL);

    fprintf(stream, "graph {\n");

    AST_fprint_graphviz_node_definition(stream, root);
    AST_fprint_graphviz_node_connection(stream, root);

    fprintf(stream, "}\n");
}

AST_NODE_PTR AST_get_node_by_kind(AST_NODE_PTR owner, enum AST_SyntaxElement_t kind) {
  for (size_t i = 0; i < owner->children->len; i++) {
    AST_NODE_PTR child = AST_get_node(owner, i);

    if (child->kind == kind) {
      return child;
    }
  }

  return NULL;
}

void AST_merge_modules(AST_NODE_PTR dst, size_t k, AST_NODE_PTR src) {
    assert(dst != NULL);
    assert(src != NULL);

    size_t elements = src->children->len;
    for (size_t i = 0; i < elements; i++) {
        AST_insert_node(dst, k + i, AST_remove_child(src, 0));
    }
    AST_delete_node(src);
}

void AST_insert_node(AST_NODE_PTR owner, size_t idx, AST_NODE_PTR child) {
    assert(owner != NULL);
    assert(child != NULL);

    DEBUG("Reallocating old children array");

    g_array_insert_val(owner->children, idx, child);
}

size_t AST_get_child_count(AST_NODE_PTR node) {
    return node->children->len;
}

AST_NODE_PTR AST_get_last_node(AST_NODE_PTR node) {
    assert(node != NULL);

    return g_array_index(node->children, AST_NODE_PTR, node->children->len - 1);
}
