///////////////////////////////////////////////////////////////////////////////
// Name:        src/common/cmdline.cpp
// Purpose:     wxCmdLineParser implementation
// Author:      Vadim Zeitlin
// Created:     05.01.00
// Copyright:   (c) 2000 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
// Licence:     wxWindows licence
///////////////////////////////////////////////////////////////////////////////

// ============================================================================
// declarations
// ============================================================================

// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------

// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"


#ifndef WX_PRECOMP
    #include "wx/dynarray.h"
    #include "wx/string.h"
    #include "wx/log.h"
    #include "wx/intl.h"
    #include "wx/app.h"
#endif //WX_PRECOMP

#include "wx/cmdline.h"

#if wxUSE_CMDLINE_PARSER

#include <ctype.h>
#include <locale.h>             // for LC_ALL

#include "wx/datetime.h"
#include "wx/msgout.h"
#include "wx/filename.h"
#include "wx/apptrait.h"
#include "wx/scopeguard.h"

#include "wx/private/terminal.h"
#include "wx/private/wordwrap.h"

// ----------------------------------------------------------------------------
// private functions
// ----------------------------------------------------------------------------

static wxString GetTypeName(wxCmdLineParamType type);

static wxString GetOptionName(wxString::const_iterator p,
                              wxString::const_iterator end,
                              const wxChar *allowedChars);

static wxString GetShortOptionName(wxString::const_iterator p,
                                   wxString::const_iterator end);

static wxString GetLongOptionName(wxString::const_iterator p,
                                  wxString::const_iterator end);

// ----------------------------------------------------------------------------
// private structs
// ----------------------------------------------------------------------------


class wxCmdLineArgImpl: public wxCmdLineArg
{
public:
    wxCmdLineArgImpl(wxCmdLineEntryType k,
                 const wxString& shrt,
                 const wxString& lng,
                 wxCmdLineParamType typ);

    wxCmdLineArgImpl& SetDoubleVal(double val);
    wxCmdLineArgImpl& SetLongVal(long val);
    wxCmdLineArgImpl& SetStrVal(const wxString& val);
#if wxUSE_DATETIME
    wxCmdLineArgImpl& SetDateVal(const wxDateTime& val);
#endif // wxUSE_DATETIME

    bool HasValue() const { return m_hasVal; }
    wxCmdLineArgImpl& SetHasValue() { m_hasVal = true; return *this; }

    wxCmdLineArgImpl& SetNegated() { m_isNegated = true; return *this; }

    // Reset to the initial state, called before parsing another command line.
    void Reset()
    {
        m_hasVal =
        m_isNegated = false;
    }

public:
    wxCmdLineEntryType kind;
    wxString shortName,
             longName;
    wxCmdLineParamType type;

    // from wxCmdLineArg
    virtual wxCmdLineEntryType GetKind() const override { return kind; }
    virtual wxString GetShortName() const override {
        wxASSERT_MSG( kind == wxCMD_LINE_OPTION || kind == wxCMD_LINE_SWITCH,
                      wxT("kind mismatch in wxCmdLineArg") );
        return shortName;
    }
    virtual wxString GetLongName() const override {
        wxASSERT_MSG( kind == wxCMD_LINE_OPTION || kind == wxCMD_LINE_SWITCH,
                      wxT("kind mismatch in wxCmdLineArg") );
        return longName;
    }
    virtual wxCmdLineParamType GetType() const override {
        wxASSERT_MSG( kind == wxCMD_LINE_OPTION,
                      wxT("kind mismatch in wxCmdLineArg") );
        return type;
    }
    double GetDoubleVal() const override;
    long GetLongVal() const override;
    const wxString& GetStrVal() const override;
#if wxUSE_DATETIME
    const wxDateTime& GetDateVal() const override;
#endif // wxUSE_DATETIME
    bool IsNegated() const override {
        wxASSERT_MSG( kind == wxCMD_LINE_SWITCH,
                      wxT("kind mismatch in wxCmdLineArg") );
        return m_isNegated;
    }

private:
    // can't use union easily here, so just store all possible data fields, we
    // don't waste much (might still use union later if the number of supported
    // types increases)

    void Check(wxCmdLineParamType typ) const;

    bool m_hasVal;
    bool m_isNegated;

    double m_doubleVal;
    long m_longVal;
    wxString m_strVal;
#if wxUSE_DATETIME
    wxDateTime m_dateVal;
#endif // wxUSE_DATETIME

};

// an internal representation of an option
struct wxCmdLineOption: public wxCmdLineArgImpl
{
    wxCmdLineOption(wxCmdLineEntryType k,
                    const wxString& shrt,
                    const wxString& lng,
                    const wxString& desc,
                    wxCmdLineParamType typ,
                    int fl)
                    : wxCmdLineArgImpl(k, shrt, lng, typ)
        , description(desc)
    {
        flags = fl;
    }

    wxString description;
    int flags;
};

struct wxCmdLineParam
{
    wxCmdLineParam(const wxString& desc,
                   wxCmdLineParamType typ,
                   int fl)
        : description(desc)
    {
        type = typ;
        flags = fl;
    }

    wxString description;
    wxCmdLineParamType type;
    int flags;
};

// the parser internal state
struct wxCmdLineParserData
{
    // options
    wxString m_switchChars;     // characters which may start an option
    bool m_enableLongOptions;   // true if long options are enabled
    wxString m_logo;            // some extra text to show in Usage()
    wxString m_synopsis;        // the program synopsis or empty to generate it

    // cmd line data
    wxArrayString m_arguments;  // == argv, argc == m_arguments.size()
    std::vector<wxCmdLineOption> m_options;   // all possible options and switches
    std::vector<wxCmdLineParam> m_paramDesc;  // description of all possible params
    wxArrayString m_parameters; // all params found
    std::vector<wxCmdLineArgImpl> m_parsedArguments; // all options and parameters in parsing order

