#ifndef DOBJECT_H
#define DOBJECT_H

#include <variant>
#include <memory>
#include <vector>
#include <string>
#include <mutex>
#include <map>
#include <functional>

/**
 * @file DObject.h
 * @brief Defines the DObject class, a universal data container for the project.
 *
 * The DObject class encapsulates arbitrary data and its associated metadata,
 * providing a consistent interface for public-facing functions. It includes
 * support for dynamically registered plugins.
 */

/**
 * @class DObject
 * @brief A universal data container class.
 *
 * The DObject class is designed to store arbitrary data alongside descriptive metadata.
 * It supports plugin registration to allow extensible functionality.
 *
 * The general purpose of this is to simplify memory managment, function interfaces,
 * and interoperability with other languages by wrapping all of that up inside of
 * one location (DObject). There are still a limited number of types that DOBject can
 * represent these include
 * - bool
 * - short int
 * - int
 * - long int
 * - float
 * - double
 * - long double
 * - std::string
 * - std::monostate
 * - std::vector<int>
 * - std::vector<float>
 * - std::vector<double>
 * - std::vector<std::string>
 * - std::vector<std::vector<int>>
 * - std::vector<std::vector<float>>
 * - std::vector<std::vector<double>>
 * - std::vector<std::vector<std::vector<int>>>
 * - std::vector<std::vector<std::vector<float>>>
 * - std::vector<std::vector<std::vector<double>>
 *
 *
 * If more types are needed, they can be added to the DataType variant.
 *
 * In general the usage of this might look like
 * @code
 * DObject obj;
 * std::vector<int> data = {1, 2, 3, 4, 5};
 * obj.setData(data);
 * std::cout << "Data is " << obj << std::end;
 * someFunction(&obj);
 * @endcode
 *
 * All memory managment should be taken care of
 * as all datatypes accepted by the varient are either primatives or
 * include their own built in memory managment (i.e. this means that
 * wherever possible vectors should be used in place of raw arrays).
 */
class DObject {
public:
    /**
     * @brief Supported data types for the DObject.
     *
     * This type alias uses `std::variant` to store different types of data,
     * ensuring type safety and flexibility.
     */
    using DataType = std::variant<
        bool, short int, int, long int, float, double, 
        long double, std::string, std::monostate, std::vector<int>,
        std::vector<float>, std::vector<double>, std::vector<std::string>,
        std::vector<std::vector<int>>, std::vector<std::vector<float>>,
        std::vector<std::vector<double>>,
        std::vector<std::vector<std::vector<int>>>,
        std::vector<std::vector<std::vector<float>>>,
        std::vector<std::vector<std::vector<double>>>
    >;

    // Mapping of data types to their string representations
    std::map<int, std::string> dataTypeMap = {
        {0, "bool"}, {1, "short int"}, {2, "int"}, {3, "long int"}, {4, "float"},
        {5, "double"}, {6, "long double"}, {7, "string"}, {8, "std::monostate"},
        {9, "vector<int>"}, {10, "vector<float>"}, {11, "vector<double>"},
        {12, "vector<string>"}, {13, "vector<vector<int>"},
        {14, "vector<vector<float>"}, {15, "vector<vector<double>"},
        {16, "vector<vector<vector<int>>"}, {17, "vector<vector<vector<float>>"},
        {18, "vector<vector<vector<double>>"}
    };


    /**
     * @brief Placeholder type for plugins.
     *
     * In the future, this will be replaced with a concrete interface.
     */
    using Plugin = std::function<void(DObject&)>;

    /**
     * @brief Default constructor.
     *
     * Creates an empty DObject.
     */
    DObject();

    /**
     * @brief Constructor to initialize a DObject with data.
     *
     * @param data The data to be stored in the DObject.
     */
    DObject(const DataType& data);

    /**
     * @brief Retrieves the data stored in the DObject.
     *
     * Use the appropriate type (matching the stored data) with `std::get<T>()`.
     *
     * @return A constant reference to the stored data.
     */
    const DataType& getData() const noexcept;

    /**
     * @brief Retrieves the data stored in the DObject as a typed object so that std::get<T>() is not needed.
     * 
     * @return Data as type T
     */
    template <typename T>
    T getDataAs() const {
        if (std::holds_alternative<T>(data_)) {
            return std::get<T>(data_);
        } else {
            throw std::bad_variant_access();
        }
    }

    /**
     * @brief Sets the data for the DObject.
     *
     * Updates the stored data and optionally updates metadata.
     *
     * @param data The new data to store in the DObject.
     */
    void setData(const DataType& data);

    /**
     * @brief Enables or disables debugging and tracing for the DObject.
     *
     * When debugging is enabled, the DObject tracks creation and modification history.
     *
     * @param enableDebug True to enable debugging, false to disable it.
     */
    void setDebugging(bool enableDebug);

    /**
     * @brief Checks if debugging is enabled for the DObject.
     *
     * @return True if debugging is enabled, false otherwise.
     */
    bool isDebuggingEnabled() const noexcept;

    /**
     * @breif Set error code tracked by the DOBject
     *
     * @return The previous error code
     */
    int setErrorCode(int code) noexcept;

    /**
     * @brief Get the error code tracked by the DObject
     *
     * @return The error code
     */
    int getErrorCode() const noexcept;


    /**
     * @brief Registers a plugin with the DObject.
     *
     * Plugins are stored in a registry and can add custom functionality to the DObject.
     *
     * @param id A unique identifier for the plugin.
     * @param plugin The plugin function to register.
     */
    void registerPlugin(const std::string& id, Plugin plugin);

    /**
     * @brief Unregisters a plugin by its identifier.
     *
     * Removes the plugin from the registry if it exists.
     *
     * @param id The unique identifier of the plugin to unregister.
     */
    void unregisterPlugin(const std::string& id);

    /**
     * @brief Executes a plugin by its identifier.
     *
     * Invokes the registered plugin function. If the plugin is not found, no action is taken.
     *
     * @param id The unique identifier of the plugin to execute.
     */
    void runPlugin(const std::string& id);

    /**
     * @brief Executes all registered plugins in the registry.
     *
     * Iterates through all plugins and invokes them on the current DObject.
     */
    void runAllPlugins();

    /**
     * @brief Provides a human-readable summary of the DObject.
     *
     * Useful for quick inspection or logging during debugging sessions.
     *
     * @param os The output stream to write to.
     * @param obj The DObject to summarize.
     * @return A reference to the output stream.
     */
    friend std::ostream& operator<<(std::ostream& os, const DObject& obj);

private:
    DataType data_;                              ///< The main data stored in the DObject.
    bool debugEnabled_ = false;                  ///< Indicates whether debugging is enabled.
    int errorCode_ = 0;                          ///< Error code tracked by the DObject.
    std::map<std::string, Plugin> plugins_;      ///< Registry for dynamically registered plugins.
};

#endif // DOBJECT_H
