/*
 * Copyright (c) 2011-2015, Intel Corporation
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation and/or
 * other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors
 * may be used to endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include <algorithm>
#include "SystemClass.h"
#include "SubsystemLibrary.h"
#include "VirtualSubsystem.h"
#include "LoggingElementBuilderTemplate.h"
#include <cassert>
#include "PluginLocation.h"
#include "DynamicLibrary.hpp"
#include "Utility.h"
#include "Memory.hpp"

#define base CConfigurableElement

#ifndef PARAMETER_FRAMEWORK_PLUGIN_ENTRYPOINT_V1
#error Missing PARAMETER_FRAMEWORK_PLUGIN_ENTRYPOINT_V1 macro definition
#endif
#define QUOTE(X) #X
#define MACRO_TO_STR(X) QUOTE(X)
const char CSystemClass::entryPointSymbol[] =
    MACRO_TO_STR(PARAMETER_FRAMEWORK_PLUGIN_ENTRYPOINT_V1);
using PluginEntryPointV1 = void (*)(CSubsystemLibrary *, core::log::Logger &);

using std::list;
using std::string;

// FIXME: integrate SystemClass to core namespace
using namespace core;

CSystemClass::CSystemClass(log::Logger &logger)
    : _pSubsystemLibrary(new CSubsystemLibrary()), _logger(logger)
{
}

CSystemClass::~CSystemClass()
{
    delete _pSubsystemLibrary;

    // Destroy child subsystems *before* unloading the libraries (otherwise crashes will occur
    // as unmapped code will be referenced)
    clean();
}

bool CSystemClass::childrenAreDynamic() const
{
    return true;
}

string CSystemClass::getKind() const
{
    return "SystemClass";
}

bool CSystemClass::getMappingData(const std::string & /*strKey*/,
                                  const std::string *& /*pStrValue*/) const
{
    // Although it could make sense to have mapping in the system class,
    // just like at subsystem level, it is currently not supported.
    return false;
}

string CSystemClass::getFormattedMapping() const
{
    return "";
}

bool CSystemClass::loadSubsystems(string &strError, const CSubsystemPlugins *pSubsystemPlugins,
                                  bool bVirtualSubsystemFallback)
{
    // Start clean
    _pSubsystemLibrary->clean();

    typedef TLoggingElementBuilderTemplate<CVirtualSubsystem> VirtualSubsystemBuilder;
    // Add virtual subsystem builder
    _pSubsystemLibrary->addElementBuilder("Virtual", new VirtualSubsystemBuilder(_logger));
    // Set virtual subsytem as builder fallback if required
    if (bVirtualSubsystemFallback) {
        _pSubsystemLibrary->setDefaultBuilder(
            utility::make_unique<VirtualSubsystemBuilder>(_logger));
    }

    // Add subsystem defined in shared libraries
    core::Results errors;
    bool bLoadPluginsSuccess = loadSubsystemsFromSharedLibraries(errors, pSubsystemPlugins);

    // Fill strError for caller, he has to decide if there is a problem depending on
    // bVirtualSubsystemFallback value
    strError = utility::asString(errors);

    return bLoadPluginsSuccess || bVirtualSubsystemFallback;
}

bool CSystemClass::loadSubsystemsFromSharedLibraries(core::Results &errors,
                                                     const CSubsystemPlugins *pSubsystemPlugins)
{
    // Plugin list
    list<string> lstrPluginFiles;

    size_t pluginLocation;

    for (pluginLocation = 0; pluginLocation < pSubsystemPlugins->getNbChildren();
         pluginLocation++) {

        // Get Folder for current Plugin Location
        const CPluginLocation *pPluginLocation =
            static_cast<const CPluginLocation *>(pSubsystemPlugins->getChild(pluginLocation));

        string strFolder(pPluginLocation->getFolder());
        if (!strFolder.empty()) {
            strFolder += "/";
        }
        // Iterator on Plugin List:
        list<string>::const_iterator it;

        const list<string> &pluginList = pPluginLocation->getPluginList();

        for (it = pluginList.begin(); it != pluginList.end(); ++it) {

            // Fill Plugin files list
            lstrPluginFiles.push_back(strFolder + *it);
        }
    }

    // Actually load plugins
    while (!lstrPluginFiles.empty()) {

        // Because plugins might depend on one another, loading will be done
        // as an iteration process that finishes successfully when the remaining
        // list of plugins to load gets empty or unsuccessfully if the loading
        // process failed to load at least one of them

        // Attempt to load the complete list
        if (!loadPlugins(lstrPluginFiles, errors)) {

            // Unable to load at least one plugin
            break;
        }
    }

    if (!lstrPluginFiles.empty()) {
        // Unable to load at least one plugin
        errors.push_back("Unable to load the following plugins: " +
                         utility::asString(lstrPluginFiles, ", ") + ".");
        return false;
    }

    return true;
}

// Plugin loading
bool CSystemClass::loadPlugins(list<string> &lstrPluginFiles, core::Results &errors)
{
    assert(lstrPluginFiles.size());

    bool bAtLeastOneSubsystemPluginSuccessfullyLoaded = false;

    auto it = lstrPluginFiles.begin();

    while (it != lstrPluginFiles.end()) {

        string strPluginFileName = *it;

        // Load attempt
        try {
            auto library = utility::make_unique<DynamicLibrary>(strPluginFileName);

            // Load symbol from library
            auto subSystemBuilder = library->getSymbol<PluginEntryPointV1>(entryPointSymbol);

            // Store libraries handles
            _subsystemLibraryHandleList.push_back(std::move(library));

            // Fill library
            subSystemBuilder(_pSubsystemLibrary, _logger);

        } catch (std::exception &e) {
            errors.push_back(e.what());

            // Next plugin
            ++it;

            continue;
        }

        // Account for this success
        bAtLeastOneSubsystemPluginSuccessfullyLoaded = true;

        // Remove successfully loaded plugin from list and select next
        lstrPluginFiles.erase(it++);
    }

    return bAtLeastOneSubsystemPluginSuccessfullyLoaded;
}

const CSubsystemLibrary *CSystemClass::getSubsystemLibrary() const
{
    return _pSubsystemLibrary;
}

void CSystemClass::checkForSubsystemsToResync(CSyncerSet &syncerSet, core::Results &infos)
{
    size_t uiNbChildren = getNbChildren();
    size_t uiChild;

    for (uiChild = 0; uiChild < uiNbChildren; uiChild++) {

        CSubsystem *pSubsystem = static_cast<CSubsystem *>(getChild(uiChild));

        // Collect and consume the need for a resync
        if (pSubsystem->needResync(true)) {

            infos.push_back("Resynchronizing subsystem: " + pSubsystem->getName());
            // get all subsystem syncers
            pSubsystem->fillSyncerSet(syncerSet);
        }
    }
}

void CSystemClass::cleanSubsystemsNeedToResync()
{
    size_t uiNbChildren = getNbChildren();
    size_t uiChild;

    for (uiChild = 0; uiChild < uiNbChildren; uiChild++) {

        CSubsystem *pSubsystem = static_cast<CSubsystem *>(getChild(uiChild));

        // Consume the need for a resync
        pSubsystem->needResync(true);
    }
}