
#ifndef _AST_H_
#define _AST_H_

#include <stdio.h>
#include <io/files.h>

/**
 * @brief The type of a AST node
 * @attention The last element is not to be used in the AST
 *            as it is used as a lazy way to get the total number of available
 *            variants of this enum.
 */
enum AST_SyntaxElement_t {
    AST_Stmt = 0,
    AST_Module,
    AST_Expr,
    // Literals
    AST_Int,
    AST_Float,
    AST_String,
    // Control flow
    AST_While,
    AST_If,
    AST_IfElse,
    AST_Else,
    AST_Condition,
    // Variable management
    AST_Decl,
    AST_Assign,
    AST_Def,
    AST_Ident,
    // Arithmetic operators
    AST_Add,
    AST_Sub,
    AST_Mul,
    AST_Div,
    // Bitwise operators
    AST_BitAnd,
    AST_BitOr,
    AST_BitXor,
    AST_BitNot,
    // Boolean operators
    AST_BoolAnd,
    AST_BoolOr,
    AST_BoolXor,
    AST_BoolNot,
    // Logical operators
    AST_Eq,
    AST_Greater,
    AST_Less,
    // Casts
    AST_Typecast,   // type cast
    AST_Transmute,  // reinterpret cast
    AST_Call,       // function call
    AST_Macro,      // builtin functions: lineno(), filename(), ...
    // Defintions
    AST_Typedef,
    AST_Box,
    AST_Fun,
    AST_Import,
    // amount of variants
    // in this enums
    AST_List,
    AST_ExprList,
    AST_ArgList,
    AST_ParamList,
    AST_StmtList,
    AST_IdentList,
    AST_Storage,
    AST_Type,
    AST_Typekind,
    AST_Sign,
    AST_Scale,
    AST_Negate,
    AST_Parameter,
    AST_Qualifyier,
    AST_ParamDecl,
    AST_AddressOf,
    AST_Dereference,
    AST_Reference,
    AST_Include,
    AST_Char,
    AST_Return,
    AST_ELEMENT_COUNT
};

/**
 * @brief A single node which can be joined with other nodes like a graph.
 * Every node can have one ancestor (parent) but multiple (also none) children.
 * Nodes have two properties:
 *  - kind: The type of the node. Such as AST_Expr, AST_Add, ...
 *  - value: A string representing an optional value. Can be a integer literal for kind AST_int
 */
typedef struct AST_Node_t {
    // parent node that owns this node
    struct AST_Node_t *parent;

    // type of AST node: if, declaration, ...
    enum AST_SyntaxElement_t kind;
    // optional value: integer literal, string literal, ...
    const char* value;

    TokenLocation location;

    // children array
    GArray* children;
} AST_Node;

/**
 * Shorthand type for a single AST node
 */
typedef struct AST_Node_t* AST_NODE_PTR;

/**
 * @brief Initalize the global state of this module. Required for some functionality to work correctly.
 */
void AST_init(void);

/**
 * @brief Returns the string representation of the supplied node
 * @attention The retuned pointer is not to be freed as it may either be a statically stored string or
 *            used by the node after this function call.
 * @param node to return string representation of
 * @return string represenation of the node
 */
[[maybe_unused]]
[[gnu::nonnull(1)]]
const char* AST_node_to_string(const struct AST_Node_t* node);

/**
 * @brief Create a new node struct on the system heap. Initializes the struct with the given values.
 *        All other fields are set to either NULL or 0. No allocation for children array is preformed.
*  @attention parameter value can be NULL in case no value can be provided for the node
 * @param kind the type of this node
 * @param value an optional value for this node
 * @return
 */
[[maybe_unused]]
[[nodiscard("pointer must be freed")]]
[[gnu::returns_nonnull]]
struct AST_Node_t *AST_new_node(TokenLocation location, enum AST_SyntaxElement_t kind, const char* value);

/**
 * @brief Deallocate this node and all of its children.
 * @attention This function will detach this node from its parent if one is present
 *            Use of the supplied node after this call is undefined behavior
 * @param node The node to deallocate
 */
[[maybe_unused]]
[[gnu::nonnull(1)]]
void AST_delete_node(struct AST_Node_t * node);

/**
 * @brief Add a new child node to a parent node
 * @attention This can reallocate the children array
 * @param owner node to add a child to
 * @param child node to be added as a child
 */
[[maybe_unused]]
[[gnu::nonnull(1), gnu::nonnull(2)]]
void AST_push_node(struct AST_Node_t *owner, struct AST_Node_t *child);

/**
 * @brief Remove the specified child from the owner.
 * @attention The parent of the removed node is set to NULL.
 *            The returned pointer is still valid. It must be freed at some pointer later.
 * @param owner Node to remove the child from
 * @param idx the index of the child to remove
 * @return a pointer to the child which was removed
 */
[[maybe_unused]]
[[nodiscard("pointer must be freed")]]
[[gnu::nonnull(1)]]
struct AST_Node_t* AST_remove_child(struct AST_Node_t* owner, size_t idx);

/**
 * @brief Detach a child from its parent. This involves removing the child from its parent
 *        and marking the parent of the child as NULL.
 * @attention The returned pointer is still valid. It must be freed at some pointer later.
 * @param owner the owner to remove the child from
 * @param child the child to detach
 * @return a pointer to child detached
 */
[[nodiscard("pointer must be freed")]]
[[gnu::nonnull(1), gnu::nonnull(1)]]
struct AST_Node_t* AST_detach_child(struct AST_Node_t* owner, const struct AST_Node_t* child);

/**
 * @brief Return a pointer to the n-th child of a node
 * @attention Pointer to childen nodes will never change.
 *            However, the index a node is stored within a parent can change
 *            if a child of lower index is removed, thus reducing the childrens index by one.
 * @param owner the parent node which owns the children
 * @param idx the index of the child to get a pointer to
 * @return a pointer to the n-th child of the owner node
 */
[[maybe_unused]]
[[gnu::nonnull(1)]]
struct AST_Node_t *AST_get_node(struct AST_Node_t *owner, size_t idx);

AST_NODE_PTR AST_get_last_node(AST_NODE_PTR node);

/**
 * @brief Execute a function for every child, grandchild, ... and the supplied node as topmost ancestor
 * @param root the root to recursively execute a function for
 * @param for_each the function to execute
 */
[[maybe_unused]]
[[gnu::nonnull(1), gnu::nonnull(2)]]
void AST_visit_nodes_recurse(struct AST_Node_t *root,
                             void (*for_each)(struct AST_Node_t *node,
                                              size_t depth));

/**
 * @brief Prints a graphviz graph of the node and all its ancestors.
 * @param stream The stream to print to. Can be a file, stdout, ...
 * @param node the topmost ancestor
 */
[[maybe_unused]]
[[gnu::nonnull(1), gnu::nonnull(2)]]
void AST_fprint_graphviz(FILE* stream, const struct AST_Node_t* node);

AST_NODE_PTR AST_get_node_by_kind(AST_NODE_PTR owner, enum AST_SyntaxElement_t kind);

[[gnu::nonnull(1), gnu::nonnull(3)]]
void AST_merge_modules(AST_NODE_PTR dst, size_t i, AST_NODE_PTR src);

[[gnu::nonnull(1), gnu::nonnull(3)]]
void AST_insert_node(AST_NODE_PTR owner, size_t idx, AST_NODE_PTR child);

size_t AST_get_child_count(AST_NODE_PTR node);

#endif
