#ifndef CORE_COMPONENTS_H
#define CORE_COMPONENTS_H

#include "core/HashMap.h"
#include "core/List.h"

#define isInvalidKeySize(key) ((key) == 0)
#define equalSize(a, b) ((a) == (b))
#define hashSize(key) (key)

LIST(size_t, Size)
HASHMAP(size_t, size_t, Size)

typedef size_t Entity;

#define COMPONENTS(T, N)                                                       \
    typedef struct {                                                           \
        HashMapSize entityToIndex;                                             \
        ListSize indexToEntity;                                                \
        List##N components;                                                    \
    } Components##N;                                                           \
                                                                               \
    typedef struct {                                                           \
        Entity entity;                                                         \
        T* component;                                                          \
    } ComponentNode##N;                                                        \
                                                                               \
    typedef struct {                                                           \
        const Entity* indexToEntity;                                           \
        const Entity* indexToEntityEnd;                                        \
        T* component;                                                          \
        T* componentEnd;                                                       \
        ComponentNode##N node;                                                 \
    } ComponentIterator##N;                                                    \
                                                                               \
    void initComponents##N(Components##N* c);                                  \
    void destroyComponents##N(Components##N* c);                               \
    T* getOrAddComponent##N(Components##N* c, Entity e);                       \
    T* searchComponent##N(Components##N* c, Entity e);                         \
    bool removeComponent##N(Components##N* c, Entity e);                       \
    void initComponentIterator##N(ComponentIterator##N* ci, Components##N* c); \
    bool hasNextComponentNode##N(ComponentIterator##N* ci);                    \
    ComponentNode##N* nextComponentNode##N(ComponentIterator##N* ci);          \
    T* getComponentsStart##N(Components##N* c);                                \
    T* getComponentsEnd##N(Components##N* c);

#define COMPONENTS_SOURCE(T, N)                                         \
    void initComponents##N(Components##N* c) {                          \
        initHashMapSize(&c->entityToIndex);                             \
        initListSize(&c->indexToEntity);                                \
        initList##N(&c->components);                                    \
    }                                                                   \
                                                                        \
    void destroyComponents##N(Components##N* c) {                       \
        destroyHashMapSize(&c->entityToIndex);                          \
        destroyListSize(&c->indexToEntity);                             \
        destroyList##N(&c->components);                                 \
    }                                                                   \
                                                                        \
    T* getOrAddComponent##N(Components##N* c, Entity e) {               \
        void* component = searchComponent##N(c, e);                     \
        if(component != nullptr) {                                      \
            return component;                                           \
        }                                                               \
        size_t index = c->components.length;                            \
        *putHashMapKeySize(&c->entityToIndex, e) = index;               \
        addListDataSize(&c->indexToEntity, e);                          \
        return addEmptyListData##N(&c->components);                     \
    }                                                                   \
                                                                        \
    T* searchComponent##N(Components##N* c, Entity e) {                 \
        size_t* index = searchHashMapKeySize(&c->entityToIndex, e);     \
        if(index == nullptr) {                                          \
            return nullptr;                                             \
        }                                                               \
        return getListIndex##N(&c->components, *index);                 \
    }                                                                   \
                                                                        \
    bool removeComponent##N(Components##N* c, Entity e) {               \
        size_t* indexP = searchHashMapKeySize(&c->entityToIndex, e);    \
        if(indexP == nullptr) {                                         \
            return false;                                               \
        }                                                               \
        size_t lastIndex = c->components.length - 1;                    \
        size_t index = *indexP;                                         \
        removeHashMapKeySize(&c->entityToIndex, e);                     \
        removeListIndexBySwap##N(&c->components, index);                \
        if(index == lastIndex) {                                        \
            removeListIndexBySwapSize(&c->indexToEntity, index);        \
            return true;                                                \
        }                                                               \
        Entity other = *getListIndexSize(&c->indexToEntity, lastIndex); \
        removeListIndexBySwapSize(&c->indexToEntity, index);            \
        *putHashMapKeySize(&c->entityToIndex, other) = index;           \
        return true;                                                    \
    }                                                                   \
                                                                        \
    void initComponentIterator##N(                                      \
        ComponentIterator##N* ci, Components##N* c) {                   \
        ci->indexToEntity = getListStartSize(&c->indexToEntity);        \
        ci->indexToEntityEnd = getListEndSize(&c->indexToEntity);       \
        ci->component = getListStart##N(&c->components);                \
        ci->componentEnd = getListEnd##N(&c->components);               \
        ci->node = (ComponentNode##N){0};                               \
    }                                                                   \
                                                                        \
    bool hasNextComponentNode##N(ComponentIterator##N* ci) {            \
        return ci->indexToEntity != ci->indexToEntityEnd;               \
    }                                                                   \
                                                                        \
    ComponentNode##N* nextComponentNode##N(ComponentIterator##N* ci) {  \
        ci->node.component = ci->component;                             \
        ci->node.entity = *ci->indexToEntity;                           \
        ci->indexToEntity++;                                            \
        ci->component++;                                                \
        return &ci->node;                                               \
    }                                                                   \
                                                                        \
    T* getComponentsStart##N(Components##N* c) {                        \
        return getListStart##N(&c->components);                         \
    }                                                                   \
                                                                        \
    T* getComponentsEnd##N(Components##N* c) {                          \
        return getListEnd##N(&c->components);                           \
    }                                                                   \
    LIST_SOURCE(size_t, Size)                                           \
    HASHMAP_SOURCE(size_t, size_t, Size)

#endif