﻿/**
@file CmdLineToArgv.cpp
@brief Parse command line to an argument vector.

This source file defines the CommandLineToArgVector function which may be used to parse a command
line as returned by the Win32 API function GetCommandLine to an argument vector.

This function is based on the function parse_cmdline found in the CRT source file STDARGV.C.
*/
#include "yekneb_config.h"

#include <string>
#include <vector>

#include "CmdLineToArgv.h"

#include "CharacterType.h"
#include "ConstantOfType.h"
#include "MaybeUnused.h"

namespace yekneb {

template<typename CharType>
size_t CommandLineToArgVector(const CharType* commandLine, std::vector<std::basic_string<CharType>>& arg_vector)
{
	using string_type = std::basic_string<CharType>;
    const auto NULCHAR = YCC(CharType, '\0');
    const auto SPACECHAR = YCC(CharType, ' ');
    const auto TABCHAR = YCC(CharType, '\t');
    const auto DQUOTECHAR = YCC(CharType, '\"');
    const auto SLASHCHAR = YCC(CharType, '\\');
    arg_vector.clear();
    if (commandLine == nullptr || commandLine[0] == 0)
    {
        return 0;
    }
    const CharType* p = commandLine;
    CharType c = 0;
    bool inquote = false; /* 1 = inside quotes */
    bool copychar = false; /* 1 = copy char to *args */
    size_t numslash = 0; /* number of backslashes seen */
    string_type strArg;
    /* first scan the program name, copy it, and count the bytes */
    /* A quoted program name is handled here. The handling is much
    simpler than for other arguments. Basically, whatever lies
    between the leading double-quote and next one, or a terminal null
    character is simply accepted. Fancier handling is not required
    because the program name must be a legal NTFS/HPFS file name.
    Note that the double-quote characters are not copied, nor do they
    contribute to numchars. */
    if (*p == DQUOTECHAR)
    {
        /* scan from just past the first double-quote through the next
        double-quote, or up to a null, whichever comes first */
        while ((*(++p) != DQUOTECHAR) && (*p != NULCHAR))
        {
            if (::yekneb::ctype::islead(*p))
            {
                strArg += *p++;
            }
            strArg += *p;
        }
        /* if we stopped on a double-quote (usual case), skip over it */
        if (*p == DQUOTECHAR)
        {
            ++p;
        }
    }
    else
    {
        /* Not a quoted program name */
        do
        {
            strArg += *p;
            c = *p++;
            if (::yekneb::ctype::islead(c))
            {
                strArg += *p;
                ++p; /* skip over trail byte */
            }
        } while (c != SPACECHAR && c != NULCHAR && c != TABCHAR);
        if (c == NULCHAR)
        {
            --p;
        }
    }
    arg_vector.push_back(strArg);
    strArg.erase();
    /* loop on each argument */
    for (;;)
    {
        if (*p)
        {
            while (*p == SPACECHAR || *p == TABCHAR)
            {
                ++p;
            }
        }
        if (*p == NULCHAR)
        {
            break; /* end of args */
        }
        /* loop through scanning one argument */
        for (;;)
        {
            copychar = true;
            /* Rules: 2N backslashes + " ==> N backslashes and begin/end quote
            2N+1 backslashes + " ==> N backslashes + literal "
            N backslashes ==> N backslashes */
            numslash = 0;
            while (*p == SLASHCHAR)
            {
                /* count number of backslashes for use below */
                ++p;
                ++numslash;
            }
            if (*p == DQUOTECHAR)
            {
                /* if 2N backslashes before, start/end quote, otherwise
                copy literally */
                if (numslash % 2 == 0)
                {
                    if (inquote)
                    {
                        if (p[1] == DQUOTECHAR)
                        {
                            ++p; /* Double quote inside quoted string */
                        }
                        else /* skip first quote char and copy second */
                        {
                            copychar = false;
                        }
                    }
                    else
                    {
                        copychar = false; /* don't copy quote */
                    }
                    inquote = !inquote;
                }
                numslash /= 2; /* divide numslash by two */
            }
            /* copy slashes */
            while (numslash--)
            {
                strArg += SLASHCHAR;
            }
            /* if at end of arg, break loop */
            if (*p == NULCHAR || (!inquote && (*p == SPACECHAR || *p == TABCHAR)))
            {
                break;
            }
            /* copy character into argument */
            if (copychar)
            {
                if (::yekneb::ctype::islead(*p))
                {
                    strArg += *p++;
                }
                strArg += *p;
            }
            ++p;
        }
        arg_vector.push_back(strArg);
        strArg.erase();
    }
    return static_cast<size_t>(arg_vector.size());
}

template size_t CommandLineToArgVector<char>(const char* commandLine, std::vector<std::basic_string<char>>& arg_vector);
template size_t CommandLineToArgVector<wchar_t>(const wchar_t* commandLine, std::vector<std::basic_string<wchar_t>>& arg_vector);

} // namespace yekneb