    // methods
    wxCmdLineParserData();
    void SetArguments(int argc, char **argv);
    void SetArguments(int argc, wxChar **argv);
    void SetArguments(int argc, const wxCmdLineArgsArray& argv);
    void SetArguments(const wxString& cmdline);

    int FindOption(const wxString& name);
    int FindOptionByLongName(const wxString& name);

    // Find the option by either its short or long name.
    //
    // Asserts and returns nullptr if option with this name is not found.
    const wxCmdLineOption* FindOptionByAnyName(const wxString& name);
};

// ============================================================================
// implementation
// ============================================================================

// ----------------------------------------------------------------------------
// wxCmdLineArg
// ----------------------------------------------------------------------------

wxCmdLineArgImpl::wxCmdLineArgImpl(wxCmdLineEntryType k,
                const wxString& shrt,
                const wxString& lng,
                wxCmdLineParamType typ)
{
    // wxCMD_LINE_USAGE_TEXT uses only description, shortName and longName is empty
    if ( k != wxCMD_LINE_USAGE_TEXT && k != wxCMD_LINE_PARAM)
    {
        wxASSERT_MSG
        (
            !shrt.empty() || !lng.empty(),
                wxT("option should have at least one name")
        );

        wxASSERT_MSG
        (
            GetShortOptionName(shrt.begin(), shrt.end()).Len() == shrt.Len(),
            wxT("Short option contains invalid characters")
        );

        wxASSERT_MSG
        (
            GetLongOptionName(lng.begin(), lng.end()).Len() == lng.Len(),
            wxT("Long option contains invalid characters")
        );
    }

    kind = k;

    shortName = shrt;
    longName = lng;

    type = typ;

    Reset();
}

void wxCmdLineArgImpl::Check(wxCmdLineParamType WXUNUSED_UNLESS_DEBUG(typ)) const
{
    // NB: Type is always wxCMD_LINE_VAL_NONE for booleans, so mismatch between
    //  switches / options / params is well checked by this test
    // The parameters have type == wxCMD_LINE_VAL_STRING and thus can be
    //  retrieved only by GetStrVal()
    wxASSERT_MSG( type == typ, wxT("type mismatch in wxCmdLineArg") );
}

double wxCmdLineArgImpl::GetDoubleVal() const
{
    Check(wxCMD_LINE_VAL_DOUBLE);
    return m_doubleVal;
}

long wxCmdLineArgImpl::GetLongVal() const
{
    Check(wxCMD_LINE_VAL_NUMBER);
    return m_longVal;
}

const wxString& wxCmdLineArgImpl::GetStrVal() const
{
    Check(wxCMD_LINE_VAL_STRING);
    return m_strVal;
}

#if wxUSE_DATETIME
const wxDateTime& wxCmdLineArgImpl::GetDateVal() const
{
    Check(wxCMD_LINE_VAL_DATE);
    return m_dateVal;
}
#endif // wxUSE_DATETIME

wxCmdLineArgImpl& wxCmdLineArgImpl::SetDoubleVal(double val)
{
    Check(wxCMD_LINE_VAL_DOUBLE);
    m_doubleVal = val;
    m_hasVal = true;
    return *this;
}

wxCmdLineArgImpl& wxCmdLineArgImpl::SetLongVal(long val)
{
    Check(wxCMD_LINE_VAL_NUMBER);
    m_longVal = val;
    m_hasVal = true;
    return *this;
}

wxCmdLineArgImpl& wxCmdLineArgImpl::SetStrVal(const wxString& val)
{
    Check(wxCMD_LINE_VAL_STRING);
    m_strVal = val;
    m_hasVal = true;
    return *this;
}

#if wxUSE_DATETIME
wxCmdLineArgImpl& wxCmdLineArgImpl::SetDateVal(const wxDateTime& val)
{
    Check(wxCMD_LINE_VAL_DATE);
    m_dateVal = val;
    m_hasVal = true;
    return *this;
}
#endif // wxUSE_DATETIME

// ----------------------------------------------------------------------------
// wxCmdLineArgsArrayRef
// ----------------------------------------------------------------------------

size_t wxCmdLineArgs::size() const
{
    return m_parser.m_data->m_parsedArguments.size();
}

// ----------------------------------------------------------------------------
// wxCmdLineArgsArrayRef::const_iterator
// ----------------------------------------------------------------------------

wxCmdLineArgs::const_iterator::reference
    wxCmdLineArgs::const_iterator::operator *() const
{
    return m_parser->m_data->m_parsedArguments[m_index];
}

wxCmdLineArgs::const_iterator::pointer
    wxCmdLineArgs::const_iterator::operator ->() const
{
    return &**this;
}

wxCmdLineArgs::const_iterator &wxCmdLineArgs::const_iterator::operator ++ ()
{
    ++m_index;
    return *this;
}

wxCmdLineArgs::const_iterator wxCmdLineArgs::const_iterator::operator ++ (int)
{
    wxCmdLineArgs::const_iterator tmp(*this);
    ++*this;
    return tmp;
}

wxCmdLineArgs::const_iterator &wxCmdLineArgs::const_iterator::operator -- ()
{
    --m_index;
    return *this;
}

wxCmdLineArgs::const_iterator wxCmdLineArgs::const_iterator::operator -- (int)
{
    wxCmdLineArgs::const_iterator tmp(*this);
    --*this;
    return tmp;
}

// ----------------------------------------------------------------------------
// wxCmdLineParserData
// ----------------------------------------------------------------------------

wxCmdLineParserData::wxCmdLineParserData()
#ifdef __UNIX_LIKE__
    : m_switchChars("-")
#else // !Unix
    : m_switchChars("/-")
#endif
{
    m_enableLongOptions = true;
}

namespace
{

// Small helper function setting locale for all categories.
//
// We define it because wxSetlocale() can't be easily used with wxScopeGuard as
// it has several overloads -- while this one can.
inline char *SetAllLocaleFacets(const char *loc)
{
    return wxSetlocale(LC_ALL, loc);
}

} // private namespace

