/*-------------------------------------------------------------------------
 * drawElements Utility Library
 * ----------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Command line parser.
 *//*--------------------------------------------------------------------*/

#include "deCommandLine.h"
#include "deMemPool.h"
#include "dePoolArray.h"
#include "deMemory.h"
#include "deString.h"

#include <string.h>

DE_DECLARE_POOL_ARRAY(CharPtrArray, char *);

enum
{
    MAX_ARGS = 64
};

deCommandLine *deCommandLine_parse(const char *commandLine)
{
    deMemPool *tmpPool = deMemPool_createRoot(DE_NULL, 0);
    CharPtrArray *args = tmpPool ? CharPtrArray_create(tmpPool) : DE_NULL;
    char *buf          = DE_NULL;
    char *outPtr;
    int pos;
    int argNdx;
    char strChr;

    if (!args)
    {
        if (tmpPool)
            deMemPool_destroy(tmpPool);
        return DE_NULL;
    }

    DE_ASSERT(commandLine);

    /* Create buffer for args (no expansion can happen). */
    buf    = (char *)deCalloc(strlen(commandLine) + 1);
    pos    = 0;
    argNdx = 0;
    outPtr = buf;
    strChr = 0;

    if (!buf || !CharPtrArray_pushBack(args, buf))
    {
        deMemPool_destroy(tmpPool);
        return DE_NULL;
    }

    while (commandLine[pos] != 0)
    {
        char c = commandLine[pos++];

        if (strChr != 0 && c == '\\')
        {
            /* Escape. */
            c = commandLine[pos++];
            switch (c)
            {
            case 'n':
                *outPtr++ = '\n';
                break;
            case 't':
                *outPtr++ = '\t';
                break;
            default:
                *outPtr++ = c;
                break;
            }
        }
        else if (strChr != 0 && c == strChr)
        {
            /* String end. */
            strChr = 0;
        }
        else if (strChr == 0 && (c == '"' || c == '\''))
        {
            /* String start. */
            strChr = c;
        }
        else if (c == ' ' && strChr == 0)
        {
            /* Arg end. */
            *outPtr++ = 0;
            argNdx += 1;
            if (!CharPtrArray_pushBack(args, outPtr))
            {
                deFree(buf);
                deMemPool_destroy(tmpPool);
                return DE_NULL;
            }
        }
        else
            *outPtr++ = c;
    }

    DE_ASSERT(commandLine[pos] == 0);

    /* Terminate last arg. */
    *outPtr = 0;

    /* Create deCommandLine. */
    {
        deCommandLine *cmdLine = (deCommandLine *)deCalloc(sizeof(deCommandLine));

        if (!cmdLine ||
            !(cmdLine->args = (char **)deCalloc(sizeof(char *) * (size_t)CharPtrArray_getNumElements(args))))
        {
            deFree(cmdLine);
            deFree(buf);
            deMemPool_destroy(tmpPool);
            return DE_NULL;
        }

        cmdLine->numArgs = CharPtrArray_getNumElements(args);
        cmdLine->argBuf  = buf;

        for (argNdx = 0; argNdx < cmdLine->numArgs; argNdx++)
            cmdLine->args[argNdx] = CharPtrArray_get(args, argNdx);

        deMemPool_destroy(tmpPool);
        return cmdLine;
    }
}

void deCommandLine_destroy(deCommandLine *cmdLine)
{
    deFree(cmdLine->argBuf);
    deFree(cmdLine);
}

static void testParse(const char *cmdLine, const char *const *refArgs, int numArgs)
{
    deCommandLine *parsedCmdLine = deCommandLine_parse(cmdLine);
    int argNdx;

    DE_TEST_ASSERT(parsedCmdLine);
    DE_TEST_ASSERT(parsedCmdLine->numArgs == numArgs);

    for (argNdx = 0; argNdx < numArgs; argNdx++)
        DE_TEST_ASSERT(deStringEqual(parsedCmdLine->args[argNdx], refArgs[argNdx]));

    deCommandLine_destroy(parsedCmdLine);
}

void deCommandLine_selfTest(void)
{
    {
        const char *cmdLine = "hello";
        const char *ref[]   = {"hello"};
        testParse(cmdLine, ref, DE_LENGTH_OF_ARRAY(ref));
    }
    {
        const char *cmdLine = "hello world";
        const char *ref[]   = {"hello", "world"};
        testParse(cmdLine, ref, DE_LENGTH_OF_ARRAY(ref));
    }
    {
        const char *cmdLine = "hello/world";
        const char *ref[]   = {"hello/world"};
        testParse(cmdLine, ref, DE_LENGTH_OF_ARRAY(ref));
    }
    {
        const char *cmdLine = "hello/world --help";
        const char *ref[]   = {"hello/world", "--help"};
        testParse(cmdLine, ref, DE_LENGTH_OF_ARRAY(ref));
    }
    {
        const char *cmdLine = "hello/world --help foo";
        const char *ref[]   = {"hello/world", "--help", "foo"};
        testParse(cmdLine, ref, DE_LENGTH_OF_ARRAY(ref));
    }
    {
        const char *cmdLine = "hello\\world --help foo";
        const char *ref[]   = {"hello\\world", "--help", "foo"};
        testParse(cmdLine, ref, DE_LENGTH_OF_ARRAY(ref));
    }
    {
        const char *cmdLine = "\"hello/worl d\" --help --foo=\"bar\" \"ba z\\\"\"";
        const char *ref[]   = {"hello/worl d", "--help", "--foo=bar", "ba z\""};
        testParse(cmdLine, ref, DE_LENGTH_OF_ARRAY(ref));
    }
    {
        const char *cmdLine = "'hello/worl d' --help --foo='bar' 'ba z\\\''";
        const char *ref[]   = {"hello/worl d", "--help", "--foo=bar", "ba z'"};
        testParse(cmdLine, ref, DE_LENGTH_OF_ARRAY(ref));
    }
    {
        const char *cmdLine = "hello \"'world'\"";
        const char *ref[]   = {"hello", "'world'"};
        testParse(cmdLine, ref, DE_LENGTH_OF_ARRAY(ref));
    }
    {
        const char *cmdLine = "hello '\"world\"'";
        const char *ref[]   = {"hello", "\"world\""};
        testParse(cmdLine, ref, DE_LENGTH_OF_ARRAY(ref));
    }
    {
        const char *cmdLine = "hello \"world\\n\"";
        const char *ref[]   = {"hello", "world\n"};
        testParse(cmdLine, ref, DE_LENGTH_OF_ARRAY(ref));
    }
}
