#ifndef AST_H
#define AST_H

#include "scope.h"

struct stmt_node;
struct expr_node;

struct type_ref_node {
    struct type_ref type;
};

struct int_lit_node {
    long long val;
};

struct float_lit_node {
    double val;
};

struct char_lit_node {
    char val;
};

struct str_lit_node {
    char* val;
};

struct var_ref_node {
    struct var_def* def_ref;
};

struct var_decl_node {
    struct type_ref_node type;
    struct var_def* def_ref;
};

struct lval_node {
    enum {
        LVAL_VAR_DECL,
        LVAL_VAR_REF,
    } type;
    union {
        struct var_ref_node var_ref;
        struct var_decl_node var_decl;
    } inner;

    const struct type_def* resolved_type;
};

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

struct args_eval_node {
    struct expr_node* expr;
    struct args_eval_node* next;
};

struct call_node {
    /* TODO: eventually this could also be a function pointer */
    struct fn_decl_node* called_fn_ref; /* borrowed */
    struct args_eval_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 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,
    } 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;
    } inner;

    const struct type_def* resolved_type;
};

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

struct args_decl_node {
    struct var_decl_node* decl;
    struct args_decl_node* next;
};

struct fn_decl_node {
    struct type_ref_node return_type;
    char* name;
    struct args_decl_node* args;
    struct group_node body;
    struct scope* scope;

    const struct type_def* resolved_return_type;
};

struct return_node {
    struct expr_node* ret_val; /* null to return void */
};

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

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

struct stmt_node {
    enum {
        STMT_EMPTY,
        STMT_EXPR,
        STMT_VAR_DECL,
        STMT_RETURN,
        STMT_GROUP,
        STMT_IF,
        STMT_LOOP,
    } type;
    union {
        struct expr_node expr;
        struct var_decl_node var_decl;
        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;

    /* nice shortcuts to have */
    const struct type_def* void_type;
    const struct type_def* char_type;
    const struct type_def* integral_type;
    const struct type_def* decimal_type;
    const struct type_def* pointer_type;
};

void ast_destroy(struct ast* ast);

#endif