void wxCmdLineParserData::SetArguments(int argc, char **argv)
{
    m_arguments.clear();

    // Command-line arguments are supposed to be in the user locale encoding
    // (what else?) but wxLocale probably wasn't initialized yet as we're
    // called early during the program startup and so our locale might not have
    // been set from the environment yet. To work around this problem we
    // temporarily change the locale here. The only drawback is that changing
    // the locale is thread-unsafe but precisely because we're called so early
    // it's hopefully safe to assume that no other threads had been created yet.
    const wxCharBuffer locOld(SetAllLocaleFacets(nullptr));
    SetAllLocaleFacets("");
    wxON_BLOCK_EXIT1( SetAllLocaleFacets, locOld.data() );

    for ( int n = 0; n < argc; n++ )
    {
        // try to interpret the string as being in the current locale
        wxString arg(argv[n]);

        // but just in case we guessed wrongly and the conversion failed, do
        // try to salvage at least something
        if ( arg.empty() && argv[n][0] != '\0' )
            arg = wxString(argv[n], wxConvISO8859_1);

        m_arguments.push_back(arg);
    }
}

void wxCmdLineParserData::SetArguments(int argc, wxChar **argv)
{
    m_arguments.clear();

    for ( int n = 0; n < argc; n++ )
    {
        m_arguments.push_back(argv[n]);
    }
}

void wxCmdLineParserData::SetArguments(int WXUNUSED(argc),
                                       const wxCmdLineArgsArray& argv)
{
    m_arguments = argv.GetArguments();
}

void wxCmdLineParserData::SetArguments(const wxString& cmdLine)
{
    m_arguments.clear();

    if(wxTheApp && wxTheApp->argc > 0)
        m_arguments.push_back(wxTheApp->argv[0]);
    else
        m_arguments.push_back(wxEmptyString);

    wxArrayString args = wxCmdLineParser::ConvertStringToArgs(cmdLine);

    WX_APPEND_ARRAY(m_arguments, args);
}

int wxCmdLineParserData::FindOption(const wxString& name)
{
    if ( !name.empty() )
    {
        size_t count = m_options.size();
        for ( size_t n = 0; n < count; n++ )
        {
            if ( m_options[n].shortName == name )
            {
                // found
                return n;
            }
        }
    }

    return wxNOT_FOUND;
}

int wxCmdLineParserData::FindOptionByLongName(const wxString& name)
{
    size_t count = m_options.size();
    for ( size_t n = 0; n < count; n++ )
    {
        if ( m_options[n].longName == name )
        {
            // found
            return n;
        }
    }

    return wxNOT_FOUND;
}

const wxCmdLineOption*
wxCmdLineParserData::FindOptionByAnyName(const wxString& name)
{
    int i = FindOption(name);
    if ( i == wxNOT_FOUND )
    {
        i = FindOptionByLongName(name);

        if ( i == wxNOT_FOUND )
        {
            wxFAIL_MSG( wxS("Unknown option ") + name );
            return nullptr;
        }
    }

    return &m_options[(size_t)i];
}

// ----------------------------------------------------------------------------
// construction and destruction
// ----------------------------------------------------------------------------

void wxCmdLineParser::Init()
{
    m_data = new wxCmdLineParserData;
}

void wxCmdLineParser::SetCmdLine(int argc, char **argv)
{
    m_data->SetArguments(argc, argv);
}

void wxCmdLineParser::SetCmdLine(int argc, wxChar **argv)
{
    m_data->SetArguments(argc, argv);
}

void wxCmdLineParser::SetCmdLine(int argc, const wxCmdLineArgsArray& argv)
{
    m_data->SetArguments(argc, argv);
}

void wxCmdLineParser::SetCmdLine(const wxString& cmdline)
{
    m_data->SetArguments(cmdline);
}

wxCmdLineParser::~wxCmdLineParser()
{
    delete m_data;
}

// ----------------------------------------------------------------------------
// options
// ----------------------------------------------------------------------------

void wxCmdLineParser::SetSwitchChars(const wxString& switchChars)
{
    m_data->m_switchChars = switchChars;
}

void wxCmdLineParser::EnableLongOptions(bool enable)
{
    m_data->m_enableLongOptions = enable;
}

bool wxCmdLineParser::AreLongOptionsEnabled() const
{
    return m_data->m_enableLongOptions;
}

void wxCmdLineParser::SetLogo(const wxString& logo)
{
    m_data->m_logo = logo;
}

void wxCmdLineParser::SetUsageSynopsis(const wxString& synopsis)
{
    m_data->m_synopsis = synopsis;
}

// ----------------------------------------------------------------------------
// command line construction
// ----------------------------------------------------------------------------

void wxCmdLineParser::SetDesc(const wxCmdLineEntryDesc *desc)
{
    for ( ;; desc++ )
    {
        switch ( desc->kind )
        {
            case wxCMD_LINE_SWITCH:
                AddSwitch(desc->shortName, desc->longName,
                          wxGetTranslation(desc->description),
                          desc->flags);
                break;

            case wxCMD_LINE_OPTION:
                AddOption(desc->shortName, desc->longName,
                          wxGetTranslation(desc->description),
                          desc->type, desc->flags);
                break;

            case wxCMD_LINE_PARAM:
                AddParam(wxGetTranslation(desc->description),
                         desc->type, desc->flags);
                break;

            case wxCMD_LINE_USAGE_TEXT:
                AddUsageText(wxGetTranslation(desc->description));
                break;

            default:
                wxFAIL_MSG( wxT("unknown command line entry type") );
                wxFALLTHROUGH;

            case wxCMD_LINE_NONE:
                return;
        }
    }
}

void wxCmdLineParser::AddSwitch(const wxString& shortName,
                                const wxString& longName,
                                const wxString& desc,
                                int flags)
{
    wxASSERT_MSG( m_data->FindOption(shortName) == wxNOT_FOUND,
                  wxT("duplicate switch") );

    m_data->m_options.emplace_back(wxCMD_LINE_SWITCH,
                                   shortName, longName, desc,
                                   wxCMD_LINE_VAL_NONE, flags);
}

