/*-------------------------------------------------------------------------
 * 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((int)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*)*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));
	}
}
