// %pacpus:license{
// This file is part of the PACPUS framework distributed under the
// CECILL-C License, Version 1.0.
// %pacpus:license}
/// @author  Gerald Dherbomez <firstname.surname@utc.fr>
/// @version    $Id: ComponentManager.cpp 76 2013-01-10 17:05:10Z kurdejma $

#include <Pacpus/kernel/ComponentFactoryBase.h>
#include <Pacpus/kernel/ComponentManager.h>
#include <Pacpus/kernel/ComponentBase.h>
#include <Pacpus/kernel/ConnectionBase.h>
#include <Pacpus/kernel/Log.h>
#include <QObject>
#include <QDomNodeList>

using namespace pacpus;

DECLARE_STATIC_LOGGER("pacpus.core.ComponentManager");

ComponentManager * ComponentManager::mInstance = NULL;

ComponentManager * ComponentManager::create()
{
    return getInstance();
}

ComponentManager * ComponentManager::getInstance()
{
    LOG_TRACE("getInstance()");
    LOG_TRACE("before: mInstance = " << mInstance);

    if (!mInstance) {
        LOG_INFO("creating new instance...");
        mInstance = new ComponentManager();
        LOG_DEBUG("mInstance = " << mInstance);
    }

    LOG_TRACE("after : mInstance = " << mInstance);
    return mInstance;
}

void ComponentManager::destroy()
{
    LOG_TRACE("destroy");

    LOG_TRACE("before: mInstance = " << mInstance);
    delete mInstance;
    mInstance = NULL;
    LOG_TRACE("after : mInstance = " << mInstance);
}

ComponentManager::ComponentManager()
{
    LOG_TRACE("constructor");

    xmlTree_ = XmlConfigFile::create();
    LOG_DEBUG("component manager was created");
}

ComponentManager::~ComponentManager()
{
    LOG_TRACE("destructor");

    for (ComponentMap::iterator it = componentMap_.begin(), itend = componentMap_.end(); it != itend; ++it) {
        bool unregisteredSuccessfully = unregisterComponent(it.key());
    }

    LOG_DEBUG("component manager was deleted");
}

bool ComponentManager::registerComponentFactory(ComponentFactoryBase* addr, const QString& type)
{
    LOG_TRACE("registerComponentFactory(type="<< type << ")");

    if (factoryMap_.contains(type))
    {
        LOG_WARN("cannot register a component factory of type '" << type << "'. It already belongs to the manager");
        return false;
    }

    factoryMap_[type] = addr;
    LOG_INFO("registered component factory '" << type << "'");

    return true;
}

bool ComponentManager::unregisterComponentFactory(const QString& type)
{
    LOG_TRACE("unregisterComponentFactory(type="<< type << ")");

    if (!factoryMap_.contains(type)) {
        LOG_WARN("cannot unregister component factory '" << type << "'. It was not registered");
        return false;
    }

    factoryMap_.remove(type);
    LOG_INFO("unregistered component factory '" << type << "'");

    return true;
}

bool ComponentManager::registerComponent(ComponentBase* addr, const QString& name)
{
    LOG_TRACE("registerComponent(name="<< name << ")");

    if (componentMap_.contains(name))
    {
        LOG_WARN("cannot register component '" << name << "'. A component with the same name exists already");
        return false;
    }

    componentMap_[name] = addr;
    LOG_INFO("registered component " << name);

    return true;
}

bool ComponentManager::unregisterComponent(const QString& name)
{
    LOG_TRACE("unregisterComponent(name="<< name << ")");

    if (!componentMap_.contains(name)) {
        LOG_WARN("cannot unregister component '" << name << "'. It was not registered");
        return false;
    }

    // FIXME: delete component
    ComponentBase* component = componentMap_.value(name, NULL);
    //delete component;

    // FIXME: remove from map (causes segfault in QMap::qMapLessThanKey on Windows)
    //componentMap_.remove(name);
    LOG_INFO("unregistered component '" << name << "'");

    return true;
}

bool ComponentManager::createComponent(const QString& type, const QString& name)
{
    LOG_TRACE("createComponent(type=" << type << ", " << "name="<< name << ")");

    if (factoryMap_.contains(type)) {
        ComponentFactoryBase* factory = factoryMap_.value(type);
        assert(factory);
        factory->addComponent(name);
        return true;
    }

    LOG_WARN("cannot create component '" << name << "'"
             << ". Component factory for type '" << type << "'"
             << " does not exist or was not registered"
             );
    return false;
}