void wxCmdLineParser::AddOption(const wxString& shortName,
                                const wxString& longName,
                                const wxString& desc,
                                wxCmdLineParamType type,
                                int flags)
{
    wxASSERT_MSG( m_data->FindOption(shortName) == wxNOT_FOUND,
                  wxT("duplicate option") );

    m_data->m_options.emplace_back(wxCMD_LINE_OPTION,
                                   shortName, longName, desc,
                                   type, flags);
}

void wxCmdLineParser::AddParam(const wxString& desc,
                               wxCmdLineParamType type,
                               int flags)
{
    // do some consistency checks: a required parameter can't follow an
    // optional one and nothing should follow a parameter with MULTIPLE flag
#if wxDEBUG_LEVEL
    if ( !m_data->m_paramDesc.empty() )
    {
        wxCmdLineParam& param = *m_data->m_paramDesc.rbegin();

        wxASSERT_MSG( !(param.flags & wxCMD_LINE_PARAM_MULTIPLE),
                      wxT("all parameters after the one with wxCMD_LINE_PARAM_MULTIPLE style will be ignored") );

        if ( !(flags & wxCMD_LINE_PARAM_OPTIONAL) )
        {
            wxASSERT_MSG( !(param.flags & wxCMD_LINE_PARAM_OPTIONAL),
                          wxT("a required parameter can't follow an optional one") );
        }
    }
#endif // wxDEBUG_LEVEL

    m_data->m_paramDesc.emplace_back(desc, type, flags);
}

void wxCmdLineParser::AddUsageText(const wxString& text)
{
    wxASSERT_MSG( !text.empty(), wxT("text can't be empty") );

    m_data->m_options.emplace_back(wxCMD_LINE_USAGE_TEXT,
                                   wxEmptyString, wxEmptyString,
                                   text, wxCMD_LINE_VAL_NONE, 0);
}

// ----------------------------------------------------------------------------
// access to parse command line
// ----------------------------------------------------------------------------

bool wxCmdLineParser::Found(const wxString& name) const
{
    const wxCmdLineOption* const opt = m_data->FindOptionByAnyName(name);

    return opt && opt->HasValue();
}

wxCmdLineSwitchState wxCmdLineParser::FoundSwitch(const wxString& name) const
{
    const wxCmdLineOption* const opt = m_data->FindOptionByAnyName(name);

    if ( !opt || !opt->HasValue() )
        return wxCMD_SWITCH_NOT_FOUND;

    return opt->IsNegated() ? wxCMD_SWITCH_OFF : wxCMD_SWITCH_ON;
}

bool wxCmdLineParser::Found(const wxString& name, wxString *value) const
{
    const wxCmdLineOption* const opt = m_data->FindOptionByAnyName(name);

    if ( !opt || !opt->HasValue() )
        return false;

    wxCHECK_MSG( value, false, wxT("null pointer in wxCmdLineOption::Found") );

    *value = opt->GetStrVal();

    return true;
}

bool wxCmdLineParser::Found(const wxString& name, long *value) const
{
    const wxCmdLineOption* const opt = m_data->FindOptionByAnyName(name);

    if ( !opt || !opt->HasValue() )
        return false;

    wxCHECK_MSG( value, false, wxT("null pointer in wxCmdLineOption::Found") );

    *value = opt->GetLongVal();

    return true;
}

bool wxCmdLineParser::Found(const wxString& name, double *value) const
{
    const wxCmdLineOption* const opt = m_data->FindOptionByAnyName(name);

    if ( !opt || !opt->HasValue() )
        return false;

    wxCHECK_MSG( value, false, wxT("null pointer in wxCmdLineOption::Found") );

    *value = opt->GetDoubleVal();

    return true;
}

#if wxUSE_DATETIME
bool wxCmdLineParser::Found(const wxString& name, wxDateTime *value) const
{
    const wxCmdLineOption* const opt = m_data->FindOptionByAnyName(name);

    if ( !opt || !opt->HasValue() )
        return false;

    wxCHECK_MSG( value, false, wxT("null pointer in wxCmdLineOption::Found") );

    *value = opt->GetDateVal();

    return true;
}
#endif // wxUSE_DATETIME

size_t wxCmdLineParser::GetParamCount() const
{
    return m_data->m_parameters.size();
}

wxString wxCmdLineParser::GetParam(size_t n) const
{
    wxCHECK_MSG( n < GetParamCount(), wxEmptyString, wxT("invalid param index") );

    return m_data->m_parameters[n];
}

// Resets switches and options
void wxCmdLineParser::Reset()
{
    for ( auto& opt : m_data->m_options )
    {
        opt.Reset();
    }

    m_data->m_parsedArguments.clear();
}


// ----------------------------------------------------------------------------
// the real work is done here
// ----------------------------------------------------------------------------

