#pragma once
#include <catch2/internal/catch_console_colour.hpp>
#include <cstdint>
#include <format>
#include <queue>
#include <stdexcept>
#include <typeindex>
#include <unordered_map>
#include <unordered_set>

namespace zecsy 
{
    using entity_id = uint64_t;
    using entities_set = std::unordered_set<entity_id>; 

    class entity final
    {
    public:
        entity(class world* w, entity_id id);

        entity() = default;
        operator entity_id() const;

        bool is_alive() const;

        template<typename... T>
        bool has() const;

        template<typename T>
        T& get();

        template<typename... T>
        void set();

        template<typename... T>
        void set(const T&... comps);

        template<typename... T>
        void remove();
    private:
        entity_id id = 0;
        class world* w = nullptr;
    };

    class component_storage
    {
    public:
        template<typename T>
        bool has(entity_id e) const;

        template<typename First, typename Second, typename... Rest>
        bool has(entity_id e) const;

        template<typename T>
        T& get(entity_id e);

        template<typename T>
        void set(entity_id e);
        
        template<typename T>
        void set(entity_id e, const T& comp);
 
        template<typename First, typename Second, typename... Rest>
        void set(entity_id e);

        template<typename First, typename Second, typename... Rest>
        void set(entity_id e, const First& comp0, const Second& comp1, 
                const Rest&... rest_comps);
        
        template<typename T>
        void remove(entity_id e);
    
        template<typename First, typename Second, typename... Rest>
        void remove(entity_id e);
    private:
        using comp_index = std::type_index;
        using comp_to_entities_set = std::unordered_map<comp_index, entities_set>;
        using entity_to_index = std::unordered_map<entity_id, size_t>;

        using comp_to_entity_dict = std::unordered_map<comp_index, 
              entity_to_index>;
        
        using comp_to_storage = std::unordered_map<comp_index, 
              std::vector<uint8_t>>;

        using comp_to_reusable_ids = std::unordered_map<comp_index, 
              std::queue<size_t>>;

        comp_to_entities_set entities_dict;
        comp_to_entity_dict indices_dict; 
        comp_to_storage storage_dict;
        comp_to_reusable_ids reusable_id_queues;
    };

    class world final
    {
    public:
        entity make_entity();
        void destroy_entity(entity_id e);
        bool is_alive(entity_id e) const; 

        template<typename... T>
        bool has(entity_id e) const;

        template<typename T>
        T& get(entity_id e);

        template<typename... T>
        void set(entity_id e);
        
        template<typename... T>
        void set(entity_id e, const T&... comps);

        template<typename... T>
        void remove(entity_id e);
    private:
        entities_set alive_entities;
        entity_id entity_counter = 0;
        component_storage storage;
    };

    inline entity::entity(class world* w, entity_id id): w(w), id(id)
    {

    }

    inline entity::operator entity_id() const 
    {
        return id;
    }

    inline bool entity::is_alive() const
    {
        return w && w->is_alive(id);
    }

    inline entity world::make_entity()
    {
        auto id = ++entity_counter;
        alive_entities.emplace(id);

        return entity(this, id);
    }

    inline void world::destroy_entity(entity_id e)
    {
        alive_entities.erase(e);
    }

    inline bool world::is_alive(entity_id e) const
    {
        return alive_entities.contains(e);
    }

    template<typename T>
    inline bool component_storage::has(entity_id e) const
    {
        if(entities_dict.contains(typeid(T)))
        {
            return entities_dict.at(typeid(T)).contains(e);
        }
        return false;
    }

    template<typename T>
    inline T& component_storage::get(entity_id e)
    {
        if(!has<T>(e))
        {
            throw std::runtime_error(std::format("Entity #{} doesn't have {}",
                        e, typeid(T).name()));
        }

        auto index = indices_dict[typeid(T)].at(e);
        
        auto* ptr = reinterpret_cast<T*>(&storage_dict[typeid(T)][0]);
        ptr += index;

        return *ptr;
    }

    template<typename... T>
    inline bool entity::has() const
    {
        return w->has<T...>(id);    
    }

    template<typename T>
    inline T& world::get(entity_id e)
    {
        return storage.get<T>(e);
    }

    template<typename T>
    inline T& entity::get()
    {
        return w->get<T>(id);
    }

    template<typename... T>
    inline void entity::set()
    {
        (set(T{}), ...);
    }

    template<typename T>
    inline void component_storage::set(entity_id e, const T& comp)
    {
        entities_dict[typeid(T)].emplace(e);

        auto& storage = storage_dict[typeid(T)];

        auto& reusable_ids = reusable_id_queues[typeid(T)];

        if(reusable_ids.empty())
        {
            size_t T_size = sizeof(T);
            size_t old_size = storage.size();

            storage.resize(old_size + T_size);
            void* ptr = &storage[0] + old_size;
            new(ptr) T(comp);

            indices_dict[typeid(T)][e] = old_size / T_size;
            return;
        }

        auto index = reusable_ids.front();
        reusable_ids.pop();

        auto ptr = reinterpret_cast<T*>(&storage[0]);
        new(ptr + index) T(comp);

        indices_dict[typeid(T)][e] = index;
    }
    
    template<typename... T>
    inline void world::set(entity_id e)
    {
        storage.set<T...>(e);
    }

    template<typename... T>
    inline void world::set(entity_id e, const T&... comps)
    {
        storage.set(e, comps...);
    }

    template<typename... T>
    inline void entity::set(const T&... comps)
    {
        w->set(id, comps...);
    }

    template<typename T>
    inline void component_storage::remove(entity_id e)
    {
        if(!has<T>(e))
        {
            return;
        }

        entities_dict[typeid(T)].erase(e);
        reusable_id_queues[typeid(T)].push(indices_dict[typeid(T)][e]);
        indices_dict[typeid(T)].erase(e);
    }

    template<typename... T>
    inline void world::remove(entity_id e)
    {
        storage.remove<T...>(e);
    }

    template<typename... T>
    inline void entity::remove()
    {
        w->remove<T...>(id);
    }

    template<typename... T>
    inline bool world::has(entity_id e) const
    {
        return storage.has<T...>(e);
    }

    template<typename First, typename Second, typename... Rest>
    inline bool component_storage::has(entity_id e) const
    {
        return has<First>(e) && has<Second>(e) && (has<Rest>(e) && ...);
    }
    
    template<typename First, typename Second, typename... Rest>
    inline void component_storage::set(entity_id e)
    {
        set(e, First{});
        set(e, Second{});
        (set(e, Rest{}), ...);
    }

    template<typename First, typename Second, typename... Rest>
    inline void component_storage::set(entity_id e, const First& comp0, const Second& comp1, 
            const Rest&... rest_comps)
    {
        set(e, comp0);
        set(e, comp1);
        (set(e, rest_comps), ...);
    }

    template<typename First, typename Second, typename... Rest>
    inline void component_storage::remove(entity_id e)
    {
        remove<First>(e);
        remove<Second>(e);
        (remove<Rest>(e), ...);
    }
};