#pragma once
#include "mytypes.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

enum flags
{
    LOOKUP_GLOBAL = 1,
    LOOKUP_LOCAL  = 2,
    LOOKUP_PARENT = 4,
    LOOKUP_FUN    = 8,
    LOOKUP_VAR    = 16,

    // Flag types (lookup type and lookup scope)
    _LOOKUP_TYPE  = LOOKUP_FUN | LOOKUP_VAR,
    _LOOKUP_SCOPE = LOOKUP_GLOBAL | LOOKUP_LOCAL | LOOKUP_PARENT
};

struct symbol_table
{
    struct symbol_table* parent;
    struct st_entry*     head;
    struct st_entry*     tail;
    int                  n_vars;
    int                  nest_level;
};

typedef enum
{
    ENTRY_VAR,
    ENTRY_FUN,
} entry_type_t;

struct st_entry
{
    entry_type_t         entry_type;
    struct symbol_table* containing_table;
    struct st_entry*     prev;
    struct st_entry*     next;
    char*                name;
    type_t               type;
    bool                 isexported;
    bool                 isextern;
    bool                 isconst;
    int                  frameoffset;
    int                  import_export_num;
    int                  global_number;
};

struct st_entry_fn
{
    struct st_entry;
    struct symbol_table* child_syms;
    int                  num_params;
    int                  fun_num;
};

struct st_entry_arr
{
    struct st_entry;
    int             n_dims;
    struct st_entry* dim_vars[]; // list of dim vars, allocated in st_insert_arr
};

/* Free a symbol table entry. */
void st_free_entry(struct st_entry* st_entry);

/* Free a symbol table recursively. */
void st_free(struct symbol_table* st);

/* Initialises a new symbol table. */
struct symbol_table* st_init(struct symbol_table* parent);

/* Inserts a new variable into the symbol table. Returns a pointer to the newly inserted entry. */
struct st_entry* st_insert(struct symbol_table* st, char* name, type_t type, bool isexp,
                           bool isext);

/* Inserts a new function into the symbol table. Currently these have the same representation as
 * variables, but they also have a child symbol table for locally scoped variables or functions.
 *
 * Returns a pointer to the newly inserted entry.
 */
struct st_entry_fn* st_insert_fn(struct symbol_table* st_par, char* name, type_t type,
                                 int num_params, bool isexp, bool isext);

/* Inserts a new array into the symbol table. This is mostly the same as a regular variable but has
 * some extra data, namely the number of dimensions and references to the variables that contain the
 * length of each dimension.
 */
struct st_entry_arr* st_insert_arr(struct symbol_table* st, char* name, type_t type, int n_dims,
                                   bool isexp, bool isext);

/* Looks up a symbol in progressively higher scopes. Returns the first symbol with the given
 * name.
 *
 * The saveframeno pointer will hold the relative frame number that holds this variable.
 * You can pass NULL to not save the frame number.
 *
 */
struct st_entry* st_lookup(struct symbol_table* st, char* name, enum flags flgs, int* saveframeno);

/* Append the source symbol table to the destination symbol table. */
void st_append_symtabs(struct symbol_table* src, struct symbol_table* dst);

/* Check if a variable exists, but don't return it. */
bool st_check(struct symbol_table* st, char* name, enum flags flgs);

/* Print a symbol table. */
void st_print(struct symbol_table* st, char* funname, int indent);