int wxCmdLineParser::Parse(bool showUsage, int wrapColumn)
{
    bool maybeOption = true;    // can the following arg be an option?
    bool ok = true;             // true until an error is detected
    bool helpRequested = false; // true if "-h" was given
    bool hadRepeatableParam = false; // true if found param with MULTIPLE flag

    size_t currentParam = 0;    // the index in m_paramDesc

    size_t countParam = m_data->m_paramDesc.size();
    wxString errorMsg;

    Reset();

    // parse everything
    m_data->m_parameters.clear();
    wxString arg;
    size_t count = m_data->m_arguments.size();
    for ( size_t n = 1; ok && (n < count); n++ )    // 0 is program name
    {
        arg = m_data->m_arguments[n];

        // special case: "--" should be discarded and all following arguments
        // should be considered as parameters, even if they start with '-' and
        // not like options (this is POSIX-like)
        if ( arg == wxT("--") )
        {
            maybeOption = false;

            continue;
        }
#ifdef __WXOSX__
        if ( arg == wxS("-ApplePersistenceIgnoreState") ||
             arg == wxS("-AppleTextDirection") ||
             arg == wxS("-AppleLocale") ||
             arg == wxS("-AppleLanguages") )
        {
            maybeOption = false;
            n++;

            continue;
        }
#endif

        // empty argument or just '-' is not an option but a parameter
        if ( maybeOption && arg.length() > 1 &&
                wxStrchr(m_data->m_switchChars.wc_str(), arg[0u]) )
        {
            bool isLong;
            wxString name;
            int optInd = wxNOT_FOUND;   // init to suppress warnings

            // an option or a switch: find whether it's a long or a short one
            if ( arg.length() >= 3 && arg[0u] == wxT('-') && arg[1u] == wxT('-') )
            {
                // a long one
                isLong = true;

                // Skip leading "--"
                wxString::const_iterator p = arg.begin() + 2;

                bool longOptionsEnabled = AreLongOptionsEnabled();

                name = GetLongOptionName(p, arg.end());

                if (longOptionsEnabled)
                {
                    wxString errorOpt;

                    optInd = m_data->FindOptionByLongName(name);
                    if ( optInd == wxNOT_FOUND )
                    {
                        // Check if this could be a negatable long option.
                        if ( name.Last() == '-' )
                        {
                            name.RemoveLast();

                            optInd = m_data->FindOptionByLongName(name);
                            if ( optInd != wxNOT_FOUND )
                            {
                                if ( !(m_data->m_options[optInd].flags &
                                        wxCMD_LINE_SWITCH_NEGATABLE) )
                                {
                                    errorOpt.Printf
                                             (
                                              _("Option '%s' can't be negated"),
                                              name
                                             );
                                    optInd = wxNOT_FOUND;
                                }
                            }
                        }

                        if ( optInd == wxNOT_FOUND )
                        {
                            if ( errorOpt.empty() )
                            {
                                errorOpt.Printf
                                         (
                                          _("Unknown long option '%s'"),
                                          name
                                         );
                            }

                            errorMsg << errorOpt << wxT('\n');
                        }
                    }
                }
                else
                {
                    optInd = wxNOT_FOUND; // Sanity check

                    // Print the argument including leading "--"
                    name.Prepend( wxT("--") );
                    errorMsg << wxString::Format(_("Unknown option '%s'"), name.c_str())
                             << wxT('\n');
                }

            }
            else // not a long option
            {
                isLong = false;

                // a short one: as they can be cumulated, we try to find the
                // longest substring which is a valid option
                wxString::const_iterator p = arg.begin() + 1;

                name = GetShortOptionName(p, arg.end());

                size_t len = name.length();
                do
                {
                    if ( len == 0 )
                    {
                        // we couldn't find a valid option name in the
                        // beginning of this string
                        errorMsg << wxString::Format(_("Unknown option '%s'"), name.c_str())
                                 << wxT('\n');

                        break;
                    }
                    else
                    {
                        optInd = m_data->FindOption(name.Left(len));

                        // will try with one character less the next time
                        len--;
                    }
                }
                while ( optInd == wxNOT_FOUND );

                len++;  // compensates extra len-- above
                if ( (optInd != wxNOT_FOUND) && (len != name.length()) )
                {
                    // first of all, the option name is only part of this
                    // string
                    name = name.Left(len);

                    // our option is only part of this argument, there is
                    // something else in it - it is either the value of this
                    // option or other switches if it is a switch
                    if ( m_data->m_options[(size_t)optInd].kind
                            == wxCMD_LINE_SWITCH )
                    {
                        // if the switch is negatable and it is just followed
                        // by '-' the '-' is considered to be part of this
                        // switch
                        if ( (m_data->m_options[(size_t)optInd].flags &
                                    wxCMD_LINE_SWITCH_NEGATABLE) &&
                                arg[len] == '-' )
                            ++len;

                        // pretend that all the rest of the argument is the
                        // next argument, in fact
                        wxString arg2 = arg[0u];
                        arg2 += arg.Mid(len + 1); // +1 for leading '-'

                        m_data->m_arguments.insert
                            (m_data->m_arguments.begin() + n + 1, arg2);
                        count++;

                        // only leave the part which wasn't extracted into the
                        // next argument in this one
                        arg = arg.Left(len + 1);
                    }
                    //else: it's our value, we'll deal with it below
                }
            }

            if ( optInd == wxNOT_FOUND )
            {
                ok = false;

                continue;   // will break, in fact
            }

            // look at what follows:

            // +1 for leading '-'
            wxString::const_iterator p = arg.begin() + 1 + name.length();
            wxString::const_iterator end = arg.end();

            if ( isLong )
                ++p;    // for another leading '-'

            wxCmdLineOption& opt = m_data->m_options[(size_t)optInd];
            if ( opt.kind == wxCMD_LINE_SWITCH )
            {
                // we must check that there is no value following the switch
                bool negated = (opt.flags & wxCMD_LINE_SWITCH_NEGATABLE) &&
                                    p != arg.end() && *p == '-';

                if ( !negated && p != arg.end() )
                {
                    errorMsg << wxString::Format(_("Unexpected characters following option '%s'."), name.c_str())
                             << wxT('\n');
                    ok = false;
                }
                else // no value, as expected
                {
                    // nothing more to do
                    opt.SetHasValue();
                    if ( negated )
                        opt.SetNegated();

                    if ( opt.flags & wxCMD_LINE_OPTION_HELP )
                    {
                        helpRequested = true;

                        // it's not an error, but we still stop here
                        ok = false;
                    }
                }
            }
            else // it's an option. not a switch
            {
                switch ( p == end ? '\0' : (*p).GetValue() )
                {
                    case '=':
                    case ':':
                        // the value follows
                        ++p;
                        break;

                    case '\0':
                        // the value is in the next argument
                        if ( ++n == count )
                        {
                            // ... but there is none
                            errorMsg << wxString::Format(_("Option '%s' requires a value."),
                                                         name.c_str())
                                     << wxT('\n');

                            ok = false;
                        }
                        else
                        {
                            // ... take it from there
                            p = m_data->m_arguments[n].begin();
                            end = m_data->m_arguments[n].end();
                        }
                        break;

                    default:
                        // the value is right here: this may be legal or
                        // not depending on the option style
                        if ( opt.flags & wxCMD_LINE_NEEDS_SEPARATOR )
                        {
                            errorMsg << wxString::Format(_("Separator expected after the option '%s'."),
                                                         name.c_str())
                                    << wxT('\n');

                            ok = false;
                        }
                }

                if ( ok )
                {
                    wxString value(p, end);
                    switch ( opt.type )
                    {
                        default:
                            wxFAIL_MSG( wxT("unknown option type") );
                            wxFALLTHROUGH;

                        case wxCMD_LINE_VAL_STRING:
                            opt.SetStrVal(value);
                            break;

                        case wxCMD_LINE_VAL_NUMBER:
                            {
                                long val;
                                if ( value.ToLong(&val) )
                                {
                                    opt.SetLongVal(val);
                                }
                                else
                                {
                                    errorMsg << wxString::Format(_("'%s' is not a correct numeric value for option '%s'."),
                                                                 value.c_str(), name.c_str())
                                             << wxT('\n');

                                    ok = false;
                                }
                            }
                            break;

                        case wxCMD_LINE_VAL_DOUBLE:
                            {
                                double val;
                                if ( value.ToDouble(&val) )
                                {
                                    opt.SetDoubleVal(val);
                                }
                                else
                                {
                                    errorMsg << wxString::Format(_("'%s' is not a correct numeric value for option '%s'."),
                                                                 value.c_str(), name.c_str())
                                             << wxT('\n');

                                    ok = false;
                                }
                            }
                            break;

#if wxUSE_DATETIME
                        case wxCMD_LINE_VAL_DATE:
                            {
                                wxDateTime dt;
                                wxString::const_iterator endDate;
                                if ( !dt.ParseDate(value, &endDate) || endDate != value.end() )
                                {
                                    errorMsg << wxString::Format(_("Option '%s': '%s' cannot be converted to a date."),
                                                                 name.c_str(), value.c_str())
                                             << wxT('\n');

                                    ok = false;
                                }
                                else
                                {
                                    opt.SetDateVal(dt);
                                }
                            }
                            break;
#endif // wxUSE_DATETIME
                    }
                }
            }

            if (ok)
                m_data->m_parsedArguments.push_back (opt);
        }
        else // not an option, must be a parameter
        {
            if ( currentParam < countParam )
            {
                wxCmdLineParam& param = m_data->m_paramDesc[currentParam];

                // TODO check the param type

                m_data->m_parameters.push_back(arg);
                m_data->m_parsedArguments.push_back (
                    wxCmdLineArgImpl(wxCMD_LINE_PARAM, wxString(), wxString(),
                                     wxCMD_LINE_VAL_STRING).SetStrVal(arg));

                if ( !(param.flags & wxCMD_LINE_PARAM_MULTIPLE) )
                {
                    currentParam++;
                }
                else
                {
                    wxASSERT_MSG( currentParam == countParam - 1,
                                  wxT("all parameters after the one with wxCMD_LINE_PARAM_MULTIPLE style are ignored") );

                    // remember that we did have this last repeatable parameter
                    hadRepeatableParam = true;
                }
            }
            else
            {
                errorMsg << wxString::Format(_("Unexpected parameter '%s'"), arg.c_str())
                         << wxT('\n');

                ok = false;
            }
        }
    }

    // verify that all mandatory options were given
    if ( ok )
    {
        size_t countOpt = m_data->m_options.size();
        for ( size_t n = 0; ok && (n < countOpt); n++ )
        {
            wxCmdLineOption& opt = m_data->m_options[n];
            if ( (opt.flags & wxCMD_LINE_OPTION_MANDATORY) && !opt.HasValue() )
            {
                wxString optName;
                if ( opt.longName.empty() )
                {
                    optName = opt.shortName;
                }
                else
                {
                    if ( AreLongOptionsEnabled() )
                    {
                        optName.Printf(// TRANSLATORS: Short name and long name for a command line option
                                        _("%s (or %s)"),
                                        opt.shortName.c_str(),
                                        opt.longName.c_str() );
                    }
                    else
                    {
                        optName.Printf( wxT("%s"),
                                        opt.shortName.c_str() );
                    }
                }

                errorMsg << wxString::Format(_("The value for the option '%s' must be specified."),
                                             optName.c_str())
                         << wxT('\n');

                ok = false;
            }
        }

        for ( ; ok && (currentParam < countParam); currentParam++ )
        {
            wxCmdLineParam& param = m_data->m_paramDesc[currentParam];
            if ( (currentParam == countParam - 1) &&
                 (param.flags & wxCMD_LINE_PARAM_MULTIPLE) &&
                 hadRepeatableParam )
            {
                // special case: currentParam wasn't incremented, but we did
                // have it, so don't give error
                continue;
            }

            if ( !(param.flags & wxCMD_LINE_PARAM_OPTIONAL) )
            {
                errorMsg << wxString::Format(_("The required parameter '%s' was not specified."),
                                             param.description.c_str())
                         << wxT('\n');

                ok = false;
            }
        }
    }

    // if there was an error during parsing the command line, show this error
    // and also the usage message if it had been requested
    if ( !ok && (!errorMsg.empty() || (helpRequested && showUsage)) )
    {
        if ( showUsage )
            Usage(wrapColumn);

        wxSafeMessageOutput(errorMsg);
    }

    return ok ? 0 : helpRequested ? -1 : 1;
}