bool ComponentManager::loadPlugin(const QString& filename)
{
    LOG_TRACE("loadPlugin(filename=" << filename << ")");

    pluginLoader_.setFileName(filename);

    if (!pluginLoader_.load()) {
        LOG_ERROR("cannot load plugin '" << filename << "'"
                  << ". Plugin loader returned error: " << pluginLoader_.errorString()
                  );
        return false;
    }

    QObject * plugin = pluginLoader_.instance();
    if (NULL == plugin) {
        LOG_WARN("cannot create an instance of the plugin '" << filename << "'"
                 << ". Plugin loader returned error: " << pluginLoader_.errorString()
                 );
        return false;
    }
    pluginList_.append(plugin);
    LOG_INFO("loaded plugin '" << qobject_cast<PacpusPluginInterface*>(plugin)->name() << "'"
             << " from file '" << pluginLoader_.fileName() << "'"
             );
    return true;
}

bool ComponentManager::createConnection(const QString& outputSignature, const QString& inputSignature, const QString& type, int priority = 0)
{
    QStringList output = outputSignature.split(".");
    QStringList input  = inputSignature.split(".");

    if(getComponent(output[0])==NULL) {
        LOG_WARN("cannot make connection : component " << output[0] << " not found");
        return false;}
    if(getComponent(output[0])->getOutput(output[1]) == NULL) {
        LOG_WARN("cannot make connection : component " << output[0] << " doesn't have output " << output[1]);
        return false;}
    if(getComponent(input[0])==NULL) {
        LOG_WARN("cannot make connection : component " << input[0] << " not found");
        return false;}
    if(getComponent(input[0])->getInput(input[1]) == NULL) {
        LOG_WARN("cannot make connection : component " << input[0] << " doesn't have intput " << input[1]);
        return false;}

    // NOTE Create communicationInterface if needed ??

    return connectInterface(getComponent(output[0])->getOutput(output[1]), getComponent(input[0])->getInput(input[1]), priority);

}

std::size_t ComponentManager::loadComponents(const QString& configFilename)
{
    LOG_TRACE("loadComponents(filename=" << configFilename << ")");

    // load the components tree in memory
    xmlTree_->loadFile(configFilename);

    {
        // Load the plugins containing the components
        QStringList plugins = xmlTree_->getAllPluginsNames();
        Q_FOREACH (QString plugin, plugins) {
            if (!loadPlugin(plugin)) {
                LOG_WARN("cannot load plugin '" << plugin << "'");
            } else {
                LOG_INFO("successfully loaded plugin '" << plugin << "'");
            }
        }
    }

    QDomNodeList componentsNodeList = xmlTree_->getAllComponents();
    XmlComponentConfig cfg;

    // First, create all the components in the XML list
    for (int i = 0; i < componentsNodeList.size(); ++i) {
        cfg.localCopy(componentsNodeList.item(i).toElement());
        QString componentType = cfg.getComponentType();
        QString componentName = cfg.getComponentName();
        LOG_DEBUG("try to create component '" << componentName << "'");

        // create the component and automatically add it to the component manager list
        if (!createComponent(componentType, componentName)) {
            LOG_ERROR("cannot create component '" << componentName << "'");
            continue;
        }
    }

    int componentsToConfigureCount = componentMap_.count();

    // Second, try to configure the components without regarding the dependencies
    for (int i = 0; i < componentsNodeList.size(); ++i) {
        cfg.localCopy(componentsNodeList.item(i).toElement());
        QString componentName = cfg.getComponentName();
        LOG_DEBUG("try to configure component '" << componentName << "'");

        // copy locally the config parameters of the component
        ComponentBase * component = getComponent(componentName);
        if (NULL == component) {
            LOG_WARN("component '" << componentName << "' does not exist");
        } else {
            component->param.localCopy(cfg.qDomElement());
            component->setConfigurationState(component->configureComponent(cfg));
        }
    } // for

    // Third, if some components requested a delayed configuration, retry
    for (int i = 0; i < componentsNodeList.size(); ++i) {
        cfg.localCopy(componentsNodeList.item(i).toElement());
        QString componentName = cfg.getComponentName();

        ComponentBase * component = getComponent(componentName);
        if (NULL == component) {
            LOG_WARN("component '" << componentName << "' does not exist");
        } else {
           // Pacpus 2.0 : add inputs and outputs
            component->addInputs();
            component->addOutputs();

            if (ComponentBase::CONFIGURATION_DELAYED == component->configurationState()) {
                LOG_DEBUG("try to configure component '" << componentName << "'");

                // copy locally the config parameters of the component
                component->param.localCopy(cfg.qDomElement());
                component->setConfigurationState(component->configureComponent(cfg));
            }

            if (ComponentBase::CONFIGURED_OK == component->configurationState()) {
                --componentsToConfigureCount;
            } else {
                LOG_ERROR("cannot configure component '" << componentName << "'"
                          << ". Dependencies with other components are too complex"
                          << ". It was not configured, please review your configuration and/or your component"
                          );
                component->setConfigurationState(ComponentBase::CONFIGURED_FAILED);
            }
        }
    } // for

    LOG_INFO(componentMap_.count() << " component(s) were loaded");
    if (componentsToConfigureCount > 0) {
        LOG_WARN(componentsToConfigureCount << " component(s) were not configured");
    }

    // Fourthly, create connections find in the XML list
    QDomNodeList connectionsNodeList = xmlTree_->getAllConnections();

    for (int i = 0; i < connectionsNodeList.size(); ++i) {
        cfg.localCopy(connectionsNodeList.item(i).toElement());
        QString connectionInput = cfg.getConnectionInput();
        QString connectionOutput = cfg.getConnectionOutput();
        QString connectionType = cfg.getConnectionType();
        int connectionPriority = cfg.getConnectionPriority();

        //TODO set connection mode from string

        //InputInterfaceBase::GetLast;
        //InputInterfaceBase::NeverSkip;
        //InputInterfaceBase::TimeBounded;

        if (!createConnection(connectionOutput, connectionInput, connectionType,connectionPriority)) {
            LOG_ERROR("cannot create connection '" << connectionOutput+"=>"+connectionInput << "'");
            continue;
        }
    } // for

    return componentMap_.count();
}

