/*========================== begin_copyright_notice ============================
Copyright (C) 2018-2022 Intel Corporation

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


/*!
 * @file Common definitions of the configuration (KNOB) interface.
 * @note Currently, KNOB interface in GTPin supports a few basic types of Knobs: bool, int, string, etc.
 *       Adding support for a new type 'T' is as simple as following:
 *       - Define the corresponding value in the KNOB_TYPE enumerator
 *       - Add the corresponding member to the KnobValue structure
 *       - Provide 'T'-specializations for the following template functions: 
 *         KnobType(), MakeKnobValue() and ExtractKnobValue()
 *       - Extend implementation of KnobTypeName() and DispatchKnobType() functions
 */

#ifndef GT_KNOB_DEFS_H
#define GT_KNOB_DEFS_H

#include <stdint.h>
#include <assert.h>
#include <string>

namespace gtpin
{
#pragma pack(push, 8)

/*! @ingroup GTPIN_KNOB
 * @brief Handle to a %Knob argument
 */
typedef class IGtKnobArg*  GTPinKnob;

/*! @ingroup GTPIN_KNOB
 * @brief Possible types of %Knob values
 */
enum KNOB_TYPE :uint32_t
{
    KNOB_TYPE_INVALID,      ///< Indicates invalid %Knob type
    KNOB_TYPE_BOOL,         ///< Boolean (bool) type
    KNOB_TYPE_INTEGER,      ///< Integer (int) type
    KNOB_TYPE_DOUBLE,       ///< Floating-point (double) type
    KNOB_TYPE_STRING        ///< String (string) type
};

/*! @ingroup GTPIN_KNOB
 * @brief  %Knob flags; can be combined to specify various %Knob properties 
 */
enum KNOB_FLAG :uint32_t
{
    KNOB_FLAG_NONE      = 0,    ///< Regular %Knob
    KNOB_FLAG_INTERNAL  = 0x1,  ///< GTPin internal (development) %Knob
    KNOB_FLAG_REQUIRED  = 0x2,  ///< Indicates that the %Knob value must be specified in the command line
    KNOB_FLAG_VECTOR    = 0x4   ///< A %Knob associated with a vector of values. If this flag is turned off,
                                ///< the %Knob is assumed to have a single, scalar value
};

/*! @ingroup GTPIN_KNOB
 * @brief %Knob descriptor: set of immutable %Knob properties
 */
struct KnobDescriptor
{
    const char*  name;      ///< Unique identifier of the %Knob
    const char*  helpMsg;   ///< Description of the %Knob in the help message
    uint32_t     flags;     ///< A combination of KNOB_FLAG properties
    char         symId;     ///< Single-symbol identifier of the %Knob in the command line,
                            ///< or \0 character if such identifier does not exist
    KNOB_TYPE    knobType;  ///< Type of the %Knob value(s)
};

/*! @ingroup GTPIN_KNOB
 * @brief Structure that holds knob value along with its type
 */
struct KnobValue
{
    KNOB_TYPE       type;   ///< Type of the %Knob value
    union
    {
        bool        _bool;      ///< knob value for 'type' = KNOB_TYPE_BOOL
        int         _int;       ///< knob value for 'type' = KNOB_TYPE_INTEGER
        double      _double;    ///< knob value for 'type' = KNOB_TYPE_DOUBLE
        const char* _str;       ///< knob value for 'type' = KNOB_TYPE_STRING
    }value;
};

/*!
 * Template function that converts type 'T' into corresponding KNOB_TYPE enumeration value
 */
template <typename T>   inline KNOB_TYPE KnobType()                 { return KNOB_TYPE_INVALID; }
template<>              inline KNOB_TYPE KnobType<bool>()           { return KNOB_TYPE_BOOL; }
template<>              inline KNOB_TYPE KnobType<int>()            { return KNOB_TYPE_INTEGER; }
template<>              inline KNOB_TYPE KnobType<double>()         { return KNOB_TYPE_DOUBLE; }
template<>              inline KNOB_TYPE KnobType<std::string>()    { return KNOB_TYPE_STRING; }

/*! @ingroup GTPIN_KNOB
 * Construct KnobValue out of the value of type 'T'
 */
template <typename T> inline KnobValue MakeKnobValue(const T& val);

template <> inline KnobValue MakeKnobValue<bool>(const bool& val)
{ KnobValue knobVal; knobVal.type = KNOB_TYPE_BOOL; knobVal.value._bool = val; return knobVal; }

template <> inline KnobValue MakeKnobValue<int>(const int& val)
{ KnobValue knobVal; knobVal.type = KNOB_TYPE_INTEGER; knobVal.value._int = val; return knobVal; }

template <> inline KnobValue MakeKnobValue<double>(const double& val)
{ KnobValue knobVal; knobVal.type = KNOB_TYPE_DOUBLE; knobVal.value._double = val; return knobVal; }

template <> inline KnobValue MakeKnobValue<std::string>(const std::string& val)
{ KnobValue knobVal; knobVal.type = KNOB_TYPE_STRING; knobVal.value._str = val.c_str(); return knobVal; }

/*! @ingroup GTPIN_KNOB
 * Extract value of type 'T' out of KnobValue
 */
template <typename T> inline T ExtractKnobValue(const KnobValue& knobVal);

template <> inline bool ExtractKnobValue<bool>(const KnobValue& knobVal)
{ assert(knobVal.type == KNOB_TYPE_BOOL); return knobVal.value._bool; }

template <> inline int ExtractKnobValue<int>(const KnobValue& knobVal)
{ assert(knobVal.type == KNOB_TYPE_INTEGER); return knobVal.value._int; }

template <> inline double ExtractKnobValue<double>(const KnobValue& knobVal)
{ assert(knobVal.type == KNOB_TYPE_DOUBLE); return knobVal.value._double; }

template <> inline std::string ExtractKnobValue<std::string>(const KnobValue& knobVal)
{ assert(knobVal.type == KNOB_TYPE_STRING); return knobVal.value._str; }

/*!
 * @return Human readable name of the specified %Knob type
 */
inline const char* KnobTypeName(KNOB_TYPE knobType)
{
    switch (knobType)
    {
    case KNOB_TYPE_BOOL:    return "bool";
    case KNOB_TYPE_INTEGER: return "int";
    case KNOB_TYPE_DOUBLE:  return "double";
    case KNOB_TYPE_STRING:  return "string";
    default:                return "";
    };
}

/*!
 * Template function that converts the specified 'knobType' into corresponding C++type 'T',
 * and invokes method void F::ProcessKnobType<T>() in the specified object 'f'.
 */
template <typename F> inline void DispatchKnobType(F& f, KNOB_TYPE knobType)
{
    switch (knobType)
    {
    case KNOB_TYPE_BOOL:    f.template ProcessKnobType<bool>();         return;
    case KNOB_TYPE_INTEGER: f.template ProcessKnobType<int>();          return;
    case KNOB_TYPE_DOUBLE:  f.template ProcessKnobType<double>();       return;
    case KNOB_TYPE_STRING:  f.template ProcessKnobType<std::string>();  return;
    default:                                                            return;
    };
}

#pragma pack(pop)
}
#endif
