#include "fmp_manager.h"
#include "fmp_me_handlers.h"
#include <ctkPluginFramework.h>
#include <ctkPluginContext.h>
#include <ctkPluginFrameworkLauncher.h>
#include <ctkPluginException.h>
#include <QtPlugin>
#include <QDir>
#include <QDirIterator>
#include <QCoreApplication>
#include <fmp_logger_i.h>
#include <fmp_settings_i.h>
#include <fmp_syncer_i.h>

FMPluginManager::FMPluginManager() :
    _ctx(nullptr),
    _fw_factory(nullptr),
    _logger(nullptr),
    _setting(nullptr)
{
}

FMPluginManager::~FMPluginManager()
{
    StopService();
}

int FMPluginManager::StartService()
{
    if (_fw_factory) return FMP_SUCCESS;

    _fw_factory = new ctkPluginFrameworkFactory(_fw_props);

    _fw = _fw_factory->getFramework();
    try {
        _fw->init();

        _ctx = _fw->getPluginContext();
        _ctx->connectFrameworkListener(this, SLOT(OnFrameworkEvent(ctkPluginFrameworkEvent)));
        _ctx->connectPluginListener(this, SLOT(OnPluginEvent(ctkPluginEvent)));
        _ctx->connectServiceListener(this, "OnServiceEvent");

        _fw->start();

        //! 设置模块必须第一个加载
        LoadPlugin("fmp.settings");
        LoadPlugin("fmp.logger");
        LoadPlugin("fmp.syncer");
        LoadPlugin("org.commontk.eventadmin");

        if (!(_logger = GetService<FMPLoggerInterface>(_ctx))) {
            FMP_WARN(_logger) << "Logger service is not ready.";
        }
        else {
            FMP_INFO(_logger) << "Logger service is ready.";
        }

        if (!(_setting = GetService<FMPSettingsInterface>(_ctx))) {
            FMP_WARN(_logger) << "Setting service is not ready.";
        }
        else {
            FMP_INFO(_logger) << "Setting service is ready.";
        }

        if (!(_syncer = GetService<FMPSyncerInterface>(_ctx))) {
            FMP_WARN(_logger) << "Synchronizer service is not ready.";
        }
        else {
            FMP_INFO(_logger) << "Synchronizer service is ready.";
        }

        LoadNewPlugins();

        connect(&_watcher, SIGNAL(directoryChanged(QString)), SLOT(OnPluginsChanged(QString)));
        _watcher.addPath(_fw_props[FMP_PROPKEY_PLUGINPATH].toString());

        FMPUpgradeEventHandler *upd_handler = new FMPUpgradeEventHandler(_ctx, this);

        //! 启动配置的入口服务
        QString entry = _fw_props[FMP_PROPKEY_ENTRY].toString();
        ctkServiceReference ref = _ctx->getServiceReference(entry);
        if (ref) {
            FMPBaseInterface *svc = qobject_cast<FMPBaseInterface*>(_ctx->getService(ref));
            if (svc) {
                svc->StartService();
            }
        }

    }
    catch(const ctkPluginException& e) {
        FMP_ERROR(_logger) << e.cause()->what();
        exit(FMP_FAILURE);
    }

    return FMP_SUCCESS;
}

int FMPluginManager::StopService()
{
    _watcher.removePath(_fw_props[FMP_PROPKEY_PLUGINPATH].toString());
    _fw->stop();
    if (_fw_factory) {
        delete _fw_factory;
        _fw_factory = nullptr;
        _fw.clear();
    }

    return FMP_SUCCESS;
}

int FMPluginManager::SetProperties(const FMPProps &props)
{
    _fw_props = props;
    if (!_fw_props.contains(FMP_PROPKEY_PLUGINPATH) || !_fw_props.contains(FMP_PROPKEY_CFG)
            || !_fw_props.contains(FMP_PROPKEY_ENTRY)
            || _fw_props[FMP_PROPKEY_PLUGINPATH].isNull() || _fw_props[FMP_PROPKEY_CFG].isNull()
            || _fw_props[FMP_PROPKEY_CFG].isNull()) {
        FMP_ERROR(_logger) << "Missing properties(PluginPath|Configuration|Entry).";
        return FMP_FAILURE;
    }

    return FMP_SUCCESS;
}

