// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-

/*========================== begin_copyright_notice ===========================
 Copyright (C) 2015-2019 Intel Corporation

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

/******************************************************************************
 *
 *  file:  CmdLine.h
 *
 *  Copyright (c) 2003, Michael E. Smoot .
 *  Copyright (c) 2004, Michael E. Smoot, Daniel Aarno.
 *  All rights reserved.
 *
 *  See the file COPYING in the top directory of this distribution for
 *  more information.
 *
 *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS
 *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 *  DEALINGS IN THE SOFTWARE.
 *
 *****************************************************************************/
#ifndef TCLAP_CMDLINE_H
#define TCLAP_CMDLINE_H

#include <tclap/SwitchArg.h>
#include <tclap/MultiSwitchArg.h>
#include <tclap/UnlabeledValueArg.h>
#include <tclap/UnlabeledMultiArg.h>

#include <tclap/XorHandler.h>
#include <tclap/HelpVisitor.h>
#include <tclap/VersionVisitor.h>
#include <tclap/IgnoreRestVisitor.h>

#include <tclap/CmdLineOutput.h>
#include <tclap/StdOutput.h>

#include <tclap/Constraint.h>
#include <tclap/ValuesConstraint.h>

#include <string>
#include <vector>
#include <list>
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <stdlib.h> // Needed for exit(), which isn't defined in some envs.

namespace TCLAP
{

template<typename T> void DelPtr(T ptr)
{
    delete ptr;
}

template<typename C> void ClearContainer(C &c)
{
    typedef typename C::value_type value_type;
    std::for_each(c.begin(), c.end(), DelPtr<value_type>);
    c.clear();
}


/**
 * The base class that manages the command line definition and passes
 * along the parsing to the appropriate Arg classes.
 */
class CmdLine : public CmdLineInterface
{
    protected:

        /**
         * The list of arguments that will be tested against the
         * command line.
         */
        std::list<Arg*> _argList;

        /**
         * The list of developer arguments that will be tested against the
         * command line only if "--xyyzy" was given.
         */
        std::list<Arg*> _devArgList;

        /**
         * The name of the program.  Set to argv[0].
         */
        std::string _progName;

        /**
         * A message used to describe the program.  Used in the usage output.
         */
        std::string _message;

        /**
         * The version to be displayed with the --version switch.
         */
        std::string _version;

        /**
         * The number of arguments that are required to be present on
         * the command line. This is set dynamically, based on the
         * Args added to the CmdLine object.
         */
        int _numRequired;

        /**
         * The character that is used to separate the argument flag/name
         * from the value.  Defaults to ' ' (space).
         */
        char _delimiter;

        /**
         * The handler that manages xoring lists of args.
         */
        XorHandler _xorHandler;

        /**
         * A list of Args to be explicitly deleted when the destructor
         * is called.  At the moment, this only includes the three default
         * Args.
         */
        std::list<Arg*> _argDeleteOnExitList;

        /**
         * A list of Visitors to be explicitly deleted when the destructor
         * is called.  At the moment, these are the Visitors created for the
         * default Args.
         */
        std::list<Visitor*> _visitorDeleteOnExitList;

        /**
         * Object that handles all output for the CmdLine.
         */
        CmdLineOutput* _output;

        /**
         * Should CmdLine handle parsing exceptions internally?
         */
        bool _handleExceptions;

        /**
         * Throws an exception listing the missing args.
         */
        void missingArgsException();

        /**
         * Checks whether a name/flag string matches entirely matches
         * the Arg::blankChar.  Used when multiple switches are combined
         * into a single argument.
         * \param s - The message to be used in the usage.
         */
        bool _emptyCombined(const std::string& s);

        /**
         * Perform a delete ptr; operation on ptr when this object is deleted.
         */
        void deleteOnExit(Arg* ptr);

        /**
         * Perform a delete ptr; operation on ptr when this object is deleted.
         */
        void deleteOnExit(Visitor* ptr);

private:

        /**
         * Prevent accidental copying.
         */
        CmdLine(const CmdLine& rhs);
        CmdLine& operator=(const CmdLine& rhs);

