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

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

/*!
 * @file Implementation of the command line parser for GTPIn tools
 * @note This file can be included into GTPin implementation project or built into a GTPin tool.
 *       It is assumed that the macro 'GTPIN_IMPL' is defined if this file is included into GTPin
 *       implementation project.
 */
#include "knob_parser.h"

#include <stdint.h>
#include <assert.h>
#include <vector>
#include <string>
#include <tclap/CmdLine.h>

#include "knob.h"

#if !defined(GTPIN_IMPL)
#include "gtreplay_client.h"
#else
#include "gtreplay_assert.h"
extern const char* GTREPLAY_VERSION;
extern const char* GTREPLAY_REVISION;

#endif

 /* ===================================================================================================== */
 // Class KnobParser
 /* ===================================================================================================== */
 /*!
 * Class that extracts GTReplay configuration parameters from a command line and initializes Knob
 * arguments accordingly
 */
class KnobParser
{
public:
    inline KnobParser() : _links(), _errorMsg("")  {}  ///< Constructor
    inline ~KnobParser() { DestroyArgLinks(); }        ///< Destructor

    /*!
     * Parse the specified command line arguments and setup GTReplay configuration accordingly
     * @param[in] argc, argv    Arguments of the GTReplay command line
     * @return true - success, false - failure
     */
    inline bool operator()(int argc, const char** argv);

    /// Get text of the last error detected by the parser
    inline std::string ErrorMsg();

private:
    KnobParser(const KnobParser&);              // Disabled
    KnobParser& operator=(const KnobParser&);   // Disabled

    /// Abstract interface and implementation classes that link Knob to its corresponding TCLAP argument
    class ArgLink;
    template <typename T> class ScalarArgLink;
    template <typename T> class VectorArgLink;

    class ArgLinkFactory; ///< Factory of ArgLink instances

    /// Populate the '_links' vector with ArgLink instances for all GTReplay Knobs
    inline void CreateArgLinks();

    /// Destroy all ArgLink instances in the '_links' vector
    inline void DestroyArgLinks();

    /// Reset this object before next command line parsing
    inline void Reset();

    /// @return The string to be displayed by --version switch.
    static inline std::string VersionMsg();

    /// @return Short usage string
    static inline std::string UsageMsg();

private:
    std::vector<ArgLink*> _links; ///< Container of ArgLink instances for all GTReplay Knobs
    std::string _errorMsg;        ///< Text of the error detected by the parser
};

/* ===================================================================================================== */
// Class KnobParser::ArgLink
/* ===================================================================================================== */
/// Abstract class that represents a link between Knob and its corresponding TCLAP argument
class KnobParser::ArgLink
{
public:
    ArgLink() {}                                    ///< Constructor
    virtual ~ArgLink() {}                           ///< Destructor
    virtual void XferValueToKnob() = 0;             ///< Transfer value from TCLAP argument to the corresponding Knob
    virtual void AddToCmd(TCLAP::CmdLine& cmd) = 0; ///< Add the linked TCLAP argument to the specified command line

private:
    ArgLink(const KnobParser&) = delete;
    ArgLink& operator=(const KnobParser&) = delete;
};

/* ===================================================================================================== */
// Class KnobParser::ScalarArgLink
/* ===================================================================================================== */
/// Implementation of the KnobParser::ArgLink interface for scalar Knob of type T
template <typename T> class KnobParser::ScalarArgLink : public ArgLink
{
public:
    ScalarArgLink(GTPinKnob knob, TCLAP::ValueArg<T>* arg) : _knob(knob), _arg(arg) {}
    ~ScalarArgLink() { delete _arg; }

    void XferValueToKnob()              { _knob.AddValue(_arg->getValue()); }
    void AddToCmd(TCLAP::CmdLine& cmd)  { cmd.add(_arg); }

private:
    Knob<T>             _knob;  ///< Knob argument
    TCLAP::ValueArg<T>* _arg;   ///< TCLAP argument
};