void FMPluginManager::LoadNewPlugins()
{
    QStringList new_plugins = GetNewPlugins();
    foreach(QString plg, new_plugins) {
        LoadPlugin(plg);
    }
}

int FMPluginManager::LoadPlugin(const QString &plugin)
{
    try {
        if(!plugin.isEmpty()) {
          QString pluginPath = GetPluginPath(plugin);
          if (pluginPath.isEmpty()) throw ctkPluginException("Empty path for plugin" + plugin);

          ctkPluginContext* pc = _fw->getPluginContext();
          QSharedPointer<ctkPlugin> p = pc->installPlugin(QUrl::fromLocalFile(pluginPath));
          if (!p.isNull()) {
              p->start(ctkPlugin::START_TRANSIENT);
          }
        }
        else {
            FMP_WARN(_logger) << "Empty plugin name." << plugin;
            return FMP_FAILURE;
        }
    }
    catch(const ctkPluginException &e) {
        FMP_WARN(_logger) << (e.cause() ? e.cause()->what() : e.what());
        return FMP_FAILURE;
    }

    return FMP_SUCCESS;
}

void FMPluginManager::UpgradeOldPlugins(const QVariantList &pids)
{
    foreach (QVariant pid, pids) {
        UpgradePlugin(pid.toInt());
    }
}

int FMPluginManager::UpgradePlugin(long pid)
{
    QSharedPointer<ctkPlugin> p = _ctx->getPlugin(pid);
    if (p) {
        QString plugin_name = p->getSymbolicName();
        p->stop();
        return LoadPlugin(plugin_name);
    }

    return FMP_FAILURE;
}

int FMPluginManager::PostEvent(const QString &topic, const FMPProps &pps)
{
    ctkEventAdmin *event_admin = GetService<ctkEventAdmin>(_ctx);
    if (event_admin) {
        ctkEvent ctk_event(topic, pps);
        event_admin->postEvent(ctk_event);
        FMP_INFO(_logger) << "Posted event" << topic << "Properties:" << pps;
        return FMP_SUCCESS;
    }
    else {
        FMP_WARN(_logger) << topic << "not send: Event admin not available.";
    }

    return FMP_FAILURE;
}

void FMPluginManager::OnPluginsChanged(const QString &path)
{
    FMP_INFO(_logger) << "Directory change detected:" << path;
    LoadNewPlugins();

    QVariantList expired_plugins = GetExpiredPlugins();
    if (!expired_plugins.isEmpty()) {
        FMPProps props;
        props[FMP_PROPKEY_PID_LIST] = expired_plugins;
        PostEvent(QString(FMP_TOPICS_SERVICES) + FMPE_SERVICE_REQ_UPDATE, props);
    }
}

QString FMPluginManager::GetPluginPath(const QString &plugin_name, const QString &ver)
{
    QString plugin_file_name(plugin_name);
    plugin_file_name.replace(".", "_");
    QStringList filters;
#ifdef Q_OS_WIN
    filters << plugin_file_name + "*" + ".dll";
#else
#   ifdef Q_OS_LINUX
    filters << plugin_file_name + "*" + ".so";
#   else
    filters << plugin_file_name + "*" + ".dylib";
#   endif
#endif
    QDirIterator dir_iter(qApp->applicationDirPath() + "/" + _fw_props[FMP_PROPKEY_PLUGINPATH].toString(), filters, QDir::Files);
    QString plugin_version = ver;
    QFileInfo plugin_file_info;
    while(dir_iter.hasNext()) {
        dir_iter.next();
        QFileInfo file_info = dir_iter.fileInfo();
        QString file_basename = file_info.completeBaseName();
        if (file_basename.startsWith("lib")) file_basename = file_basename.mid(3);
        QString tmp_ver = file_basename.section(plugin_file_name, 1, 1).replace("_", "");
        if (plugin_version.isEmpty() || plugin_version < tmp_ver) {
            plugin_version = tmp_ver;
            plugin_file_info = file_info;
        }
    }

    return plugin_file_info.canonicalFilePath();
}

