/*
 * Copyright (c) 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 "ParameterFramework.h"
#include <ParameterMgrPlatformConnector.h>

#include <NonCopyable.hpp>

#include <iostream>
#include <limits>
#include <string>
#include <map>

#include <cassert>
#include <cstring>
#include <cstdlib>

using std::string;

/** Rename long pfw types to short ones in pfw namespace. */
namespace pfw
{
typedef ISelectionCriterionInterface Criterion;
typedef std::map<string, Criterion *> Criteria;
typedef CParameterMgrPlatformConnector Pfw;
} // namespace pfw

/** Class to abstract the boolean+string status api. */
class Status
{
public:
    /** Fail without an instance of status. */
    static bool failure() { return false; }
    /** Fail with the given error msg. */
    bool failure(const string &msg)
    {
        mMsg = msg;
        return false;
    }
    /** Success (no error message). */
    bool success()
    {
        mMsg.clear();
        return true;
    }

    /** Forward a status operation.
      * @param[in] success the operaton status to forward
      *                    or forward a previous failure if omitted
      */
    bool forward(bool success = false)
    {
        if (success) {
            mMsg.clear();
        }
        return success;
    }
    /** Error message accessors.
      *
      * Pfw api requires to provide a reference to a string in order
      * for it to log. This function provide a reference to a string that
      * will be added to the error message on failure.
      */
    string &msg() { return mMsg; }
private:
    string mMsg;
};

///////////////////////////////
///////////// Log /////////////
///////////////////////////////

/** Default log callback. Log to cout or cerr depending on level. */
static void defaultLogCb(void *, PfwLogLevel level, const char *logLine)
{
    switch (level) {
    case pfwLogInfo:
        std::cout << logLine << std::endl;
        break;
    case pfwLogWarning:
        std::cerr << logLine << std::endl;
        break;
    };
}

static PfwLogger defaultLogger = {nullptr, &defaultLogCb};

class LogWrapper : public CParameterMgrPlatformConnector::ILogger
{
public:
    LogWrapper(const PfwLogger &logger) : mLogger(logger) {}
    LogWrapper() : mLogger() {}
    virtual ~LogWrapper() {}
private:
    void info(const string &msg) override { log(pfwLogInfo, msg); }

    void warning(const string &msg) override { log(pfwLogWarning, msg); }

    void log(PfwLogLevel level, const string &strLog)
    {
        // A LogWrapper should NOT be register to the pfw (thus log called)
        // if logCb is NULL.
        assert(mLogger.logCb != nullptr);
        mLogger.logCb(mLogger.userCtx, level, strLog.c_str());
    }

    PfwLogger mLogger;
};

///////////////////////////////
///////////// Core ////////////
///////////////////////////////

struct PfwHandler_ : private utility::NonCopyable
{
    void setLogger(const PfwLogger *logger);
    bool createCriteria(const PfwCriterion criteria[], size_t criterionNb);

    pfw::Criteria criteria;
    pfw::Pfw *pfw = nullptr;
    /** Status of the last called function.
      * Is mutable because even a const function can fail.
      */
    mutable Status lastStatus;

private:
    LogWrapper mLogger;
};

PfwHandler *pfwCreate()
{
    return new PfwHandler();
}

void pfwDestroy(PfwHandler *handle)
{
    delete handle->pfw;
    delete handle;
}

void PfwHandler::setLogger(const PfwLogger *logger)
{
    if (logger != nullptr and logger->logCb == nullptr) {
        return; // There is no callback, do not log => do not add a logger
    }
    mLogger = logger != nullptr ? *logger : defaultLogger;
    pfw->setLogger(&mLogger);
}

bool PfwHandler::createCriteria(const PfwCriterion criteriaArray[], size_t criterionNb)
{
    Status &status = lastStatus;
    // Add criteria
    for (size_t criterionIndex = 0; criterionIndex < criterionNb; ++criterionIndex) {
        const PfwCriterion &criterion = criteriaArray[criterionIndex];
        if (criterion.name == nullptr) {
            return status.failure("Criterion name is NULL");
        }
        if (criterion.values == nullptr) {
            return status.failure("Criterion values is NULL");
        }
        // Check that the criterion does not exist
        if (criteria.find(criterion.name) != criteria.end()) {
            return status.failure("Criterion \"" + string(criterion.name) + "\" already exist");
        }

        // Create criterion type
        ISelectionCriterionTypeInterface *type =
            pfw->createSelectionCriterionType(criterion.inclusive);
        assert(type != nullptr);
        // Add criterion values
        for (size_t valueIndex = 0; criterion.values[valueIndex] != nullptr; ++valueIndex) {
            int value;
            if (criterion.inclusive) {
                // Check that (int)1 << valueIndex would not overflow (UB)
                if (std::numeric_limits<int>::max() >> valueIndex == 0) {
                    return status.failure("Too many values for criterion " +
                                          string(criterion.name));
                }
                value = 1 << valueIndex;
            } else {
                value = static_cast<int>(valueIndex);
            }
            const char *valueName = criterion.values[valueIndex];
            string error;
            if (not type->addValuePair(value, valueName, error)) {
                return status.failure("Could not add value " + string(valueName) +
                                      " to criterion " + criterion.name + ": " + error);
            }
        }
        // Create criterion and add it to the pfw
        criteria[criterion.name] = pfw->createSelectionCriterion(criterion.name, type);
    }
    return status.success();
}