// ----------------------------------------------------------------------------
// give the usage message
// ----------------------------------------------------------------------------

void wxCmdLineParser::Usage(int wrapColumn) const
{
    wxSafeMessageOutput(GetUsageString(wrapColumn));
}

wxString wxCmdLineParser::GetUsageString(int wrapColumn) const
{
    wxString appname;
    if ( m_data->m_arguments.empty() )
    {
        if ( wxTheApp )
            appname = wxTheApp->GetAppName();
    }
    else // use argv[0]
    {
        appname = wxFileName(m_data->m_arguments[0]).GetName();
    }

    // we construct the brief cmd line desc on the fly, but not the detailed
    // help message below because we want to align the options descriptions
    // and for this we must first know the longest one of them
    wxString usage;
    wxArrayString namesOptions, descOptions;

    if ( !m_data->m_logo.empty() )
    {
        usage << m_data->m_logo << wxT('\n');
    }

    // Do we need to generate synopsis or were we already given one?
    const bool needSynopsis = m_data->m_synopsis.empty();

    if ( needSynopsis )
        usage << wxString::Format(_("Usage: %s"), appname.c_str());

    // the switch char is usually '-' but this can be changed with
    // SetSwitchChars() and then the first one of possible chars is used
    wxChar chSwitch = m_data->m_switchChars.empty() ? wxT('-')
                                                    : m_data->m_switchChars[0u];

    // Note that we need to execute this loop even if we don't need the
    // synopsis because it also fills namesOptions that we use for the detailed
    // description below.
    bool areLongOptionsEnabled = AreLongOptionsEnabled();
    for ( const auto& opt : m_data->m_options )
    {
        wxString option, negator;

        if ( opt.flags & wxCMD_LINE_HIDDEN )
            continue;

        if ( opt.kind != wxCMD_LINE_USAGE_TEXT )
        {
            if ( needSynopsis )
                usage << wxT(' ');
            if ( !(opt.flags & wxCMD_LINE_OPTION_MANDATORY) )
            {
                if ( needSynopsis )
                    usage << wxT('[');
            }

            if ( opt.flags & wxCMD_LINE_SWITCH_NEGATABLE )
                negator = wxT("[-]");

            if ( !opt.shortName.empty() )
            {
                if ( needSynopsis )
                    usage << chSwitch << opt.shortName << negator;
            }
            else if ( areLongOptionsEnabled && !opt.longName.empty() )
            {
                if ( needSynopsis )
                    usage << wxT("--") << opt.longName << negator;
            }
            else
            {
                if (!opt.longName.empty())
                {
                    wxFAIL_MSG( wxT("option with only a long name while long ")
                                wxT("options are disabled") );
                }
                else
                {
                    wxFAIL_MSG( wxT("option without either short or long name") );
                }
            }

            if ( !opt.shortName.empty() )
            {
                option << wxT("  ") << chSwitch << opt.shortName;
            }

            if ( areLongOptionsEnabled && !opt.longName.empty() )
            {
                option << (option.empty() ? wxT("  ") : wxT(", "))
                       << wxT("--") << opt.longName;
            }

            if ( opt.kind != wxCMD_LINE_SWITCH )
            {
                wxString val;
                val << wxT('<') << GetTypeName(opt.type) << wxT('>');
                if ( needSynopsis )
                    usage << wxT(' ') << val;
                option << (opt.longName.empty() ? wxT(':') : wxT('=')) << val;
            }

            if ( !(opt.flags & wxCMD_LINE_OPTION_MANDATORY) )
            {
                if ( needSynopsis )
                    usage << wxT(']');
            }
        }

        namesOptions.push_back(option);
        descOptions.push_back(opt.description);
    }

    if ( needSynopsis )
    {
        for ( const auto& param : m_data->m_paramDesc )
        {
            if ( param.flags & wxCMD_LINE_HIDDEN )
                continue;

            usage << wxT(' ');
            if ( param.flags & wxCMD_LINE_PARAM_OPTIONAL )
            {
                usage << wxT('[');
            }

            usage << param.description;

            if ( param.flags & wxCMD_LINE_PARAM_MULTIPLE )
            {
                usage << wxT("...");
            }

            if ( param.flags & wxCMD_LINE_PARAM_OPTIONAL )
            {
                usage << wxT(']');
            }
        }
    }

    // If we didn't output the synopsis above, do it now.
    if ( !needSynopsis )
        usage << m_data->m_synopsis;
    usage << wxT('\n');

    // set to number of our own options, not counting the standard ones
    const size_t count = namesOptions.size();

    // get option names & descriptions for standard options, if any:
    wxString stdDesc;
    if ( wxAppTraits *traits = wxApp::GetTraitsIfExists() )
        stdDesc = traits->GetStandardCmdLineOptions(namesOptions, descOptions);

    // now construct the detailed help message
    size_t lenMax = 0;
    for ( const auto& name : namesOptions )
    {
        size_t len = name.length();
        if ( len > lenMax )
            lenMax = len;
    }

    // Leave some space between the options and their descriptions.
    constexpr int MARGIN = 3;
    const int widthName = lenMax + MARGIN;

    // Determine the column to wrap at, if necessary.
    if ( wrapColumn == wxCMD_LINE_WRAP_AUTO )
    {
        // Note that GetWidth() returns 0 if the terminal size couldn't be
        // determined, which happens to be exactly wxCMD_LINE_WRAP_NONE.
        wrapColumn = wxTerminal::GetWidth();
    }

    // Check that we end up with reasonable layout, where descriptions column
    // is at least as wide as the names one.
    if ( wrapColumn != wxCMD_LINE_WRAP_NONE && wrapColumn < 2*widthName )
    {
        // It doesn't make sense to wrap anything if there is so little space.
        wrapColumn = wxCMD_LINE_WRAP_NONE;
    }

    // This is only used when wrapColumn is not wxCMD_LINE_WRAP_NONE.
    const int widthDesc = wrapColumn - widthName;

    for ( size_t n = 0; n < namesOptions.size(); n++ )
    {
        if ( n == count )
            usage << wxT('\n') << stdDesc;

        const auto& desc = descOptions[n];

        // desc contains text if name is empty
        if ( namesOptions[n].empty() )
        {
            usage << desc << wxT('\n');
        }
        else
        {
            // Check if the description fits on the same line if we wrap it.
            if ( wrapColumn == wxCMD_LINE_WRAP_NONE ||
                    static_cast<int>(desc.length()) <= widthDesc )
            {
                // Note that we use TAB as separator here for compatibility.
                usage << wxString::Format("%-*s%*s%s\n",
                                          static_cast<int>(lenMax),
                                          namesOptions[n],
                                          MARGIN, "",
                                          desc);
            }
            else // We need to wrap it.
            {
                usage << wxString::Format("%s\n", namesOptions[n]);

                for ( const auto& s : wxWordWrap(desc, widthDesc) )
                    usage << wxString::Format("%*s%s\n", widthName, "", s);
            }
        }
    }

    return usage;
}

