#ifndef _SYSELEMENTS_HPP_
#define _SYSELEMENTS_HPP_

#include <string>
#include <vector>
#include <unordered_map>
#include <stdexcept>

using namespace std;

/**
 * @brief Represents a basic system element with a name.
 */
class SystemElement
{
public:
    string name; // Name of the system element.

    /**
     * @brief Constructor for SystemElement.
     * @param name Name of the element.
     */
    SystemElement(string name) : name(name) {};
};

/**
 * @brief A container for managing system elements of type T.
 *
 * @tparam T Type of elements, must inherit from SystemElement.
 */
template <typename T>
class SystemElementContainer : public SystemElement
{
public:
    using MapType = unordered_map<string, T *>;              // Map to store elements by name.
    using iterator = typename MapType::iterator;             // Iterator for the map.
    using const_iterator = typename MapType::const_iterator; // Const iterator for the map.

protected:
    unsigned int iter_count; // Counter for iterations (currently unused).
    MapType content;         // Container for storing elements.

    /**
     * @brief Adds elements from a map to the container.
     * @param el_content Map of elements to add.
     */
    void add(MapType el_content)
    {
        for (const auto &[key, value] : el_content)
        {
            add(value);
        }
    }

public:
    /**
     * @brief Constructor for an empty container.
     * @param name Name of the container.
     */
    SystemElementContainer(string name) : SystemElement(name) {};

    /**
     * @brief Constructor with initial elements.
     * @param name Name of the container.
     * @param elements Vector of elements to add.
     */
    SystemElementContainer(string name, vector<T *> elements) : SystemElement(name)
    {
        add(elements);
    }

    /**
     * @brief Adds a single element to the container with a given name.
     * @param name Name of the element to add.
     * @param element Pointer to the element to add.
     * @throws runtime_error If the element's name is empty or already exists.
     */
    virtual void add(string name, T *element)
    {
        if ("" == name)
        {
            throw runtime_error("System elements with empty names are forbidden");
        }
        if (exists(name))
        {
            throw runtime_error("System elements of same names are forbidden");
        }
        content.insert({name, element});
    }

    /**
     * @brief Adds a single element to the container.
     * @param element Pointer to the element to add.
     * @throws runtime_error If the element's name is empty or already exists.
     */
    virtual void add(T *element)
    {
        add (element->name, element);
    }

    /**
     * @brief Checks if an element exists in the container.
     * @param name Name of the element to check.
     * @return True if the element exists, false otherwise.
     */
    bool exists(string name)
    {
        try
        {
            get(name);
            return true;
        }
        catch (const exception &e)
        {
            return false;
        }
    }

    /**
     * @brief Adds elements from another container.
     * @param elements Pointer to another container.
     */
    void add(SystemElementContainer<T> *elements)
    {
        add(elements->content);
    }

    /**
     * @brief Adds multiple elements from a vector.
     * @param elements Vector of elements to add.
     */
    void add(vector<T *> elements)
    {
        for (auto &element : elements)
        {
            add(element);
        }
    }

    /**
     * @brief Retrieves an element by its name.
     * @param name Name of the element to retrieve.
     * @return Pointer to the element.
     * @throws runtime_error If the element is not found.
     */
    T *get(string name)
    {
        auto it = content.find(name);
        if (it != content.end())
        {
            return it->second;
        }
        else
        {
            throw runtime_error("System elements not found");
        }
    }

    /**
     * @brief Merges an element by retrieving it or creating a new one if it doesn't exist.
     * @param name Name of the element.
     * @return Pointer to the merged or newly created element.
     */
    T *merge(string name)
    {
        if (exists(name))
        {
            return get(name);
        }
        else
        {
            T *ret = new T(name);
            add(ret);
            return ret;
        }
    }

    /**
     * @brief Returns an iterator to the beginning of the container.
     * @return Iterator to the beginning.
     */
    iterator begin() { return std::begin(content); }

    /**
     * @brief Returns an iterator to the end of the container.
     * @return Iterator to the end.
     */
    iterator end() { return std::end(content); }

    /**
     * @brief Returns a constant iterator to the beginning of the container.
     * @return Constant iterator to the beginning.
     */
    const_iterator begin() const { return std::begin(content); }

    /**
     * @brief Returns a constant iterator to the end of the container.
     * @return Constant iterator to the end.
     */
    const_iterator end() const { return std::end(content); }
};

#endif