#pragma once
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <format>
#include <functional>
#include <set>
#include <stdexcept>
#include <unordered_map>
#include <vector>

namespace zecsy
{
    using entity_id = uint64_t;

    /*
     * Using std::set for entities_set to maintain sorted order, which can
     * improve cache locality during queries and iterations.
     */
    using entities_set = std::set<entity_id>;

    class component_storage final
    {
    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_id = size_t;

        struct component_pool
        {
            std::vector<uint8_t> data;
            std::vector<size_t> free_list; // Reusable indices
            std::unordered_map<entity_id, size_t> entity_to_index;
            std::unordered_map<size_t, entity_id> index_to_entity;
        };

        std::unordered_map<comp_id, component_pool> pools;

        template<typename T>
        static comp_id get_component_id()
        {
            static comp_id id = next_component_id++;
            return id;
        }

        static comp_id next_component_id;
    };

    inline component_storage::comp_id component_storage::next_component_id = 0;

    template<typename T>
    inline bool component_storage::has(entity_id e) const
    {
        auto id = get_component_id<T>();
        if(pools.contains(id))
        {
            return pools.at(id).entity_to_index.contains(e);
        }
        return false;
    }

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

        auto& pool = pools.at(id);
        auto index = pool.entity_to_index.at(e);
        return *reinterpret_cast<T*>(&pool.data[index * sizeof(T)]);
    }

    template<typename T>
    inline void component_storage::set(entity_id e, const T& comp)
    {
        auto id = get_component_id<T>();
        auto& pool = pools[id];

        size_t index;
        if(!pool.free_list.empty())
        {
            index = pool.free_list.back();
            pool.free_list.pop_back();
        }
        else
        {
            index = pool.data.size() / sizeof(T);
            pool.data.resize(pool.data.size() + sizeof(T));
        }

        new(&pool.data[index * sizeof(T)]) T(comp);
        pool.entity_to_index[e] = index;
        pool.index_to_entity[index] = e;
    }

    template<typename T>
    inline void component_storage::set(entity_id e)
    {
        set(e, T{});
    }

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

        auto& pool = pools[id];
        auto index = pool.entity_to_index[e];
        pool.free_list.push_back(index);
        pool.entity_to_index.erase(e);
        pool.index_to_entity.erase(index);
    }

    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), ...);
    }

    class system_scheduler final
    {
    public:
        void add_system(float freq, auto&& func);

        void add_system(int freq, auto&& func);

        void update(float dt);

    private:
        struct system_handler
        {
            double interval;
            double accumulator = 0.0f;
            std::function<void(float)> callback;
        };

        std::vector<system_handler> systems;
    };

    inline void system_scheduler::add_system(float freq, auto&& func)
    {
        systems.emplace_back(1.0f / freq, 0.0f,
                             std::forward<decltype(func)>(func));
    }

    inline void system_scheduler::add_system(int freq, auto&& func)
    {
        add_system(float(freq), func);
    }

    inline void system_scheduler::update(float dt)
    {
        dt = std::max(0.0f, dt);

        for(auto& s: systems)
        {
            s.accumulator += dt;
            while(s.accumulator >= s.interval)
            {
                s.callback(dt);
                s.accumulator -= s.interval;
            }
        }
    }

    class world final
    {
    public:
        entity_id 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);

        template<typename... T>
        void query(auto&& system);

    private:
        entities_set alive_entities;
        entity_id entity_counter = 0;
        component_storage storage;
    };

    inline entity_id world::make_entity()
    {
        auto id = ++entity_counter;
        alive_entities.emplace(id);
        return 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 world::has(entity_id e) const
    {
        return storage.has<T...>(e);
    }

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

    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 world::remove(entity_id e)
    {
        storage.remove<T...>(e);
    }

    template<typename... T>
    inline void world::query(auto&& system)
    {
        for(auto e: alive_entities)
        {
            if((has<T...>(e)))
            {
                system(get<T>(e)...);
            }
        }
    }
}; // namespace zecsy