namespace Memory {

    // ------------------
    //   lisp_object
    // ------------------
    constexpr int object_memory_size = 4096 * 1024; // == 98304kb == 96mb
    Int_Array_List* free_spots_in_object_memory;
    Lisp_Object* object_memory;
    int next_index_in_object_memory = 0;

    // ------------------
    //   strings
    // ------------------
    constexpr int string_memory_size = 4096 * 1024; // == 98304kb == 96mb
    // free_spots_in_string_memory is an arraylist of pointers into
    // the string_memory, where dead String objects live (which give
    // information about their size)
    Void_Ptr_Array_List* free_spots_in_string_memory;
    String* string_memory;
    String* next_free_spot_in_string_memory;


    // ------------------
    //   immutables
    // ------------------
    Lisp_Object* nil = nullptr;
    Lisp_Object* t = nullptr;

    proc print_status() {
        printf("Memory Status:\n"
               " - %f%% of the object_memory is used\n"
               " - %d of %d total Lisp_Objects are in use\n"
               " - %d holes in used memory (fragmentation)\n",
               (1.0*next_index_in_object_memory - free_spots_in_object_memory->next_index)/object_memory_size,
               next_index_in_object_memory - free_spots_in_object_memory->next_index, object_memory_size,
               free_spots_in_object_memory->next_index);

        printf("Memory Status:\n"
               " - %f%% of the string_memory is used\n"
               " - %d holes in used memory (fragmentation)\n",
               (1.0*(size_t)next_free_spot_in_string_memory - (size_t)string_memory)/string_memory_size,
               free_spots_in_string_memory->next_index);
    }

    inline proc get_c_str(String* str) -> char* {
        return &str->data;
    }

    inline proc get_c_str(Lisp_Object* str) -> char* {
        assert_type(str, Lisp_Object_Type::String);
        return get_c_str(str->value.string);
    }

    proc create_string(const char* str, int len) -> String* {
        // TODO(Felix): check the holes first, not just always append
        // at the end

        String* ret = next_free_spot_in_string_memory;
        ret->length = len;
        strcpy(&ret->data, str);

        // now update the next_free_spot_in_string_memory pointer:
        // overstrep the counter and the first char (thik of it as if
        // we were overstepping the last ('\0') char) and then we only
        // need to overstep 'len' more chars
        next_free_spot_in_string_memory += 1;

        // overstep the other chars
        next_free_spot_in_string_memory = ((String*)((char*)next_free_spot_in_string_memory)+len);
        return ret;
    }

    proc delete_string(String* str) {
        append_to_array_list(free_spots_in_string_memory, (void*)str);
    }

    proc duplicate_string(String* str) -> String* {
        return create_string(get_c_str(str), str->length);
    }

    proc create_string (const char* str) -> String* {
        return create_string(str, (int)strlen(str));
    }

    proc create_lisp_object() -> Lisp_Object* {
        int index;
        // if we have no free spots then append at the end
        if (free_spots_in_object_memory->next_index == 0) {
            // if we still have space
            if (object_memory_size == next_index_in_object_memory) {
                create_error(Error_Type::Out_Of_Memory, nullptr);
                return nullptr;
            }
            index = next_index_in_object_memory++;
        } else {
            // else fill a free spot
            index = free_spots_in_object_memory->data[free_spots_in_object_memory->next_index--];
        }
        Lisp_Object* object = object_memory+index;
        object->sourceCodeLocation = nullptr;
        object->userType = nullptr;
        return object;
    }

    proc init() {
        free_spots_in_object_memory = create_Int_array_list();
        free_spots_in_string_memory = create_Void_Ptr_array_list();

        object_memory = (Lisp_Object*)malloc(object_memory_size * sizeof(Lisp_Object));
        string_memory = (String*)malloc(object_memory_size * sizeof(Lisp_Object));

        next_free_spot_in_string_memory = string_memory;

        // init nil
        nil = create_lisp_object();
        nil->type = Lisp_Object_Type::Nil;
        nil->value.pair = nullptr;

        // init t
        t = create_lisp_object();
        t->type = Lisp_Object_Type::T;
        t->value.pair = nullptr;
    }

    // proc get_lisp_object_nil() -> Lisp_Object* {
    //     Lisp_Object* node = create_lisp_object();
    //     node->type = Lisp_Object_Type::Nil;
    //     node->value.pair = nullptr;
    //     return node;
    // }

    // proc get_lisp_object_t() -> Lisp_Object* {
    //     Lisp_Object* node = create_lisp_object();
    //     node->type = Lisp_Object_Type::T;
    //     node->value.pair = nullptr;
    //     return node;
    // }

