#include "DObject.h"
#include <iostream>
#include <stdexcept>

/**
 * @file DObject.cpp
 * @brief Implementation of the DObject class.
 *
 * Provides the implementation for a universal data container with plugin support.
 */

// Default constructor
DObject::DObject() : data_(std::monostate()), debugEnabled_(false) {
    // The data is initialized to an empty state using std::monostate (a variant placeholder).
}

/**
 * @brief Constructor to initialize a DObject with data.
 */
DObject::DObject(const DataType& data)
    : data_(data), debugEnabled_(false) {}

/**
 * @brief Retrieves the data stored in the DObject.
 */
const DObject::DataType& DObject::getData() const noexcept {
    return data_;
}

/**
 * @brief Sets the data for the DObject.
 */
void DObject::setData(const DataType& data) {
    data_ = data;
}

/**
 * @brief Enables or disables debugging and tracing for the DObject.
 */
void DObject::setDebugging(bool enableDebug) {
    debugEnabled_ = enableDebug;
}

/**
 * @brief Checks if debugging is enabled for the DObject.
 */
bool DObject::isDebuggingEnabled() const noexcept {
    return debugEnabled_;
}

/**
* @breif Sets an error code and returns the old one
*/
int DObject::setErrorCode(int code) noexcept {
    int oldCode = errorCode_;
    errorCode_ = code;
    return oldCode;
}

int DObject::getErrorCode() const noexcept {
    return errorCode_;
}

/**
 * @brief Registers a plugin with the DObject.
 */
void DObject::registerPlugin(const std::string& id, Plugin plugin) {
    if (plugins_.find(id) != plugins_.end()) {
        throw std::runtime_error("Plugin with ID '" + id + "' already exists.");
    }
    plugins_[id] = std::move(plugin);
}

/**
 * @brief Unregisters a plugin by its identifier.
 */
void DObject::unregisterPlugin(const std::string& id) {
    if (plugins_.erase(id) == 0) {
        throw std::runtime_error("Plugin with ID '" + id + "' not found.");
    }
}

/**
 * @brief Executes a plugin by its identifier.
 */
void DObject::runPlugin(const std::string& id) {
    auto it = plugins_.find(id);
    if (it == plugins_.end()) {
        throw std::runtime_error("Plugin with ID '" + id + "' not found.");
    }
    it->second(*this); // Invoke the plugin, passing this DObject as a reference.
}

/**
 * @brief Executes all registered plugins in the registry.
 */
void DObject::runAllPlugins() {
    for (auto& [id, plugin] : plugins_) {
        plugin(*this); // Invoke each plugin, passing this DObject as a reference.
    }
}

/**
 * @brief Provides a human-readable summary of the DObject.
 */
std::ostream& operator<<(std::ostream& os, const DObject& obj) {
    os << "DObject Summary:\n";
    os << "  Debugging Enabled: " << (obj.debugEnabled_ ? "Yes" : "No") << "\n";
    os << "  Data: ";
    std::visit([&os](const auto& value) {
        using T = std::decay_t<decltype(value)>;
        if constexpr (std::is_same_v<T, std::monostate>) {
            os << "EMPTY";
        } else if constexpr (std::is_same_v<T, int>) {
            os << value;
        } else if constexpr (std::is_same_v<T, float>) {
            os << value;
        } else if constexpr (std::is_same_v<T, double>) {
            os << value;
        } else if constexpr (std::is_same_v<T, std::string>) {
            os << value;
        } else if constexpr (std::is_same_v<T, std::vector<int>>) {
            os << "[";
            for (const auto& elem : value) {
                os << elem << ", ";
            }
            os << "]";
        } else if constexpr (std::is_same_v<T, std::vector<float>>) {
            os << "[";
            for (const auto& elem : value) {
                os << elem << ", ";
            }
            os << "]";
        } else if constexpr (std::is_same_v<T, std::vector<double>>) {
            os << "[";
            for (const auto& elem : value) {
                os << elem << ", ";
            }
            os << "]";
        } else if constexpr (std::is_same_v<T, std::vector<std::vector<int>>>) {
            os << "\n[";
            for (const auto& row : value) {
                os << "[";
                for (const auto& elem : row) {
                    os << elem << ", ";
                }
                os << "], ";
            }
            os << "]";
        } else if constexpr (std::is_same_v<T, std::vector<std::vector<float>>>) {
            os << "\n[";
            for (const auto& row : value) {
                os << "[";
                for (const auto& elem : row) {
                    os << elem << ", ";
                }
                os << "], ";
            }
            os << "]";
        } else if constexpr (std::is_same_v<T, std::vector<std::vector<double>>>) {
            os << "\n[";
            for (const auto& row : value) {
                os << "[";
                for (const auto& elem : row) {
                    os << elem << ", ";
                }
                os << "], ";
            }
            os << "]";
        } else if constexpr (std::is_same_v<T, std::vector<std::vector<std::vector<int>>>>) {
            os << "\n[";
            for (const auto& plane : value) {
                os << "[";
                for (const auto& row : plane) {
                    os << "[";
                    for (const auto& elem : row) {
                        os << elem << ", ";
                    }
                    os << "], ";
                }
                os << "], ";
            }
            os << "]";
        } else if constexpr (std::is_same_v<T, std::vector<std::vector<std::vector<float>>>>) {
            os << "\n[";
            for (const auto& plane : value) {
                os << "[";
                for (const auto& row : plane) {
                    os << "[";
                    for (const auto& elem : row) {
                        os << elem << ", ";
                    }
                    os << "], ";
                }
                os << "], ";
            }
            os << "]";
        } else if constexpr (std::is_same_v<T, std::vector<std::vector<std::vector<double>>>>) {
            os << "\n[";
            for (const auto& plane : value) {
                os << "[";
                for (const auto& row : plane) {
                    os << "[";
                    for (const auto& elem : row) {
                        os << elem << ", ";
                    }
                    os << "], ";
                }
                os << "], ";
            }
            os << "]";
        } else {
            os << "UNKNOWN";
        }
    }, obj.data_);
    os << "\n  Data Type: " << obj.dataTypeMap.at(obj.data_.index());
    os << "\n  Plugins Registered: " << obj.plugins_.size() << "\n";
    return os;
}