bool ComponentManager::start()
{
    LOG_TRACE("start()");

    bool result = true;
    for (ComponentMap::iterator it = componentMap_.begin(), itend = componentMap_.end(); it != itend; ++it) {
        result &= start(it.key());
    }

    return result;
}

bool ComponentManager::start(const QString& componentName)
{
    LOG_TRACE("start(component=" << componentName << ")");

    ComponentBase* component = getComponent(componentName);
    if (!component) {
        LOG_WARN("cannot start component '" << componentName << "'.  It does not exist!");
        return false;
    }

    LOG_INFO("starting component '" << componentName << "'...");
    if (!component->startComponent()) {
        LOG_WARN("cannot start component '" << componentName << "'. It can already be started");
    }
    LOG_INFO("successfully started component '" << componentName << "'");
    return true;
}

bool ComponentManager::stop()
{
    LOG_TRACE("stop()");

    bool result = true;
    for (ComponentMap::iterator it = componentMap_.begin(), itend = componentMap_.end(); it != itend; ++it) {
        result &= stop(*it);
    }

    return result;
}

bool ComponentManager::stop(ComponentBase* component) const
{
    if (!component) {
        LOG_WARN("NULL component pointer");
        return false;
    }
    if (!component->stopComponent()) {
        return false;
    }
    return true;
}

bool ComponentManager::stop(const QString& componentName)
{
    LOG_TRACE("stop(component=" << componentName << ")");

    ComponentBase* component = getComponent(componentName);
    if (!component) {
        LOG_WARN("cannot stop component '" << componentName << "'" << ". It does not exist");
        return false;
    }

    LOG_INFO("stopping component '" << componentName << "'...");
    if (!stop(component)) {
        LOG_WARN("cannot stop component '" << componentName << "'" << ". It can be already stopped");
    }

    return true;
}

ComponentBase* ComponentManager::getComponent(const QString& name)
{
    LOG_TRACE("getComponent(name=" << name << ")");

    ComponentMap::iterator it = componentMap_.find(name);
    if (it != componentMap_.end()) {
        return *it;
    }

    LOG_WARN("cannot retrieve component '" << name << "'" << ". It does not exist");
    return NULL;
}

QStringList ComponentManager::getAllComponentsName() const
{
    LOG_TRACE("getAllComponentsName()");
    return xmlTree_->getAllComponentsNames();
}
