/********************\
|  Copyright 2024,   |
|       Ulysse Cura  |
\********************/

////////////////////////////
//                        //
//   Definition de l'ECS. //
//                        //
////////////////////////////

#ifndef ECS_HPP
#define ECS_HPP

#include <algorithm>
#include <array>
#include <bitset>
#include <stdexcept>
#include <iostream>
#include <memory>
#include <vector>
#include "../ChannelManager.hpp"

using std::bitset, std::array, std::vector, std::unique_ptr, std::make_unique, std::move, std::forward, std::runtime_error, std::sort;

class Component;
class Entity;

using ComponentID = std::size_t;

inline ComponentID getComponentTypeID()
{
    static ComponentID lastID = 0;
    return lastID++;
}

template<typename T> inline ComponentID getComponentTypeID() noexcept
{
    static ComponentID typeID = getComponentTypeID();
    return typeID;
}

constexpr std::size_t maxComponents = 32;

using ComponentBitSet = bitset<maxComponents>;
using ComponentArray = array<Component*, maxComponents>;

class Component {
  public:
    Entity *entity;

    virtual void init() {}
    virtual void update() {}
    virtual void draw() {}

    virtual ~Component() {}
};

class Entity {
  public:
    void update()
    {
        for(auto &c : m_components) c->update();
    }

    void draw()
    {
        for(auto &c : m_components) c->draw();
    }

    bool isActive() const { return m_active; }
    void destroy() { m_active = false; }

    template <typename T, typename... TArgs>
    T& addComponent(TArgs&&... args)
    {
        if(hasComponent<T>())
        {
            throw runtime_error("Vous ne pouvez pas mettre deux fois le même composant dans la même entité.\n");
        }

        // Creation d'un pointeur du type donné avec les arguments necessaires
        T *c(new T(forward<TArgs>(args)...));
        // On donne au composant une reference de l'entitée
        c->entity = this;

        // Création d'un unique_ptr et ajout du composant dans le vecteur et array associé
        unique_ptr<Component> uPtr {c};
        m_components.emplace_back(move(uPtr));

        m_componentArray[getComponentTypeID<T>()] = c;
        m_componentBitSet[getComponentTypeID<T>()] = true;

        // Initialisation du composant
        c->init();

        return *c;
    }

    template <typename T>
    bool hasComponent() const {
        // Vérifier d'abord si un type exact est présent
        ComponentID id = getComponentTypeID<T>();
        if (m_componentBitSet[id]) {
            return true;
        }

        // Si non, vérifier dynamiquement tous les composants pour un type dérivé
        for (const auto& c : m_components) {
            if (dynamic_cast<T*>(c.get())) {
                return true;
            }
        }
        return false;
    }

    template <typename T>
    T& getComponent() const {
        // Vérification rapide via le BitSet et l'accès direct au composant via l'array
        ComponentID id = getComponentTypeID<T>();
        if (m_componentBitSet[id]) {
            return *static_cast<T*>(m_componentArray[id]);
        }

        // Vérification polymorphique avec dynamic_cast
        for (const auto& c : m_components) {
            if (T* t = dynamic_cast<T*>(c.get())) {
                return *t;
            }
        }
        throw runtime_error("Composant non trouvé.\n");
    }

    int draw_priority;

  private:
    bool m_active {true};

    vector<unique_ptr<Component>> m_components;

    ComponentArray m_componentArray;
    ComponentBitSet m_componentBitSet;
};

class Manager {
  public:
    void update()
    {
        for(auto &e : m_entities) e->update();
    }

    void draw()
    {
        vector<int> drawOrder(m_entities.size());

        for(int i {0}; i < static_cast<int>(m_entities.size()); i++) drawOrder[i] = i;

        sort(drawOrder.begin(), drawOrder.end(), [this](const int &a, const int &b) {
            return m_entities.at(a)->draw_priority < m_entities.at(b)->draw_priority;
        });

        for(int i {0}; i < static_cast<int>(m_entities.size()); i++)
        {
            m_entities[drawOrder[i]]->draw();
        }
    }

    void refresh()
    {
        m_entities.erase(std::remove_if(m_entities.begin(), m_entities.end(),
        [](const unique_ptr<Entity> &mEntity) {
            return !mEntity->isActive();
        }),
          m_entities.end());
    }

    Entity &addEntity()
    {
        Entity *e = new Entity();
        unique_ptr<Entity> uPtr{e};
        m_entities.emplace_back(move(uPtr));
        return *e;
    }

    std::size_t getNumberOfEntity() const
    {
        return m_entities.size();
    }

    void erase(std::size_t index)
    {
        m_entities.at(index).get()->destroy();
    }

    vector<unique_ptr<Entity>> &getEntities() { return m_entities; }
    ChannelManager &getChannelManager() { return m_channelManager; }

  private:
    vector<unique_ptr<Entity>> m_entities;
    ChannelManager m_channelManager;
};

#endif