// ----------------------------------------------------------------------------
// private functions
// ----------------------------------------------------------------------------

static wxString GetTypeName(wxCmdLineParamType type)
{
    wxString s;
    switch ( type )
    {
        default:
            wxFAIL_MSG( wxT("unknown option type") );
            wxFALLTHROUGH;

        case wxCMD_LINE_VAL_STRING:
            s = _("str");
            break;

        case wxCMD_LINE_VAL_NUMBER:
            s = _("num");
            break;

        case wxCMD_LINE_VAL_DOUBLE:
            s = _("double");
            break;

        case wxCMD_LINE_VAL_DATE:
            s = _("date");
            break;
    }

    return s;
}

/*
Returns a string which is equal to the string pointed to by p, but up to the
point where p contains an character that's not allowed.
Allowable characters are letters and numbers, and characters pointed to by
the parameter allowedChars.

For example, if p points to "abcde-@-_", and allowedChars is "-_",
this function returns "abcde-".
*/
static wxString GetOptionName(wxString::const_iterator p,
                              wxString::const_iterator end,
                              const wxChar *allowedChars)
{
    wxString argName;

    while ( p != end && (wxIsalnum(*p) || wxStrchr(allowedChars, *p)) )
    {
        argName += *p++;
    }

    return argName;
}

