#ifndef CORE_COMPONENTS_HPP
#define CORE_COMPONENTS_HPP

#include "core/data/HashMap.hpp"

namespace Core {
    using Entity = int;

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

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

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

        public:
            EntityIterator(C& components_, size_t 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 final {
            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>
        bool put(T*& t, Entity ent, Args&&... args) {
            size_t index = components.getLength();
            size_t* indexP = nullptr;
            if(!entityToIndex.tryEmplace(indexP, ent, index)) {
                return false;
            }
            indexToEntity.add(ent);
            t = &components.put(Core::forward<Args>(args)...);
            return true;
        }

        template<typename... Args>
        bool add(Entity e, Args&&... args) {
            T* t = nullptr;
            return put(t, e, Core::forward<Args>(args)...);
        }

        bool remove(Entity ent) {
            size_t* indexP = entityToIndex.search(ent);
            if(indexP == nullptr) {
                return false;
            }
            size_t lastIndex = components.getLength() - 1;
            size_t index = *indexP;
            entityToIndex.remove(ent);
            components.removeBySwap(index);
            if(index == lastIndex) {
                indexToEntity.removeBySwap(index);
                return true;
            }
            Entity other = indexToEntity[lastIndex];
            indexToEntity.removeBySwap(index);
            entityToIndex.add(other, index);
            return true;
        }

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

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

        auto begin() {
            return components.begin();
        }

        auto begin() const {
            return components.begin();
        }

        auto end() {
            return components.end();
        }

        auto end() const {
            return components.end();
        }

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

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

#endif