        /**
         * Encapsulates the code common to the constructors
         * (which is all of it).
         */
        void _constructor();


        /**
         * Is set to true when a user sets the output object. We use this so
         * that we don't delete objects that are created outside of this lib.
         */
        bool _userSetOutput;

        /**
         * Whether or not to automatically create help and version switches.
         */
        bool _helpAndVersion;

        /**
         * GTPin Added fields to replace the short usage message,
         * default is empty string, which means it won't be used.
         * Should include the header for the long usage message for now
         */
        std::string _customShortUsageMsg;

    public:

        /**
         * Command line constructor. Defines how the arguments will be
         * parsed.
         * \param message - The message to be used in the usage
         * output.
         * \param delimiter - The character that is used to separate
         * the argument flag/name from the value.  Defaults to ' ' (space).
         * \param version - The version number to be used in the
         * --version switch.
         * \param helpAndVersion - Whether or not to create the Help and
         * Version switches. Defaults to true.
         * \param progName - The name of the program to be displayed
         * in the Help message
         * \param customShortUsageMsg - Replaces the default TCLAP
         * short usage default message. If Empty string, default is used.
         */
        CmdLine(const std::string& message,
                const char delimiter = ' ',
                const std::string& version = "none",
                bool helpAndVersion = true,
                const std::string& progName = "not set yet",
                const std::string& customShortUsageMsg = "");

        /**
         * Deletes any resources allocated by a CmdLine object.
         */
        virtual ~CmdLine();

        /**
         * Adds an argument to the list of arguments to be parsed.
         * \param a - Argument to be added.
         */
        void add( Arg& a );

        /**
         * An alternative add.  Functionally identical.
         * \param a - Argument to be added.
         */
        void add( Arg* a );

        /**
         * Add two Args that will be xor'd.  If this method is used, add does
         * not need to be called.
         * \param a - Argument to be added and xor'd.
         * \param b - Argument to be added and xor'd.
         */
        void xorAdd( Arg& a, Arg& b );

        /**
         * Add a list of Args that will be xor'd.  If this method is used,
         * add does not need to be called.
         * \param xors - List of Args to be added and xor'd.
         */
        void xorAdd( std::vector<Arg*>& xors );

        /**
         * Parses the command line.
         * \param argc - Number of arguments.
         * \param argv - Array of arguments.
         */
        void parse(int argc, const char * const * argv);

        /**
         * Parses the command line.
         * \param args - A vector of strings representing the args.
         * args[0] is still the program name.
         */
        void parse(std::vector<std::string>& args);

        /**
         *
         */
        CmdLineOutput* getOutput();

        /**
         *
         */
        void setOutput(CmdLineOutput* co);

        /**
         *
         */
        std::string& getVersion();

        /**
         *
         */
        std::string& getProgramName();

        /**
         *
         */
        std::list<Arg*>& getArgList();

        /**
         * get all args (no matter what category)
         */
        std::list<Arg*> getAllArgsList();

        /**
         *
         */
        XorHandler& getXorHandler();

        /**
         *
         */
        char getDelimiter();

        /**
         *
         */
        std::string& getMessage();

        /**
         *
         */
        bool hasHelpAndVersion();

        /**
         * Disables or enables CmdLine's internal parsing exception handling.
         *
         * @param state Should CmdLine handle parsing exceptions internally?
         */
        void setExceptionHandling(const bool state);

        /**
         * Returns the current state of the internal exception handling.
         *
         * @retval true Parsing exceptions are handled internally.
         * @retval false Parsing exceptions are propagated to the caller.
         */
        bool getExceptionHandling() const;

        /**
         * Allows the CmdLine object to be reused.
         */
        void reset();

        /**
         *
         */
        bool hasCustomShortUsage();