QStringList FMPluginManager::GetNewPlugins()
{
    QStringList new_plugins;

    QStringList filters;
#ifdef Q_OS_WIN
    filters << "*.dll";
#else
#   ifdef Q_OS_LINUX
    filters << "*.so";
#   else
    filters << "*.dylib";
#   endif
#endif
    QMap<QString, QSharedPointer<ctkPlugin> > loaded_plugins;
    //! 管理器自身已经加载过
    loaded_plugins[qobject_interface_iid<FMPManagerInterface*>()] = QSharedPointer<ctkPlugin>();

    QList<QSharedPointer<ctkPlugin> > plugins = _ctx->getPlugins();
    foreach(QSharedPointer<ctkPlugin> p, plugins) {
        if (p->getState() == ctkPlugin::ACTIVE) {
            loaded_plugins[p->getSymbolicName()] = p;
        }
    }

    QDirIterator dir_iter(qApp->applicationDirPath() + "/" + _fw_props[FMP_PROPKEY_PLUGINPATH].toString(), filters, QDir::Files);
    while(dir_iter.hasNext()) {
        dir_iter.next();
        QFileInfo file_info = dir_iter.fileInfo();
        QString file_basename = file_info.completeBaseName();
        if (file_basename.startsWith("lib")) file_basename = file_basename.mid(3);

        QString plugin_name;
        plugin_name = file_basename.section("_", 0, -2).replace("_", ".");
        if (!loaded_plugins.contains(plugin_name)) {
            new_plugins << plugin_name;
        }
    }

    return new_plugins;
}

QVariantList FMPluginManager::GetExpiredPlugins()
{
    QVariantList expired_plugins;

    QStringList filters;
#ifdef Q_OS_WIN
    filters << "*.dll";
#else
#   ifdef Q_OS_LINUX
    filters << "*.so";
#   else
    filters << "*.dylib";
#   endif
#endif

    QMap<QString, QSharedPointer<ctkPlugin> > loaded_plugins;
    //! 管理器自身已经加载过
    loaded_plugins[qobject_interface_iid<FMPManagerInterface*>()] = QSharedPointer<ctkPlugin>();

    QList<QSharedPointer<ctkPlugin> > plugins = _ctx->getPlugins();
    foreach(QSharedPointer<ctkPlugin> p, plugins) {
        loaded_plugins[p->getSymbolicName()] = p;
    }

    QDirIterator dir_iter(qApp->applicationDirPath() + "/" + _fw_props[FMP_PROPKEY_PLUGINPATH].toString(), filters, QDir::Files);
    while(dir_iter.hasNext()) {
        dir_iter.next();
        QFileInfo file_info = dir_iter.fileInfo();
        QString file_basename = file_info.completeBaseName();
        if (file_basename.startsWith("lib")) file_basename = file_basename.mid(3);

        QString plugin_name, plugin_ver;
        plugin_name = file_basename.section("_", 0, -2).replace("_", ".");
        plugin_ver  = file_basename.section("_", -1);
        QSharedPointer<ctkPlugin> p;
        if (loaded_plugins.contains(plugin_name) && (p = loaded_plugins[plugin_name])
                && plugin_ver > p->getVersion().toString()) {
            expired_plugins << p->getPluginId();
        }
    }

    return expired_plugins;
}

void FMPluginManager::OnFrameworkEvent(const ctkPluginFrameworkEvent &event)
{
    QSharedPointer<ctkPlugin> plugin = event.getPlugin();
    FMP_DEBUG(_logger) << plugin->getSymbolicName() << plugin->getVersion() << event.getType();
}

void FMPluginManager::OnPluginEvent(const ctkPluginEvent &event)
{
    QSharedPointer<ctkPlugin> plugin = event.getPlugin();
    FMP_DEBUG(_logger) << plugin->getSymbolicName() << plugin->getVersion() << event.getType();
}

void FMPluginManager::OnServiceEvent(const ctkServiceEvent &event)
{
    QSharedPointer<ctkPlugin> plugin = event.getServiceReference().getPlugin();
    FMP_DEBUG(_logger) << plugin->getSymbolicName() << plugin->getVersion() << event.getType();
}


#if(QT_VERSION < QT_VERSION_CHECK(5,0,0))
    Q_EXPORT_PLUGIN2(com_fmp_manager, FMPluginManager)
#endif