/* ===================================================================================================== */
// Class KnobParser::ScalarArgLink<bool>
/* ===================================================================================================== */
/// Specialization of KnobParser::ScalarArgLink for type 'bool'
template <> class KnobParser::ScalarArgLink<bool> : public ArgLink
{
public:
    ScalarArgLink(GTPinKnob knob, TCLAP::SwitchArg* arg) : _knob(knob), _arg(arg) {}
    ~ScalarArgLink() { delete _arg; }

    void XferValueToKnob()              { _knob.AddValue(_arg->getValue()); }
    void AddToCmd(TCLAP::CmdLine& cmd)  { cmd.add(_arg); }

private:
    Knob<bool>          _knob;  ///< Knob argument
    TCLAP::SwitchArg*   _arg;   ///< TCLAP argument
};

/* ===================================================================================================== */
// Class KnobParser::VectorArgLink
/* ===================================================================================================== */
/// Implementation of the KnobParser::ArgLink interface for vector Knob of type T
template <typename T> class KnobParser::VectorArgLink : public ArgLink
{
public:
    VectorArgLink(GTPinKnob knob, TCLAP::MultiArg<T>* arg) : _knob(knob), _arg(arg) {}
    ~VectorArgLink() { delete _arg; }

    void XferValueToKnob()
    {
        if (_arg->isSet())
        {
            _knob.Reset();
            const std::vector<T>& values = _arg->getValue();
            for (const auto& value: values) { _knob.AddValue(value); }
        }
    }

    void AddToCmd(TCLAP::CmdLine& cmd)  { cmd.add(_arg); }

private:
    Knob<T>             _knob;  ///< Knob argument
    TCLAP::MultiArg<T>* _arg;   ///< TCLAP argument
};

/* ===================================================================================================== */
// Class KnobParser::ArgLinkFactory
/* ===================================================================================================== */
/// Class that creates ArgLink instance for a given Knob handle
class KnobParser::ArgLinkFactory
{
public:
    /// @return reference to ArgEntry instance that corresponds the specified Knob
    inline ArgLink* Create(GTPinKnob knob);

private:
    /// Implementation of the ProcessKnobType() method, as required by DispatchKnobType()
    template <typename T> inline void ProcessKnobType();
    template <typename F> friend void DispatchKnobType(F& f, KNOB_TYPE knobType);

    /// Get descriptor of the specified Knob
    static inline void GetKnobDescriptor(GTPinKnob knob, KnobDescriptor& dsc);

private:
    GTPinKnob      _knob;
    KnobDescriptor _dsc;
    ArgLink*       _link;
};


/* ===================================================================================================== */
// KnobParser::ArgLinkFactory implementation
/* ===================================================================================================== */
template <typename T> inline void KnobParser::ArgLinkFactory::ProcessKnobType()
{
    bool required = ((_dsc.flags & KNOB_FLAG_REQUIRED) != 0);
    TCLAP::Category category = (((_dsc.flags & KNOB_FLAG_INTERNAL) != 0) ? TCLAP::DEVELOPER : TCLAP::REGULAR);
    char symId[] = {_dsc.symId, 0};
    std::string typeDesc = KnobTypeName(KnobType<T>());

    if ((_dsc.flags & KNOB_FLAG_VECTOR) == 0)
    {
        T defaultValue = Knob<T>(_knob).GetValue();
        auto arg = new TCLAP::ValueArg<T>(symId, _dsc.name, _dsc.helpMsg, required, defaultValue, typeDesc, category);
        _link = new ScalarArgLink<T>(_knob, arg);
    }
    else
    {
        auto arg = new TCLAP::MultiArg<T>(symId, _dsc.name, _dsc.helpMsg, required, typeDesc, category);
        _link = new VectorArgLink<T>(_knob, arg);
    }
}

/// Specialization of ArgLinkFactory::ProcessKnobType() for 'bool' type
template <> inline void KnobParser::ArgLinkFactory::ProcessKnobType<bool>()
{
    bool required = ((_dsc.flags & KNOB_FLAG_REQUIRED) != 0);
    TCLAP::Category category = (((_dsc.flags & KNOB_FLAG_INTERNAL) != 0) ? TCLAP::DEVELOPER : TCLAP::REGULAR);
    char symId[] = {_dsc.symId, 0};
    std::string typeDesc = KnobTypeName(KNOB_TYPE_BOOL);

    if ((_dsc.flags & KNOB_FLAG_VECTOR) == 0)
    {
        bool defaultValue = Knob<bool>(_knob).GetValue();
        auto arg = new TCLAP::SwitchArg(symId, _dsc.name, _dsc.helpMsg, defaultValue, category);
        _link = new ScalarArgLink<bool>(_knob, arg);
    }
    else
    {
        auto arg = new TCLAP::MultiArg<bool>(symId, _dsc.name, _dsc.helpMsg, required, typeDesc, category);
        _link = new VectorArgLink<bool>(_knob, arg);
    }
}