        /**
         *
         */
        const std::string&  getCustomShortUsageMessage();

};


///////////////////////////////////////////////////////////////////////////////
//Begin CmdLine.cpp
///////////////////////////////////////////////////////////////////////////////

inline CmdLine::CmdLine(const std::string& m,
                        char delim,
                        const std::string& v,
                        bool help,
                        const std::string& progName,
                        const std::string& customShortUsageMsg)
    :
  _argList(std::list<Arg*>()),
  _progName(progName),
  _message(m),
  _version(v),
  _numRequired(0),
  _delimiter(delim),
  _xorHandler(XorHandler()),
  _argDeleteOnExitList(std::list<Arg*>()),
  _visitorDeleteOnExitList(std::list<Visitor*>()),
  _output(0),
  _handleExceptions(true),
  _userSetOutput(false),
  _helpAndVersion(help),
  _customShortUsageMsg(customShortUsageMsg)
{
    _constructor();
}

inline CmdLine::~CmdLine()
{
    ClearContainer(_argDeleteOnExitList);
    ClearContainer(_visitorDeleteOnExitList);

    if ( !_userSetOutput )
    {
        delete _output;
        _output = 0;
    }
}

inline void CmdLine::_constructor()
{
    _output = new StdOutput;

    Arg::setDelimiter( _delimiter );

    Visitor* v;

    if ( _helpAndVersion )
    {
        v = new HelpVisitor( this, &_output );
        SwitchArg* help = new SwitchArg("h","help",
                                        "Displays usage information and exits.",
                                        false, REGULAR, v);
        add( help );
        deleteOnExit(help);
        deleteOnExit(v);

        v = new VersionVisitor( this, &_output );
        SwitchArg* vers = new SwitchArg("","version",
                                        "Displays version information and exits.",
                                        false, REGULAR, v);
        add( vers );
        deleteOnExit(vers);
        deleteOnExit(v);
    }

    v = new IgnoreRestVisitor();
    SwitchArg* ignore  = new SwitchArg(Arg::flagStartString(),
                                       Arg::ignoreNameString(),
                                       "Ignores the rest of the labeled arguments following this flag.",
                                       false, REGULAR, v);
    add( ignore );
    deleteOnExit(ignore);
    deleteOnExit(v);
}

inline void CmdLine::xorAdd( std::vector<Arg*>& ors )
{
    _xorHandler.add( ors );

    for (ArgVectorIterator it = ors.begin(); it != ors.end(); it++)
    {
        (*it)->forceRequired();
        (*it)->setRequireLabel( "OR required" );
        add( *it );
    }
}

inline void CmdLine::xorAdd( Arg& a, Arg& b )
{
    std::vector<Arg*> ors;
    ors.push_back( &a );
    ors.push_back( &b );
    xorAdd( ors );
}

inline void CmdLine::add( Arg& a )
{
    add( &a );
}

inline void CmdLine::add( Arg* a )
{
    for( ArgListIterator it = _argList.begin(); it != _argList.end(); it++ )
        if ( *a == *(*it) )
            throw( SpecificationException(
                    "Argument with same flag/name already exists!",
                    a->longID() ) );

    if (REGULAR == a->category())
        a->addToList(_argList);
    else if (DEVELOPER == a->category())
        a->addToList(_devArgList);

    if ( a->isRequired() )
        _numRequired++;
}


inline void CmdLine::parse(int argc, const char * const * argv)
{
        // this step is necessary so that we have easy access to
        // mutable strings.
        std::vector<std::string> args;
        for (int i = 0; i < argc; i++)
            args.push_back(argv[i]);

        parse(args);
}

inline void CmdLine::parse(std::vector<std::string>& args)
{
    bool shouldExit = false;
    int estat = 0;

    //if magic knob in gtpin args, add developer knobs to "active" knobs
    auto gtpinArgsEndItr = std::find(args.begin(), args.end(), "--");
    if (std::find(args.begin(), gtpinArgsEndItr, "--xyzzy") != args.end())
        _argList.splice(_argList.end(), _devArgList);

    try
    {
        int requiredCount = 0;

        for (int i = 0; static_cast<unsigned int>(i) < args.size(); i++) 
        {
            bool matched = false;
            for (ArgListIterator it = _argList.begin();
                 it != _argList.end(); it++)
            {
                if ( (*it)->processArg( &i, args ) )
                {
                    requiredCount += _xorHandler.check( *it );
                    matched = true;
                    break;
                }
            }

            // checks to see if the argument is an empty combined
            // switch and if so, then we've actually matched it
            if ( !matched && _emptyCombined( args[i] ) )
                matched = true;

            if ( !matched && !Arg::ignoreRest() )
                throw(CmdLineParseException("Couldn't find match "
                                            "for argument",
                                            args[i]));
        }

        if ( requiredCount < _numRequired )
            missingArgsException();

        if ( requiredCount > _numRequired )
            throw(CmdLineParseException("Too many arguments!"));

    }
    catch ( ArgException& e )
    {
        // If we're not handling the exceptions, rethrow.
        if ( !_handleExceptions)
        {
            throw;
        }

        try
        {
            _output->failure(*this,e);
        }
        catch ( ExitException &ee )
        {
            estat = ee.getExitStatus();
            shouldExit = true;
        }
    }
    catch (ExitException &ee)
    {
        // If we're not handling the exceptions, rethrow.
        if ( !_handleExceptions)
        {
            throw;
        }

        estat = ee.getExitStatus();
        shouldExit = true;
    }

    if (shouldExit)
        exit(estat);
}

inline bool CmdLine::_emptyCombined(const std::string& s)
{
    if ( s.length() > 0 && s[0] != Arg::flagStartChar() )
        return false;

    for ( int i = 1; static_cast<unsigned int>(i) < s.length(); i++ )
        if ( s[i] != Arg::blankChar() )
            return false;

    return true;
}

inline void CmdLine::missingArgsException()
{
        int count = 0;

        std::string missingArgList;
        for (ArgListIterator it = _argList.begin(); it != _argList.end(); it++)
        {
            if ( (*it)->isRequired() && !(*it)->isSet() )
            {
                missingArgList += (*it)->getName();
                missingArgList += ", ";
                count++;
            }
        }
        missingArgList = missingArgList.substr(0,missingArgList.length()-2);

        std::string msg;
        if ( count > 1 )
            msg = "Required arguments missing: ";
        else
            msg = "Required argument missing: ";

        msg += missingArgList;

        throw(CmdLineParseException(msg));
}

inline void CmdLine::deleteOnExit(Arg* ptr)
{
    _argDeleteOnExitList.push_back(ptr);
}

inline void CmdLine::deleteOnExit(Visitor* ptr)
{
    _visitorDeleteOnExitList.push_back(ptr);
}

inline CmdLineOutput* CmdLine::getOutput()
{
    return _output;
}

inline void CmdLine::setOutput(CmdLineOutput* co)
{
    if ( !_userSetOutput )
        delete _output;
    _userSetOutput = true;
    _output = co;
}

inline std::string& CmdLine::getVersion()
{
    return _version;
}

inline std::string& CmdLine::getProgramName()
{
    return _progName;
}

inline std::list<Arg*>& CmdLine::getArgList()
{
    return _argList;
}

inline std::list<Arg*> CmdLine::getAllArgsList()
{
    std::list<Arg*> retList = std::list<Arg*>(_argList);
    retList.insert(retList.end(), _devArgList.begin(), _devArgList.end());
    return retList;
}

inline XorHandler& CmdLine::getXorHandler()
{
    return _xorHandler;
}

inline char CmdLine::getDelimiter()
{
    return _delimiter;
}

inline std::string& CmdLine::getMessage()
{
    return _message;
}

inline bool CmdLine::hasHelpAndVersion()
{
    return _helpAndVersion;
}

inline void CmdLine::setExceptionHandling(const bool state)
{
    _handleExceptions = state;
}

inline bool CmdLine::getExceptionHandling() const
{
    return _handleExceptions;
}

inline void CmdLine::reset()
{
    for( ArgListIterator it = _argList.begin(); it != _argList.end(); it++ )
        (*it)->reset();
    
    _progName.clear();
}

inline bool CmdLine::hasCustomShortUsage()
{
    return _customShortUsageMsg != "";
}

inline const std::string& CmdLine::getCustomShortUsageMessage()
{
    return _customShortUsageMsg;
}

///////////////////////////////////////////////////////////////////////////////
//End CmdLine.cpp
///////////////////////////////////////////////////////////////////////////////



} //namespace TCLAP
#endif
