#include "ast.h"
#include "scope.h"
#include <stdlib.h>

static void expr_destroy(struct expr_node* node);
static void stmt_destroy(struct stmt_node* node);

static void var_decl_destroy(struct var_decl_node* node) {
    if (node->initial_value != NULL) {
        expr_destroy(node->initial_value);
        free(node->initial_value);
    }
}

static void assign_destroy(struct assign_node* node) {
    expr_destroy(node->lval);
    free(node->lval);
    expr_destroy(node->rval);
    free(node->rval);
}

static void group_destroy(struct group_node* node) {
    struct stmt_node* body_node = node->head;
    while (body_node != NULL) {
        struct stmt_node* next = body_node->next;
        stmt_destroy(body_node);
        free(body_node);
        body_node = next;
    }

    scope_destroy(node->scope);
    free(node->scope);
}

static void decl_list_destroy(struct decl_list_node* node) {
    var_decl_destroy(node->decl);
    free(node->decl);

    if (node->next != NULL) {
        decl_list_destroy(node->next);
        free(node->next);
    }
}

static void fn_decl_destroy(struct fn_decl_node* node) {
    if (node->args != NULL) {
        decl_list_destroy(node->args);
        free(node->args);
    }

    group_destroy(&node->body);

    scope_destroy(node->scope);
    free(node->scope);
}

static void return_destroy(struct return_node* node) {
    if (node->ret_val != NULL) {
        expr_destroy(node->ret_val);
        free(node->ret_val);
    }
}

static void str_lit_destroy(struct str_lit_node* node) {
    free(node->val);
}

static void expr_list_destroy(struct expr_list_node* node) {
    expr_destroy(node->expr);
    free(node->expr);

    if (node->next != NULL) {
        expr_list_destroy(node->next);
        free(node->next);
    }
}

static void call_destroy(struct call_node* node) {
    if (node->args != NULL) {
        expr_list_destroy(node->args);
        free(node->args);
    }
}

static void unary_destroy(struct unary_node* node) {
    expr_destroy(node->expr);
    free(node->expr);
}

static void binary_destroy(struct binary_node* node) {
    expr_destroy(node->lhs);
    free(node->lhs);

    expr_destroy(node->rhs);
    free(node->rhs);
}

static void expr_destroy(struct expr_node* node) {
    switch (node->type) {
        case EXPR_INT_LIT:
        case EXPR_FLOAT_LIT:
        case EXPR_CHAR_LIT:
            break;
        case EXPR_STR_LIT:
            str_lit_destroy(&node->inner.str_lit);
            break;
        case EXPR_VAR_REF:
            break;
        case EXPR_ASSIGN:
            assign_destroy(&node->inner.assign);
            break;
        case EXPR_CALL:
            call_destroy(&node->inner.call);
            break;
        case EXPR_UNARY:
            unary_destroy(&node->inner.unary);
            break;
        case EXPR_BINARY:
            binary_destroy(&node->inner.binary);
            break;
    }
}

static void if_destroy(struct if_node* node) {
    expr_destroy(node->cond);
    free(node->cond);

    stmt_destroy(node->true_branch);
    free(node->true_branch);

    if (node->false_branch != NULL) {
        stmt_destroy(node->false_branch);
        free(node->false_branch);
    }

    scope_destroy(node->scope);
    free(node->scope);
}

static void loop_init_destroy(struct loop_init_node* node) {
    switch (node->type) {
        case INIT_EXPR_LIST:
            expr_list_destroy(node->expr_list);
            free(node->expr_list);
            break;
        case INIT_DECL:
            var_decl_destroy(node->decl);
            free(node->decl);
            break;
    }
}

static void loop_destroy(struct loop_node* node) {
    if (node->init != NULL) {
        loop_init_destroy(node->init);
        free(node->init);
    }
    if (node->cond != NULL) {
        expr_destroy(node->cond);
        free(node->cond);
    }
    if (node->incr != NULL) {
        expr_destroy(node->incr);
        free(node->incr);
    }

    stmt_destroy(node->body);
    free(node->body);

    scope_destroy(node->scope);
    free(node->scope);
}

static void stmt_destroy(struct stmt_node* node) {
    switch (node->type) {
        case STMT_EMPTY:
            break;
        case STMT_EXPR:
            expr_destroy(&node->inner.expr);
            break;
        case STMT_VAR_DECL:
            var_decl_destroy(&node->inner.var_decl);
            break;
        case STMT_RETURN:
            return_destroy(&node->inner.return_);
            break;
        case STMT_GROUP:
            group_destroy(&node->inner.group);
            break;
        case STMT_IF:
            if_destroy(&node->inner.if_);
            break;
        case STMT_LOOP:
            loop_destroy(&node->inner.loop);
            break;
    }
}

static void root_node_destroy(struct root_node* node) {
    switch (node->type) {
        case ROOT_FN_DECL:
            fn_decl_destroy(&node->inner.fn_decl);
            break;
    }
}

void ast_destroy(struct ast* ast) {
    struct root_node* node = ast->root_node;
    while (node != NULL) {
        struct root_node* next = node->next;
        root_node_destroy(node);
        free(node);
        node = next;
    }

    scope_destroy(ast->root_scope);
    free(ast->root_scope);
}