inline void KnobParser::ArgLinkFactory::GetKnobDescriptor(GTPinKnob knob, KnobDescriptor& dsc)
{
#if defined(GTPIN_IMPL)
    knob->GetDescriptor(dsc);
#else
    KNOB_GetDescriptor(knob, &dsc);
#endif
}

inline KnobParser::ArgLink* KnobParser::ArgLinkFactory::Create(GTPinKnob knob)
{
    _knob = knob;
    GetKnobDescriptor(knob, _dsc);
    _link = nullptr;
    DispatchKnobType(*this, _dsc.knobType);
    return _link;
}

 /* ===================================================================================================== */
 // KnobParser implementation
 /* ===================================================================================================== */
inline std::string KnobParser::ErrorMsg()
{
    return _errorMsg;
}

inline std::string KnobParser::VersionMsg()
{
#if defined(GTPIN_IMPL)
    return "GTReplay version " + std::string(GTREPLAY_VERSION) + " (" + std::string(GTREPLAY_REVISION) + ")";
#else
    return "GTPin version " + std::string(GTReplay_GetVersion()) + " (" + std::string(GTReplay_GetRevision()) + ")";
#endif
}

inline std::string KnobParser::UsageMsg()
{
    return "gtreplay.exe [GTReplay args] [-t <GTReplay tool0>] [-t <GTReplay tool1>] ... [-t <GTReplay toolN>] -- path-to-directory-where-glit-is-located\n\nGTReplay args:\n\n";
}

inline void KnobParser::CreateArgLinks()
{
#if defined(GTPIN_IMPL)
    KnobDb& knobDb = KnobDb::Instance();
    uint32_t numArgs = knobDb.NumArgs();
    for (uint32_t i = 0; i != numArgs; ++i)
    {
        _links.push_back(ArgLinkFactory().Create(knobDb.GetArg(i)));
    }
#else
    uint32_t numArgs = KNOB_NumArgs();
    for (uint32_t i = 0; i != numArgs; ++i)
    {
        _links.push_back(ArgLinkFactory().Create(KNOB_GetArg(i)));
    }
#endif
}

inline void KnobParser::DestroyArgLinks()
{
    for (auto& link: _links) { delete link; }
    _links.clear();
}

inline void KnobParser::Reset()
{
    DestroyArgLinks();
    _errorMsg.clear();
}

inline bool KnobParser::operator()(int argc, const char** argv)
{
    Reset();

    // Construct the TCLAP command line object
    TCLAP::CmdLine cmd("",
        ' ',
        VersionMsg(),
        true,
        "gtreplay",
        UsageMsg());

    // Populate the '_links' vector with ArgLink instances for all GTReplay Knobs
    CreateArgLinks();

    // Add all elements of the just built '_links' vector to the command line
    for (auto& link: _links)
    {
        try
        {
            link->AddToCmd(cmd);
        }
        catch (TCLAP::ArgException &e)
        {
            _errorMsg = e.error();
            return false;
        }
    }

    // Parse  the command line
    try
    {
        cmd.parse(argc, argv);
    }
    catch (TCLAP::ArgException &e)
    {
        _errorMsg = e.error();
        return false;
    }

    /// Transfer values from TCLAP arguments to the corresponding Knobs
    for (auto& link: _links)
    {
        link->XferValueToKnob();
    }

    return true;
}

/* ===================================================================================================== */
// External interface
/* ===================================================================================================== */
extern "C" void ConfigureGTReplay(int argc, const char **argv)
{
    KnobParser parser;
    if (!parser(argc, argv))
    {
    #if defined(GTPIN_IMPL)
        GTREPLAY_ERROR_MSG(parser.ErrorMsg().c_str());
    #else
        GTReplay_Abort(parser.ErrorMsg().c_str());
    #endif
    }
}