// Besides alphanumeric characters, short and long options can
// have other characters.

// A short option additionally can have these
#define wxCMD_LINE_CHARS_ALLOWED_BY_SHORT_OPTION wxT("_?")

// A long option can have the same characters as a short option and a '-'.
#define wxCMD_LINE_CHARS_ALLOWED_BY_LONG_OPTION \
    wxCMD_LINE_CHARS_ALLOWED_BY_SHORT_OPTION wxT("-")

static wxString GetShortOptionName(wxString::const_iterator p,
                                  wxString::const_iterator end)
{
    return GetOptionName(p, end, wxCMD_LINE_CHARS_ALLOWED_BY_SHORT_OPTION);
}

static wxString GetLongOptionName(wxString::const_iterator p,
                                  wxString::const_iterator end)
{
    return GetOptionName(p, end, wxCMD_LINE_CHARS_ALLOWED_BY_LONG_OPTION);
}

#endif // wxUSE_CMDLINE_PARSER

// ----------------------------------------------------------------------------
// global functions
// ----------------------------------------------------------------------------

/*
   This function is mainly used under Windows (as under Unix we always get the
   command line arguments as argc/argv anyhow) and so it tries to follow
   Windows conventions for the command line handling, not Unix ones. For
   instance, backslash is not special except when it precedes double quote when
   it does quote it.

   TODO: Rewrite this to follow the even more complicated rule used by Windows
         CommandLineToArgv():

    * A string of backslashes not followed by a quotation mark has no special
      meaning.
    * An even number of backslashes followed by a quotation mark is treated as
      pairs of protected backslashes, followed by a word terminator.
    * An odd number of backslashes followed by a quotation mark is treated as
      pairs of protected backslashes, followed by a protected quotation mark.

    See http://blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx

    It could also be useful to provide a converse function which is also
    non-trivial, see

    http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
 */

/* static */
wxArrayString
wxCmdLineParser::ConvertStringToArgs(const wxString& cmdline,
                                     wxCmdLineSplitType type)
{
    wxArrayString args;

    wxString arg;
    arg.reserve(1024);

    const wxString::const_iterator end = cmdline.end();
    wxString::const_iterator p = cmdline.begin();

    for ( ;; )
    {
        // skip white space
        while ( p != end && (*p == ' ' || *p == '\t') )
            ++p;

        // anything left?
        if ( p == end )
            break;

        // parse this parameter
        bool lastBS = false,
             isInsideQuotes = false;
        wxUniChar chDelim = '\0';
        for ( arg.clear(); p != end; ++p )
        {
            const wxUniChar ch = *p;

            if ( type == wxCMD_LINE_SPLIT_DOS )
            {
                if ( ch == '"' )
                {
                    if ( !lastBS )
                    {
                        isInsideQuotes = !isInsideQuotes;

                        // don't put quote in arg
                        continue;
                    }
                    //else: quote has no special meaning but the backslash
                    //      still remains -- makes no sense but this is what
                    //      Windows does
                }
                // note that backslash does *not* quote the space, only quotes do
                else if ( !isInsideQuotes && (ch == ' ' || ch == '\t') )
                {
                    ++p;    // skip this space anyhow
                    break;
                }

                lastBS = !lastBS && ch == '\\';
            }
            else // type == wxCMD_LINE_SPLIT_UNIX
            {
                if ( !lastBS )
                {
                    if ( isInsideQuotes )
                    {
                        if ( ch == chDelim )
                        {
                            isInsideQuotes = false;

                            continue;   // don't use the quote itself
                        }
                    }
                    else // not in quotes and not escaped
                    {
                        if ( ch == '\'' || ch == '"' )
                        {
                            isInsideQuotes = true;
                            chDelim = ch;

                            continue;   // don't use the quote itself
                        }

                        if ( ch == ' ' || ch == '\t' )
                        {
                            ++p;    // skip this space anyhow
                            break;
                        }
                    }

                    lastBS = ch == '\\';
                    if ( lastBS )
                        continue;
                }
                else // escaped by backslash, just use as is
                {
                    lastBS = false;
                }
            }

            arg += ch;
        }

        args.push_back(arg);
    }

    return args;
}