bool pfwStart(PfwHandler *handle, const char *configPath, const PfwCriterion criteria[],
              size_t criterionNb, const PfwLogger *logger)
{
    // Check that the api is correctly used
    Status &status = handle->lastStatus;

    if (handle->pfw != nullptr) {
        return status.failure("Can not start an already started parameter framework");
    }
    // Create a pfw
    handle->pfw = new CParameterMgrPlatformConnector(configPath);

    handle->setLogger(logger);

    if (not handle->createCriteria(criteria, criterionNb)) {
        return status.failure();
    }

    return status.forward(handle->pfw->start(status.msg()));
}

const char *pfwGetLastError(const PfwHandler *handle)
{
    return handle->lastStatus.msg().c_str();
}

static pfw::Criterion *getCriterion(const pfw::Criteria &criteria, const string &name)
{
    auto it = criteria.find(name);
    return it == criteria.end() ? nullptr : it->second;
}

bool pfwSetCriterion(PfwHandler *handle, const char name[], int value)
{
    Status &status = handle->lastStatus;
    if (handle->pfw == nullptr) {
        return status.failure("Can not set criterion \"" + string(name) +
                              "\" as the parameter framework is not started.");
    }
    pfw::Criterion *criterion = getCriterion(handle->criteria, name);
    if (criterion == nullptr) {
        return status.failure("Can not set criterion " + string(name) + " as does not exist");
    }
    criterion->setCriterionState(value);
    return status.success();
}
bool pfwGetCriterion(const PfwHandler *handle, const char name[], int *value)
{
    Status &status = handle->lastStatus;
    if (handle->pfw == nullptr) {
        return status.failure("Can not get criterion \"" + string(name) +
                              "\" as the parameter framework is not started.");
    }
    pfw::Criterion *criterion = getCriterion(handle->criteria, name);
    if (criterion == nullptr) {
        return status.failure("Can not get criterion " + string(name) + " as it does not exist");
    }
    *value = criterion->getCriterionState();
    return status.success();
}

bool pfwApplyConfigurations(const PfwHandler *handle)
{
    Status &status = handle->lastStatus;
    if (handle->pfw == nullptr) {
        return status.failure("Can not commit criteria "
                              "as the parameter framework is not started.");
    }
    handle->pfw->applyConfigurations();
    return status.success();
}

///////////////////////////////
/////// Parameter access //////
///////////////////////////////

struct PfwParameterHandler_
{
    PfwHandler &pfw;
    CParameterHandle &parameter;
};

PfwParameterHandler *pfwBindParameter(PfwHandler *handle, const char path[])
{
    Status &status = handle->lastStatus;
    if (handle->pfw == nullptr) {
        status.failure("The parameter framework is not started, "
                       "while trying to bind parameter \"" +
                       string(path) + "\")");
        return nullptr;
    }

    CParameterHandle *paramHandle;
    paramHandle = handle->pfw->createParameterHandle(path, status.msg());
    if (paramHandle == nullptr) {
        return nullptr;
    }

    status.success();
    PfwParameterHandler publicHandle = {*handle, *paramHandle};
    return new PfwParameterHandler(publicHandle);
}

void pfwUnbindParameter(PfwParameterHandler *handle)
{
    delete &handle->parameter;
    delete handle;
}

bool pfwGetIntParameter(const PfwParameterHandler *handle, int32_t *value)
{
    Status &status = handle->pfw.lastStatus;
    return status.forward(handle->parameter.getAsSignedInteger(*value, status.msg()));
}
bool pfwSetIntParameter(PfwParameterHandler *handle, int32_t value)
{
    Status &status = handle->pfw.lastStatus;
    return status.forward(handle->parameter.setAsSignedInteger(value, status.msg()));
}

bool pfwGetStringParameter(const PfwParameterHandler *handle, char *value[])
{
    Status &status = handle->pfw.lastStatus;
    *value = nullptr;
    string retValue;
    bool success = handle->parameter.getAsString(retValue, status.msg());
    if (not success) {
        return status.forward();
    }

    *value = strdup(retValue.c_str());
    return status.success();
}

bool pfwSetStringParameter(PfwParameterHandler *handle, const char value[])
{
    Status &status = handle->pfw.lastStatus;
    return status.forward(handle->parameter.setAsString(value, status.msg()));
}

void pfwFree(void *ptr)
{
    std::free(ptr);
}