    proc create_lisp_object_number(double number) -> Lisp_Object* {
        Lisp_Object* node = create_lisp_object();
        node->type = Lisp_Object_Type::Number;
        node->value.number = new(Number);
        node->value.number->value = number;
        return node;
    }

    proc create_lisp_object_string(String* str) -> Lisp_Object* {
        Lisp_Object* node = create_lisp_object();
        node->type = Lisp_Object_Type::String;
        node->value.string = str;
        return node;
    }

    proc create_lisp_object_string(char* str) -> Lisp_Object* {
        Lisp_Object* node = create_lisp_object();
        node->type = Lisp_Object_Type::String;
        node->value.string = create_string(str);
        return node;
    }

    proc get_or_create_lisp_object_symbol(String* identifier) -> Lisp_Object* {
        // TODO(Felix): if we already have it stored somewhere then
        // reuse it and dont create new one
        Lisp_Object* node = create_lisp_object();
        node->type = Lisp_Object_Type::Symbol;
        node->value.symbol = new(Symbol);
        node->value.symbol->identifier = identifier;
        return node;
    }

    proc get_or_create_lisp_object_symbol(const char* identifier) -> Lisp_Object* {
        return get_or_create_lisp_object_symbol(
            Memory::create_string(identifier));
    }

    proc get_or_create_lisp_object_keyword(String* keyword) -> Lisp_Object* {
        // TODO(Felix): if we already have it stored somewhere then
        // reuse it and dont create new one
        Lisp_Object* node = create_lisp_object();
        node->type = Lisp_Object_Type::Keyword;
        node->value.keyword = new(Keyword);
        node->value.keyword->identifier = keyword;
        return node;
    }

    proc get_or_create_lisp_object_keyword(const char* keyword) -> Lisp_Object* {
        return get_or_create_lisp_object_keyword(
            Memory::create_string(keyword));
    }

    proc create_lisp_object_cfunction(std::function<Lisp_Object* (Lisp_Object*, Environment*)> function) -> Lisp_Object* {
        Lisp_Object* node = create_lisp_object();
        node->type = Lisp_Object_Type::CFunction;
        // node->value.lambdaWrapper = new Lambda_Wrapper(function);
        node->value.cFunction = new(cFunction);
        node->value.cFunction->function = function;
        return node;
    }

    proc create_lisp_object_pair(Lisp_Object* first, Lisp_Object* rest) -> Lisp_Object* {
        Lisp_Object* node = create_lisp_object();
        node->type = Lisp_Object_Type::Pair;
        node->value.pair = new(Pair);
        node->value.pair->first = first;
        node->value.pair->rest = rest;
        return node;
    }

    proc copy_lisp_object(Lisp_Object* n) -> Lisp_Object* {
        Lisp_Object* target = create_lisp_object();
        *target = *n;
        return target;
    }

    proc create_child_environment(Environment* parent) -> Environment* {
        Environment* env = new(Environment);

        int start_capacity = 16;

        env->parent     = parent;
        env->capacity   = start_capacity;
        env->next_index = 0;
        env->keys       =        (char**)malloc(start_capacity * sizeof(char*));
        env->values     = (Lisp_Object**)malloc(start_capacity * sizeof(Lisp_Object*));

        return env;
    }

    proc create_empty_environment() -> Environment* {
        return create_child_environment(nullptr);
    }

    proc create_built_ins_environment() -> Environment* {
        Environment* ret = create_child_environment(nullptr);
        load_built_ins_into_environment(ret);
        return ret;
    }

    inline proc create_list(Lisp_Object* o1) -> Lisp_Object* {
        return create_lisp_object_pair(o1, nil);
    }

    inline proc create_list(Lisp_Object* o1, Lisp_Object* o2) -> Lisp_Object* {
        return create_lisp_object_pair(o1, create_list(o2));
    }

    inline proc create_list(Lisp_Object* o1, Lisp_Object* o2, Lisp_Object* o3) -> Lisp_Object* {
        return create_lisp_object_pair(o1, create_list(o2, o3));
    }

    inline proc create_list(Lisp_Object* o1, Lisp_Object* o2, Lisp_Object* o3, Lisp_Object* o4) -> Lisp_Object* {
        return create_lisp_object_pair(o1, create_list(o2, o3, o4));
    }

    inline proc create_list(Lisp_Object* o1, Lisp_Object* o2, Lisp_Object* o3, Lisp_Object* o4, Lisp_Object* o5) -> Lisp_Object* {
        return create_lisp_object_pair(o1, create_list(o2, o3, o4, o5));
    }

    inline proc create_list(Lisp_Object* o1, Lisp_Object* o2, Lisp_Object* o3, Lisp_Object* o4, Lisp_Object* o5, Lisp_Object* o6) -> Lisp_Object* {
        return create_lisp_object_pair(o1, create_list(o2, o3, o4, o5, o6));
    }

}
