struct Lisp_Object;
struct String;
struct Environment;

define_array_list(Lisp_Object*, Lisp_Object);
define_array_list(String*, String);
define_array_list(int, Int);
define_array_list(void*, Void_Ptr);

enum struct Lisp_Object_Type {
    Nil,
    T,
    Symbol,
    Keyword,
    Number,
    String,
    Pair,
    // Pointer,
    Function,
    CFunction,
};

enum struct Function_Type {
    Lambda,
    Special_Lambda,
    Macro
};

enum struct Error_Type {
    Assertion_Error,
    File_Not_Found,
    Ill_Formed_Arguments,
    Ill_Formed_Lambda_List,
    Ill_Formed_List,
    Not_A_Function,
    Not_Yet_Implemented,
    Out_Of_Memory,
    Symbol_Not_Defined,
    Syntax_Error,
    Trailing_Garbage,
    Type_Missmatch,
    Unbalanced_Parenthesis,
    Unexpected_Eof,
    Unknown_Error,
    Unknown_Keyword_Argument,
    Wrong_Number_Of_Arguments,
};

enum struct Log_Level {
    None,
    Critical,
    Warning,
    Info,
    Debug,
};

struct String {
    int length;
    char data;
};

struct Source_Code_Location {
    String* file;
    int line;
    int column;
};

struct Symbol {
    String* identifier;
};

struct Keyword {
    String* identifier;
};

struct Number {
    double value;
};

struct Pair {
    Lisp_Object* first;
    Lisp_Object* rest;
};

struct Positional_Arguments {
    // TODO(Felix) use Lisp_Object_symbols here instead, so we don't have
    // to convert them to strings and back to symbols
    String** identifiers; // Array of Pointers to String
    int next_index;
    int length;
};

struct Keyword_Arguments {
    String** identifiers; // Array of Pointers to String
    // NOTE(Felix): values[i] will be nullptr if no defalut value was
    // declared for key identifiers[i]
    Lisp_Object_Array_List* values;
    int next_index;
    int length;
};

struct Function {
    Function_Type type;
    String*  docstring;
    Positional_Arguments* positional_arguments;
    Keyword_Arguments*    keyword_arguments;
    // rest_argument will be nullptr if no rest argument is declared
    String*      rest_argument;
    Lisp_Object* body; // implicit prog
    Environment* parent_environment; // we are doing closures now!!
};

struct cFunction {
    std::function<Lisp_Object* (Lisp_Object*, Environment*)> function;
};

struct Lisp_Object {
    Source_Code_Location* sourceCodeLocation;
    Lisp_Object_Type type;
    Lisp_Object* userType;
    union {
        Symbol*    symbol;
        Keyword*   keyword;
        Number*    number;
        String*    string;
        Pair*      pair;
        Function*  function;
        cFunction* cFunction;
    } value;
};

struct Parsed_Arguments {
    Lisp_Object_Array_List* positional_arguments;
    // TODO(Felix): Really use hashmap (keyword[sting] ->
    // value[Lisp_Object*]) here
    Lisp_Object_Array_List* keyword_keys;
    Lisp_Object_Array_List* keyword_values;
};

struct Environment {
    Environment* parent;

    int       capacity;
    int       next_index;
    // TODO(Felix): Use a hashmap here.
    char**     keys;
    Lisp_Object** values;
};

struct Error {
    Error_Type type;
    Source_Code_Location*  location;
};
