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

//////////////////////////////////////
//                                  //
//   Definition de l'Item Property. //
//                                  //
//////////////////////////////////////

#ifndef ITEMPROPERTY_HPP
#define ITEMPROPERTY_HPP

#include <algorithm>
#include <array>
#include <bitset>
#include <stdexcept>
#include <iostream>
#include <memory>
#include <vector>

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

class Property;
class Item;

using PropertyID = std::size_t;

inline PropertyID getPropertyTypeID()
{
    static PropertyID lastID = 0;
    return lastID++;
}

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

constexpr std::size_t maxProperties = 32;

using PropertyBitSet = bitset<maxProperties>;
using PropertyArray = array<Property*, maxProperties>;

class Property {
  public:
    Item *item;

    virtual void init() {}

    virtual ~Property() {}
};

class Item {
  public:
    template <typename T, typename... TArgs>
    T& addProperty(TArgs&&... args)
    {
        if(hasProperty<T>())
        {
            throw runtime_error("Vous ne pouvez pas mettre deux fois la même propriétée dans le même item.\n");
        }

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

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

        propertyArray[getPropertyTypeID<T>()] = p;
        propertyBitSet[getPropertyTypeID<T>()] = true;

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

        return *p;
    }

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

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

    template <typename T>
    T& getProperty() const {
        // Vérification rapide via le BitSet et l'accès direct au composant via l'array
        PropertyID id = getPropertyTypeID<T>();
        if (propertyBitSet[id]) {
            return *static_cast<T*>(propertyArray[id]);
        }

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

  private:
    bool active {true};

    vector<unique_ptr<Property>> properties;

    PropertyArray propertyArray;
    PropertyBitSet propertyBitSet;
};

#endif
