#ifndef AST_H
#define AST_H

#include "type.h"
#include "scope.h"

struct stmt_node;
struct expr_node;

struct expr_list_node {
    struct expr_node* expr;
    struct expr_list_node* next;

    const struct type* resolved_type;
};

struct int_lit_node {
    integral_t val;
};

struct float_lit_node {
    floating_t val;
};

struct char_lit_node {
    char val;
};

struct str_lit_node {
    char* val;
};

struct var_ref_node {
    struct var_def* def_ref;
};

struct decl_node {
    struct var_def* def_ref;
    struct expr_node* initial_value;
    struct decl_node* next;
};

struct decl_list_node {
    struct type type;
    struct decl_node* head;
};

struct assign_node {
    struct expr_node* lval;
    struct expr_node* rval;
};

struct call_node {
    /* TODO: function pointers */
    struct fn_decl_node* fn_ref; /* borrowed */
    struct expr_list_node* args;
};

struct unary_node {
    enum {
        UNARY_NEG,
    } op;
    struct expr_node* expr;
};

struct binary_node {
    enum {
        BINARY_ADD,
        BINARY_SUB,
        BINARY_MUL,
        BINARY_DIV,
    } op;
    struct expr_node* lhs;
    struct expr_node* rhs;
};

struct paren_node {
    struct expr_list_node* expr_list;
};

struct cast_node {
    struct type type;
    struct expr_node* expr;
};

struct expr_node {
    enum {
        EXPR_INT_LIT,
        EXPR_FLOAT_LIT,
        EXPR_CHAR_LIT,
        EXPR_STR_LIT,
        EXPR_VAR_REF,
        EXPR_ASSIGN,
        EXPR_CALL,
        EXPR_UNARY,
        EXPR_BINARY,
        EXPR_PAREN,
        EXPR_CAST,
    } type;
    union {
        struct int_lit_node int_lit;
        struct float_lit_node float_lit;
        struct char_lit_node char_lit;
        struct str_lit_node str_lit;
        struct var_ref_node var_ref;
        struct assign_node assign;
        struct call_node call;
        struct unary_node unary;
        struct binary_node binary;
        struct paren_node paren;
        struct cast_node cast;
    } inner;

    const struct type* resolved_type;
};

struct group_node {
    struct stmt_node* head;
    struct scope* scope;
};

struct arg_decl_node {
    struct type type;
    struct var_def* def_ref;
    struct arg_decl_node* next;
};

struct fn_decl_node {
    struct type return_type;
    const char* name;
    struct arg_decl_node* args;
    struct group_node* body;
    struct scope* scope;
};

struct return_node {
    struct fn_decl_node* fn_ref;
    struct expr_list_node* ret_val; /* null to return void */
};

struct if_node {
    struct expr_list_node* cond;
    struct stmt_node* true_branch;
    struct stmt_node* false_branch;
    struct scope* scope;
};

struct loop_init_node {
    enum {
        INIT_EXPR_LIST,
        INIT_DECL_LIST,
    } type;
    union {
        struct expr_list_node* expr_list;
        struct decl_list_node* decl_list;
    };
};

struct loop_node {
    struct loop_init_node* init; /* null if absent */
    struct expr_list_node* cond; /* null if absent */
    struct expr_list_node* incr; /* null if absent */
    struct stmt_node* body;
    struct scope* scope;
};

struct stmt_node {
    enum {
        STMT_EMPTY,
        STMT_EXPR_LIST,
        STMT_DECL_LIST,
        STMT_RETURN,
        STMT_GROUP,
        STMT_IF,
        STMT_LOOP,
    } type;
    union {
        struct expr_list_node expr_list;
        struct decl_list_node decl_list;
        struct return_node return_;
        struct group_node group;
        struct if_node if_;
        struct loop_node loop;
    } inner;

    struct stmt_node* next;
};

struct root_node {
    enum {
        ROOT_FN_DECL,
    } type;
    union {
        struct fn_decl_node fn_decl;
    } inner;

    struct root_node* next;
};

struct ast {
    struct root_node* root_node;
    struct scope* root_scope;
};

void ast_destroy(struct ast* ast);

#endif
