/*========================== begin_copyright_notice ============================
Copyright (C) 2016-2021 Intel Corporation

SPDX-License-Identifier: MIT
============================= end_copyright_notice ===========================*/

/*!
 * @file C++ wrappers of the GTPIn configuration (KNOB) interface and implementation
 * @note This file can be included by GTPin itself and any of its tools. It is assumed that
 *       the macro 'GTPIN_IMPL' is defined when this header is included by a GTPin internal
 *       implementation file
 */

#ifndef KNOB_H
#define KNOB_H

#include <stdint.h>
#include <assert.h>
#include <string>
#include <vector>
#include <iostream>
#include <utility>

#include "knob_defs.h"

/* ===================================================================================================== */
// Class Knob
/* ===================================================================================================== */
/*!
 * Template class that provides interface to Knob argument of a type 'T'
 */
template <typename T> class Knob
{
public:
    /*!
     * Construct a new Knob argument, and register it in the Knob database.
     * @param[in]   name        Unique identifier of the Knob
     * @param[in]   initValue   The initial value of the Knob. If this is a vector-value Knob,
     *                          'initVal' will be the first element in the vector of values.
     * @param[in]   helpMsg     Description of the Knob in the help message
     * @param[in]   flags       A combination of KNOB_FLAG properties
     * @param[in]   symId       A single-symbol identifier of the Knob in the command line,
     *                          or \0 character if such identifier does not exist
     */
    inline Knob(const std::string& name, const T& initValue, const std::string& helpMsg = "",
                uint32_t flags = KNOB_FLAG_NONE, char symId = 0);

    /// Construct Knob object for the specified Knob handle
    inline Knob(GTPinKnob knob) : _knob(knob) {}

    inline void AddValue(const T& val);                 ///< Assign new value to this Knob
    inline T GetValue(uint32_t idx = 0) const;          ///< Get the Knob's value element at position 'idx'
    inline void Reset();                                ///< Clear the vector of Knob values
    inline uint32_t NumValues() const;                  ///< Get the number of the Knob's value elements

    /// Conversion and assignment operators
    operator T() const              { return GetValue(); }
    Knob& operator = (const T& val) { AddValue(val); return *this;}

private:
    GTPinKnob _knob;    ///< Knob handle
};

/// Comparison operators for Knob objects and their values
template <typename T> inline bool operator==(const Knob<T>& knob, const T& val) { return val == T(knob); }
template <typename T> inline bool operator==(const T& val, const Knob<T>& knob) { return val == T(knob); }
template <typename T> inline bool operator!=(const Knob<T>& knob, const T& val) { return val != T(knob); }
template <typename T> inline bool operator!=(const T& val, const Knob<T>& knob) { return val != T(knob); }

inline bool operator==(const Knob<std::string>& knob, const char* val) { return std::string(val) == std::string(knob); }
inline bool operator==(const char* val, const Knob<std::string>& knob) { return std::string(val) == std::string(knob); }
inline bool operator!=(const Knob<std::string>& knob, const char* val) { return std::string(val) != std::string(knob); }
inline bool operator!=(const char* val, const Knob<std::string>& knob) { return std::string(val) != std::string(knob); }

/// Streaming operator for Knob objects
template <typename T> inline std::ostream& operator<<(std::ostream& os, const Knob<T>& knob) { return (os << T(knob));}  

/* ===================================================================================================== */
// Class KnobVector
/* ===================================================================================================== */
/*!
 * Template class that provides constructor for Knob arguments associated with a vector of values of type 'T'
 */
template <typename T> class KnobVector : public Knob<T>
{
public:
    typedef std::vector<T> Vector; ///< Container of Knob values

    /*!
     * Construct a new vector-value Knob argument, and register it in the Knob database.
     * @param[in]   name        Unique identifier of the Knob
     * @param[in]   initValue  The initial vector of values
     * @param[in]   helpMsg     Description of the Knob in the help message
     * @param[in]   flags       A combination of KNOB_FLAG properties. Specifying KNOB_FLAG_VECTOR
     *                          flag is not necessary; this flag is implied
     * @param[in]   symId       A single-symbol identifier of the Knob in the command line,
     *                          or \0 character if such identifier does not exist
     */
    inline KnobVector(const std::string& name, const Vector& initValue, const std::string& helpMsg = "",
                      uint32_t flags = KNOB_FLAG_NONE, char symId = 0);
};

#if defined(GTPIN_IMPL)
/* ===================================================================================================== */
// Internal implementation of classes Knob and KnobVector in GTPin
/* ===================================================================================================== */
#include "knob_impl.h"

template <typename T> inline
Knob<T>::Knob(const std::string& name, const T& initValue, const std::string& helpMsg, uint32_t flags, char symId)
{
    _knob = KnobDb::Instance().CreateArg<T>(name, helpMsg, flags, symId);
    assert(_knob != nullptr);
    AddValue(initValue);
}

template <typename T> inline T Knob<T>::GetValue(uint32_t idx) const
{
    T val = T();
    KNOB_STATUS status = _knob->GetValue(val, idx);
    assert(status == KNOB_STATUS_SUCCESS); (void)status;
    return val;
}

template <typename T> inline void Knob<T>::AddValue(const T& val)
{
    _knob->AddValue(val);
}

template <typename T> inline void Knob<T>::Reset()
{
    _knob->Reset();
}

template <typename T> inline uint32_t Knob<T>::NumValues() const
{
    return _knob->NumValues();
}

template <typename T> inline
KnobVector<T>::KnobVector(const std::string& name, const Vector& initValue, const std::string& helpMsg,
    uint32_t flags, char symId):
    Knob<T>(KnobDb::Instance().CreateArg<T>(name, helpMsg, (flags | KNOB_FLAG_VECTOR), symId))
{
    for (const auto& val : initValue) { this->AddValue(val); }
}

#else
/* ===================================================================================================== */
// Implementation of classes Knob and KnobVector for GTPin tools
/* ===================================================================================================== */
#include "knob_client.h"

template <typename T> inline
Knob<T>::Knob(const std::string& name, const T& initValue, const std::string& helpMsg,
              uint32_t flags, char symId)
{
    _knob = KNOB_CreateArg(name.c_str(), helpMsg.c_str(), flags, symId, KnobType<T>());
    assert(_knob != nullptr);
    AddValue(initValue);
}

template <typename T> inline T Knob<T>::GetValue(uint32_t idx) const
{
    KnobValue knobVal;
    KNOB_STATUS status = KNOB_GetValue(_knob, &knobVal, idx);
    assert(status == KNOB_STATUS_SUCCESS); (void)status;
    return ExtractKnobValue<T>(knobVal);
}

template <typename T> inline void Knob<T>::AddValue(const T& val)
{
    KnobValue knobVal = MakeKnobValue(val);
    KNOB_AddValue(_knob, &knobVal);
}

template <typename T> inline void Knob<T>::Reset()
{
    KNOB_Reset(_knob);
}

template <typename T> inline uint32_t Knob<T>::NumValues() const
{
    return KNOB_NumValues(_knob);
}

template <typename T> inline
KnobVector<T>::KnobVector(const std::string& name, const Vector& initValue, const std::string& helpMsg,
    uint32_t flags, char symId):
    Knob<T>(KNOB_CreateArg(name.c_str(), helpMsg.c_str(), (flags | KNOB_FLAG_VECTOR), symId, KnobType<T>()))
{
    for (const auto& val : initValue) { this->AddValue(val); }
}

#endif

#endif

