#include "core/plugin_system/PluginHost.h"

#include <dlfcn.h>

#include "core/plugin_system/IProtocolFramerPlugin.h"
#include "core/plugin_system/IProtocolMapperPlugin.h"
#include "core/plugin_system/IProtocolPlugin.h"

namespace softbus::core::plugin_system
{

namespace
{

using GetAbiVersionFn = int (*)();
using CreateProtocolPluginFn = IProtocolPlugin* (*)();
using CreateFramerPluginFn = IProtocolFramerPlugin* (*)();
using CreateMapperPluginFn = IProtocolMapperPlugin* (*)();

} // namespace

PluginHost::PluginHost(std::shared_ptr<PluginManager> manager)
    : m_manager(std::move(manager))
{
}

PluginHost::~PluginHost()
{
    unloadAll();
}

bool PluginHost::loadPlugin(const QString& path, QString* err)
{
    if (!m_manager) {
        if (err) *err = QStringLiteral("plugin_manager_null");
        return false;
    }
    if (m_loaded.contains(path)) {
        return true;
    }
    void* handle = dlopen(path.toUtf8().constData(), RTLD_NOW | RTLD_LOCAL);
    if (!handle) {
        if (err) *err = QString::fromUtf8(dlerror());
        return false;
    }
    auto* getAbi = reinterpret_cast<GetAbiVersionFn>(dlsym(handle, "softbus_plugin_abi_version"));
    const int abi = getAbi ? getAbi() : 1;
    if (abi != 1) {
        if (err) *err = QStringLiteral("abi_mismatch");
        dlclose(handle);
        return false;
    }

    auto* createParser = reinterpret_cast<CreateProtocolPluginFn>(dlsym(handle, "softbus_create_protocol_plugin"));
    QString protocolPluginId;
    if (createParser) {
        std::shared_ptr<IProtocolPlugin> p(createParser());
        if (p) {
            protocolPluginId = p->pluginId();
            m_manager->registerProtocolPlugin(p);
        }
    }
    auto* createFramer = reinterpret_cast<CreateFramerPluginFn>(dlsym(handle, "softbus_create_framer_plugin"));
    QString framerPluginId;
    if (createFramer) {
        std::shared_ptr<IProtocolFramerPlugin> p(createFramer());
        if (p) {
            framerPluginId = p->pluginId();
            m_manager->registerProtocolFramerPlugin(p);
        }
    }
    auto* createMapper = reinterpret_cast<CreateMapperPluginFn>(dlsym(handle, "softbus_create_mapper_plugin"));
    QString mapperPluginId;
    if (createMapper) {
        std::shared_ptr<IProtocolMapperPlugin> p(createMapper());
        if (p) {
            mapperPluginId = p->pluginId();
            m_manager->registerProtocolMapperPlugin(p);
        }
    }
    LoadedPlugin lp;
    lp.path = path;
    lp.protocolPluginId = protocolPluginId;
    lp.framerPluginId = framerPluginId;
    lp.mapperPluginId = mapperPluginId;
    lp.abiVersion = abi;
    lp.handle = handle;
    m_loaded.insert(path, lp);
    return true;
}

bool PluginHost::reloadPlugin(const QString& path, QString* err)
{
    QString localErr;
    if (!unloadPlugin(path, &localErr)) {
        // allow reload when not loaded
        localErr.clear();
    }
    const bool ok = loadPlugin(path, &localErr);
    if (!ok && err) {
        *err = localErr;
    }
    return ok;
}

bool PluginHost::unloadPlugin(const QString& path, QString* err)
{
    auto it = m_loaded.find(path);
    if (it == m_loaded.end()) {
        if (err) *err = QStringLiteral("not_loaded");
        return false;
    }
    if (m_manager) {
        if (!it.value().protocolPluginId.isEmpty()) {
            m_manager->unregisterProtocolPlugin(it.value().protocolPluginId);
        }
        if (!it.value().framerPluginId.isEmpty()) {
            m_manager->unregisterProtocolFramerPlugin(it.value().framerPluginId);
        }
        if (!it.value().mapperPluginId.isEmpty()) {
            m_manager->unregisterProtocolMapperPlugin(it.value().mapperPluginId);
        }
    }
    if (it.value().handle) {
        dlclose(it.value().handle);
    }
    m_loaded.erase(it);
    return true;
}

void PluginHost::unloadAll()
{
    const auto keys = m_loaded.keys();
    for (const auto& k : keys) {
        QString ignore;
        (void)unloadPlugin(k, &ignore);
    }
}

} // namespace softbus::core::plugin_system
