#ifndef COMPONENTS_H
#define COMPONENTS_H

#include "data/HashMap.h"
#include "utils/Types.h"

typedef uint32 Entity;

template<typename T>
class Components final {
    HashMap<Entity, int> entityToIndex;
    List<Entity> indexToEntity;
    List<T> components;

public:
    template<typename R>
    struct Node {
        const Entity& entity;
        R& component;
    };

    template<typename C, typename R>
    class EntityIterator final {
        C& components;
        int index;

    public:
        EntityIterator(C& components, int index)
            : components(components), index(index) {
        }

        EntityIterator& operator++() {
            index++;
            return *this;
        }

        bool operator!=(const EntityIterator& other) const {
            return index != other.index;
        }

        Node<R> operator*() const {
            return {components.indexToEntity[index],
                    components.components[index]};
        }
    };

    template<typename C, typename R>
    struct EntityIteratorAdapter {
        C& components;

        EntityIterator<C, R> begin() {
            return EntityIterator<C, R>(components, 0);
        }

        EntityIterator<C, R> end() {
            return EntityIterator<C, R>(components,
                                        components.components.getLength());
        }
    };

    template<typename... Args>
    void add(Entity e, Args&&... args) {
        int index = components.getLength();
        if(entityToIndex.tryEmplace(e, index)) {
            return;
        }
        components.add(std::forward<Args>(args)...);
        indexToEntity.add(e);
    }

    void remove(Entity e) {
        int* indexP = entityToIndex.search(e);
        if(indexP == nullptr) {
            return;
        }
        int lastIndex = components.getLength() - 1;
        int index = *indexP;
        entityToIndex.remove(e);
        components.removeBySwap(index);
        if(index == lastIndex) {
            indexToEntity.removeBySwap(index);
            return;
        }
        Entity other = indexToEntity[lastIndex];
        indexToEntity.removeBySwap(index);
        entityToIndex.add(other, index);
    }

    T* search(Entity e) {
        int* index = entityToIndex.search(e);
        if(index == nullptr) {
            return nullptr;
        }
        return &(components[*index]);
    }

    const T* search(Entity e) const {
        const int* index = entityToIndex.search(e);
        if(index == nullptr) {
            return nullptr;
        }
        return &(components[*index]);
    }

    T* begin() {
        return components.begin();
    }

    const T* begin() const {
        return components.begin();
    }

    T* end() {
        return components.end();
    }

    const T* end() const {
        return components.end();
    }

    EntityIteratorAdapter<Components, T> entities() {
        return {*this};
    }

    EntityIteratorAdapter<const Components, const T> entities() const {
        return {*this};
    }
};

#endif