/*-------------------------------------------------------------------------
 * drawElements Quality Program Tester Core
 * ----------------------------------------
 *
 * 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 parsing.
 *//*--------------------------------------------------------------------*/

#include "tcuCommandLine.hpp"
#include "tcuPlatform.hpp"
#include "tcuTestCase.hpp"
#include "tcuResource.hpp"
#include "deFilePath.hpp"
#include "deStringUtil.hpp"
#include "deString.h"
#include "deInt32.h"
#include "deCommandLine.h"
#include "qpTestLog.h"
#include "qpDebugOut.h"

#include <string>
#include <vector>
#include <sstream>
#include <fstream>
#include <iostream>
#include <algorithm>

using std::string;
using std::vector;

// OOM tests are enabled by default only on platforms that don't do memory overcommit (Win32)
#if (DE_OS == DE_OS_WIN32)
#	define TEST_OOM_DEFAULT		"enable"
#else
#	define TEST_OOM_DEFAULT		"disable"
#endif

namespace tcu
{

namespace opt
{

DE_DECLARE_COMMAND_LINE_OPT(CasePath,					std::string);
DE_DECLARE_COMMAND_LINE_OPT(CaseList,					std::string);
DE_DECLARE_COMMAND_LINE_OPT(CaseListFile,				std::string);
DE_DECLARE_COMMAND_LINE_OPT(CaseListResource,			std::string);
DE_DECLARE_COMMAND_LINE_OPT(StdinCaseList,				bool);
DE_DECLARE_COMMAND_LINE_OPT(LogFilename,				std::string);
DE_DECLARE_COMMAND_LINE_OPT(RunMode,					tcu::RunMode);
DE_DECLARE_COMMAND_LINE_OPT(ExportFilenamePattern,		std::string);
DE_DECLARE_COMMAND_LINE_OPT(WatchDog,					bool);
DE_DECLARE_COMMAND_LINE_OPT(CrashHandler,				bool);
DE_DECLARE_COMMAND_LINE_OPT(BaseSeed,					int);
DE_DECLARE_COMMAND_LINE_OPT(TestIterationCount,			int);
DE_DECLARE_COMMAND_LINE_OPT(Visibility,					WindowVisibility);
DE_DECLARE_COMMAND_LINE_OPT(SurfaceWidth,				int);
DE_DECLARE_COMMAND_LINE_OPT(SurfaceHeight,				int);
DE_DECLARE_COMMAND_LINE_OPT(SurfaceType,				tcu::SurfaceType);
DE_DECLARE_COMMAND_LINE_OPT(ScreenRotation,				tcu::ScreenRotation);
DE_DECLARE_COMMAND_LINE_OPT(GLContextType,				std::string);
DE_DECLARE_COMMAND_LINE_OPT(GLConfigID,					int);
DE_DECLARE_COMMAND_LINE_OPT(GLConfigName,				std::string);
DE_DECLARE_COMMAND_LINE_OPT(GLContextFlags,				std::string);
DE_DECLARE_COMMAND_LINE_OPT(CLPlatformID,				int);
DE_DECLARE_COMMAND_LINE_OPT(CLDeviceIDs,				std::vector<int>);
DE_DECLARE_COMMAND_LINE_OPT(CLBuildOptions,				std::string);
DE_DECLARE_COMMAND_LINE_OPT(EGLDisplayType,				std::string);
DE_DECLARE_COMMAND_LINE_OPT(EGLWindowType,				std::string);
DE_DECLARE_COMMAND_LINE_OPT(EGLPixmapType,				std::string);
DE_DECLARE_COMMAND_LINE_OPT(LogImages,					bool);
DE_DECLARE_COMMAND_LINE_OPT(LogShaderSources,			bool);
DE_DECLARE_COMMAND_LINE_OPT(TestOOM,					bool);
DE_DECLARE_COMMAND_LINE_OPT(ArchiveDir,					std::string);
DE_DECLARE_COMMAND_LINE_OPT(VKDeviceID,					int);
DE_DECLARE_COMMAND_LINE_OPT(VKDeviceGroupID,			int);
DE_DECLARE_COMMAND_LINE_OPT(LogFlush,					bool);
DE_DECLARE_COMMAND_LINE_OPT(Validation,					bool);
DE_DECLARE_COMMAND_LINE_OPT(PrintValidationErrors,		bool);
DE_DECLARE_COMMAND_LINE_OPT(ShaderCache,				bool);
DE_DECLARE_COMMAND_LINE_OPT(ShaderCacheFilename,		std::string);
DE_DECLARE_COMMAND_LINE_OPT(Optimization,				int);
DE_DECLARE_COMMAND_LINE_OPT(OptimizeSpirv,				bool);
DE_DECLARE_COMMAND_LINE_OPT(ShaderCacheTruncate,		bool);
DE_DECLARE_COMMAND_LINE_OPT(RenderDoc,					bool);
DE_DECLARE_COMMAND_LINE_OPT(CaseFraction,				std::vector<int>);
DE_DECLARE_COMMAND_LINE_OPT(CaseFractionMandatoryTests,	std::string);
DE_DECLARE_COMMAND_LINE_OPT(WaiverFile,					std::string);
DE_DECLARE_COMMAND_LINE_OPT(RunnerType,					tcu::TestRunnerType);

static void parseIntList (const char* src, std::vector<int>* dst)
{
	std::istringstream	str	(src);
	std::string			val;

	while (std::getline(str, val, ','))
	{
		int intVal = 0;
		de::cmdline::parseType(val.c_str(), &intVal);
		dst->push_back(intVal);
	}
}

void registerOptions (de::cmdline::Parser& parser)
{
	using de::cmdline::Option;
	using de::cmdline::NamedValue;

	static const NamedValue<bool> s_enableNames[] =
	{
		{ "enable",		true	},
		{ "disable",	false	}
	};
	static const NamedValue<tcu::RunMode> s_runModes[] =
	{
		{ "execute",		RUNMODE_EXECUTE				},
		{ "xml-caselist",	RUNMODE_DUMP_XML_CASELIST	},
		{ "txt-caselist",	RUNMODE_DUMP_TEXT_CASELIST	},
		{ "stdout-caselist",RUNMODE_DUMP_STDOUT_CASELIST}
	};
	static const NamedValue<WindowVisibility> s_visibilites[] =
	{
		{ "windowed",		WINDOWVISIBILITY_WINDOWED	},
		{ "fullscreen",		WINDOWVISIBILITY_FULLSCREEN	},
		{ "hidden",			WINDOWVISIBILITY_HIDDEN		}
	};
	static const NamedValue<tcu::SurfaceType> s_surfaceTypes[] =
	{
		{ "window",			SURFACETYPE_WINDOW				},
		{ "pixmap",			SURFACETYPE_OFFSCREEN_NATIVE	},
		{ "pbuffer",		SURFACETYPE_OFFSCREEN_GENERIC	},
		{ "fbo",			SURFACETYPE_FBO					}
	};
	static const NamedValue<tcu::ScreenRotation> s_screenRotations[] =
	{
		{ "unspecified",	SCREENROTATION_UNSPECIFIED	},
		{ "0",				SCREENROTATION_0			},
		{ "90",				SCREENROTATION_90			},
		{ "180",			SCREENROTATION_180			},
		{ "270",			SCREENROTATION_270			}
	};
	static const NamedValue<tcu::TestRunnerType> s_runnerTypes[] =
	{
		{ "any",	tcu::RUNNERTYPE_ANY		},
		{ "none",	tcu::RUNNERTYPE_NONE	},
		{ "amber",	tcu::RUNNERTYPE_AMBER	},
	};

	parser
		<< Option<CasePath>						("n",		"deqp-case",								"Test case(s) to run, supports wildcards (e.g. dEQP-GLES2.info.*)")
		<< Option<CaseList>						(DE_NULL,	"deqp-caselist",							"Case list to run in trie format (e.g. {dEQP-GLES2{info{version,renderer}}})")
		<< Option<CaseListFile>					(DE_NULL,	"deqp-caselist-file",						"Read case list (in trie format) from given file")
		<< Option<CaseListResource>				(DE_NULL,	"deqp-caselist-resource",					"Read case list (in trie format) from given file located application's assets")
		<< Option<StdinCaseList>				(DE_NULL,	"deqp-stdin-caselist",						"Read case list (in trie format) from stdin")
		<< Option<LogFilename>					(DE_NULL,	"deqp-log-filename",						"Write test results to given file",					"TestResults.qpa")
		<< Option<RunMode>						(DE_NULL,	"deqp-runmode",								"Execute tests, or write list of test cases into a file",
																																							s_runModes,			"execute")
		<< Option<ExportFilenamePattern>		(DE_NULL,	"deqp-caselist-export-file",				"Set the target file name pattern for caselist export",					"${packageName}-cases.${typeExtension}")
		<< Option<WatchDog>						(DE_NULL,	"deqp-watchdog",							"Enable test watchdog",								s_enableNames,		"disable")
		<< Option<CrashHandler>					(DE_NULL,	"deqp-crashhandler",						"Enable crash handling",							s_enableNames,		"disable")
		<< Option<BaseSeed>						(DE_NULL,	"deqp-base-seed",							"Base seed for test cases that use randomization",						"0")
		<< Option<TestIterationCount>			(DE_NULL,	"deqp-test-iteration-count",				"Iteration count for cases that support variable number of iterations",	"0")
		<< Option<Visibility>					(DE_NULL,	"deqp-visibility",							"Default test window visibility",					s_visibilites,		"windowed")
		<< Option<SurfaceWidth>					(DE_NULL,	"deqp-surface-width",						"Use given surface width if possible",									"-1")
		<< Option<SurfaceHeight>				(DE_NULL,	"deqp-surface-height",						"Use given surface height if possible",									"-1")
		<< Option<SurfaceType>					(DE_NULL,	"deqp-surface-type",						"Use given surface type",							s_surfaceTypes,		"window")
		<< Option<ScreenRotation>				(DE_NULL,	"deqp-screen-rotation",						"Screen rotation for platforms that support it",	s_screenRotations,	"0")
		<< Option<GLContextType>				(DE_NULL,	"deqp-gl-context-type",						"OpenGL context type for platforms that support multiple")
		<< Option<GLConfigID>					(DE_NULL,	"deqp-gl-config-id",						"OpenGL (ES) render config ID (EGL config id on EGL platforms)",		"-1")
		<< Option<GLConfigName>					(DE_NULL,	"deqp-gl-config-name",						"Symbolic OpenGL (ES) render config name")
		<< Option<GLContextFlags>				(DE_NULL,	"deqp-gl-context-flags",					"OpenGL context flags (comma-separated, supports debug and robust)")
		<< Option<CLPlatformID>					(DE_NULL,	"deqp-cl-platform-id",						"Execute tests on given OpenCL platform (IDs start from 1)",			"1")
		<< Option<CLDeviceIDs>					(DE_NULL,	"deqp-cl-device-ids",						"Execute tests on given CL devices (comma-separated, IDs start from 1)",	parseIntList,	"")
		<< Option<CLBuildOptions>				(DE_NULL,	"deqp-cl-build-options",					"Extra build options for OpenCL compiler")
		<< Option<EGLDisplayType>				(DE_NULL,	"deqp-egl-display-type",					"EGL native display type")
		<< Option<EGLWindowType>				(DE_NULL,	"deqp-egl-window-type",						"EGL native window type")
		<< Option<EGLPixmapType>				(DE_NULL,	"deqp-egl-pixmap-type",						"EGL native pixmap type")
		<< Option<VKDeviceID>					(DE_NULL,	"deqp-vk-device-id",						"Vulkan device ID (IDs start from 1)",									"1")
		<< Option<VKDeviceGroupID>				(DE_NULL,	"deqp-vk-device-group-id",					"Vulkan device Group ID (IDs start from 1)",							"1")
		<< Option<LogImages>					(DE_NULL,	"deqp-log-images",							"Enable or disable logging of result images",		s_enableNames,		"enable")
		<< Option<LogShaderSources>				(DE_NULL,	"deqp-log-shader-sources",					"Enable or disable logging of shader sources",		s_enableNames,		"enable")
		<< Option<TestOOM>						(DE_NULL,	"deqp-test-oom",							"Run tests that exhaust memory on purpose",			s_enableNames,		TEST_OOM_DEFAULT)
		<< Option<ArchiveDir>					(DE_NULL,	"deqp-archive-dir",							"Path to test resource files",											".")
		<< Option<LogFlush>						(DE_NULL,	"deqp-log-flush",							"Enable or disable log file fflush",				s_enableNames,		"enable")
		<< Option<Validation>					(DE_NULL,	"deqp-validation",							"Enable or disable test case validation",			s_enableNames,		"disable")
		<< Option<PrintValidationErrors>		(DE_NULL,	"deqp-print-validation-errors",				"Print validation errors to standard error")
		<< Option<Optimization>					(DE_NULL,	"deqp-optimization-recipe",					"Shader optimization recipe (0=disabled, 1=performance, 2=size)",		"0")
		<< Option<OptimizeSpirv>				(DE_NULL,	"deqp-optimize-spirv",						"Apply optimization to spir-v shaders as well",		s_enableNames,		"disable")
		<< Option<ShaderCache>					(DE_NULL,	"deqp-shadercache",							"Enable or disable shader cache",					s_enableNames,		"enable")
		<< Option<ShaderCacheFilename>			(DE_NULL,	"deqp-shadercache-filename",				"Write shader cache to given file",										"shadercache.bin")
		<< Option<ShaderCacheTruncate>			(DE_NULL,	"deqp-shadercache-truncate",				"Truncate shader cache before running tests",		s_enableNames,		"enable")
		<< Option<RenderDoc>					(DE_NULL,	"deqp-renderdoc",							"Enable RenderDoc frame markers",					s_enableNames,		"disable")
		<< Option<CaseFraction>					(DE_NULL,	"deqp-fraction",							"Run a fraction of the test cases (e.g. N,M means run group%M==N)",	parseIntList,	"")
		<< Option<CaseFractionMandatoryTests>	(DE_NULL,	"deqp-fraction-mandatory-caselist-file",	"Case list file that must be run for each fraction",					"")
		<< Option<WaiverFile>					(DE_NULL,	"deqp-waiver-file",							"Read waived tests from given file",									"")
		<< Option<RunnerType>					(DE_NULL,	"deqp-runner-type",							"Filter test cases based on runner",				s_runnerTypes,		"any");
}

void registerLegacyOptions (de::cmdline::Parser& parser)
{
	using de::cmdline::Option;

	parser
		<< Option<GLConfigID>			(DE_NULL,	"deqp-egl-config-id",			"Legacy name for --deqp-gl-config-id",	"-1")
		<< Option<GLConfigName>			(DE_NULL,	"deqp-egl-config-name",			"Legacy name for --deqp-gl-config-name");
}

} // opt

// \todo [2014-02-13 pyry] This could be useful elsewhere as well.
class DebugOutStreambuf : public std::streambuf
{
public:
						DebugOutStreambuf	(void);
						~DebugOutStreambuf	(void);

protected:
	std::streamsize		xsputn				(const char* s, std::streamsize count);
	int					overflow			(int ch = -1);

private:
	void				flushLine			(void);

	std::ostringstream	m_curLine;
};

DebugOutStreambuf::DebugOutStreambuf (void)
{
}

DebugOutStreambuf::~DebugOutStreambuf (void)
{
	if (m_curLine.tellp() != std::streampos(0))
		flushLine();
}

std::streamsize DebugOutStreambuf::xsputn (const char* s, std::streamsize count)
{
	for (std::streamsize pos = 0; pos < count; pos++)
	{
		m_curLine.put(s[pos]);

		if (s[pos] == '\n')
			flushLine();
	}

	return count;
}

int DebugOutStreambuf::overflow (int ch)
{
	if (ch == -1)
		return -1;
	else
	{
		DE_ASSERT((ch & 0xff) == ch);
		const char chVal = (char)(deUint8)(ch & 0xff);
		return xsputn(&chVal, 1) == 1 ? ch : -1;
	}
}

void DebugOutStreambuf::flushLine (void)
{
	qpPrint(m_curLine.str().c_str());
	m_curLine.str("");
}

class CaseTreeNode
{
public:
										CaseTreeNode		(const std::string& name) : m_name(name) {}
										~CaseTreeNode		(void);

	const std::string&					getName				(void) const { return m_name;				}
	bool								hasChildren			(void) const { return !m_children.empty();	}

	bool								hasChild			(const std::string& name) const;
	const CaseTreeNode*					getChild			(const std::string& name) const;
	CaseTreeNode*						getChild			(const std::string& name);

	void								addChild			(CaseTreeNode* child) { m_children.push_back(child); }

private:
										CaseTreeNode		(const CaseTreeNode&);
	CaseTreeNode&						operator=			(const CaseTreeNode&);

	enum { NOT_FOUND = -1 };

	// \todo [2014-10-30 pyry] Speed up with hash / sorting
	int									findChildNdx		(const std::string& name) const;

	std::string							m_name;
	std::vector<CaseTreeNode*>			m_children;
};

CaseTreeNode::~CaseTreeNode (void)
{
	for (vector<CaseTreeNode*>::const_iterator i = m_children.begin(); i != m_children.end(); ++i)
		delete *i;
}

int CaseTreeNode::findChildNdx (const std::string& name) const
{
	for (int ndx = 0; ndx < (int)m_children.size(); ++ndx)
	{
		if (m_children[ndx]->getName() == name)
			return ndx;
	}
	return NOT_FOUND;
}

inline bool CaseTreeNode::hasChild (const std::string& name) const
{
	return findChildNdx(name) != NOT_FOUND;
}

inline const CaseTreeNode* CaseTreeNode::getChild (const std::string& name) const
{
	const int ndx = findChildNdx(name);
	return ndx == NOT_FOUND ? DE_NULL : m_children[ndx];
}

inline CaseTreeNode* CaseTreeNode::getChild (const std::string& name)
{
	const int ndx = findChildNdx(name);
	return ndx == NOT_FOUND ? DE_NULL : m_children[ndx];
}

static int getCurrentComponentLen (const char* path)
{
	int ndx = 0;
	for (; path[ndx] != 0 && path[ndx] != '.'; ++ndx);
	return ndx;
}

static const CaseTreeNode* findNode (const CaseTreeNode* root, const char* path)
{
	const CaseTreeNode*	curNode		= root;
	const char*			curPath		= path;
	int					curLen		= getCurrentComponentLen(curPath);

	for (;;)
	{
		curNode = curNode->getChild(std::string(curPath, curPath+curLen));

		if (!curNode)
			break;

		curPath	+= curLen;

		if (curPath[0] == 0)
			break;
		else
		{
			DE_ASSERT(curPath[0] == '.');
			curPath		+= 1;
			curLen		 = getCurrentComponentLen(curPath);
		}
	}

	return curNode;
}

static void parseCaseTrie (CaseTreeNode* root, std::istream& in)
{
	vector<CaseTreeNode*>	nodeStack;
	string					curName;
	bool					expectNode		= true;

	if (in.get() != '{')
		throw std::invalid_argument("Malformed case trie");

	nodeStack.push_back(root);

	while (!nodeStack.empty())
	{
		const int	curChr	= in.get();

		if (curChr == std::char_traits<char>::eof() || curChr == 0)
			throw std::invalid_argument("Unterminated case tree");

		if (curChr == '{' || curChr == ',' || curChr == '}')
		{
			if (!curName.empty() && expectNode)
			{
				CaseTreeNode* const newChild = new CaseTreeNode(curName);

				try
				{
					nodeStack.back()->addChild(newChild);
				}
				catch (...)
				{
					delete newChild;
					throw;
				}

				if (curChr == '{')
					nodeStack.push_back(newChild);

				curName.clear();
			}
			else if (curName.empty() == expectNode)
				throw std::invalid_argument(expectNode ? "Empty node name" : "Missing node separator");

			if (curChr == '}')
			{
				expectNode = false;
				nodeStack.pop_back();

				// consume trailing new line
				if (nodeStack.empty())
				{
					if (in.peek() == '\r')
					  in.get();
					if (in.peek() == '\n')
					  in.get();
				}
			}
			else
				expectNode = true;
		}
		else if (isValidTestCaseNameChar((char)curChr))
			curName += (char)curChr;
		else
			throw std::invalid_argument("Illegal character in node name");
	}
}

static void parseCaseList (CaseTreeNode* root, std::istream& in, bool reportDuplicates)
{
	// \note Algorithm assumes that cases are sorted by groups, but will
	//		 function fine, albeit more slowly, if that is not the case.
	vector<CaseTreeNode*>	nodeStack;
	int						stackPos	= 0;
	string					curName;

	nodeStack.resize(8, DE_NULL);

	nodeStack[0] = root;

	for (;;)
	{
		const int	curChr	= in.get();

		if (curChr == std::char_traits<char>::eof() || curChr == 0 || curChr == '\n' || curChr == '\r')
		{
			if (curName.empty())
				throw std::invalid_argument("Empty test case name");

			if (!nodeStack[stackPos]->hasChild(curName))
			{
				CaseTreeNode* const newChild = new CaseTreeNode(curName);

				try
				{
					nodeStack[stackPos]->addChild(newChild);
				}
				catch (...)
				{
					delete newChild;
					throw;
				}
			}
			else if (reportDuplicates)
				throw std::invalid_argument("Duplicate test case");

			curName.clear();
			stackPos = 0;

			if (curChr == '\r' && in.peek() == '\n')
				in.get();

			{
				const int nextChr = in.peek();

				if (nextChr == std::char_traits<char>::eof() || nextChr == 0)
					break;
			}
		}
		else if (curChr == '.')
		{
			if (curName.empty())
				throw std::invalid_argument("Empty test group name");

			if ((int)nodeStack.size() <= stackPos+1)
				nodeStack.resize(nodeStack.size()*2, DE_NULL);

			if (!nodeStack[stackPos+1] || nodeStack[stackPos+1]->getName() != curName)
			{
				CaseTreeNode* curGroup = nodeStack[stackPos]->getChild(curName);

				if (!curGroup)
				{
					curGroup = new CaseTreeNode(curName);

					try
					{
						nodeStack[stackPos]->addChild(curGroup);
					}
					catch (...)
					{
						delete curGroup;
						throw;
					}
				}

				nodeStack[stackPos+1] = curGroup;

				if ((int)nodeStack.size() > stackPos+2)
					nodeStack[stackPos+2] = DE_NULL; // Invalidate rest of entries
			}

			DE_ASSERT(nodeStack[stackPos+1]->getName() == curName);

			curName.clear();
			stackPos += 1;
		}
		else if (isValidTestCaseNameChar((char)curChr))
			curName += (char)curChr;
		else
			throw std::invalid_argument("Illegal character in test case name");
	}
}

static CaseTreeNode* parseCaseList (std::istream& in)
{
	CaseTreeNode* const root = new CaseTreeNode("");
	try
	{
		if (in.peek() == '{')
			parseCaseTrie(root, in);
		else
			parseCaseList(root, in, true);

		{
			const int curChr = in.get();
			if (curChr != std::char_traits<char>::eof() && curChr != 0)
				throw std::invalid_argument("Trailing characters at end of case list");
		}

		return root;
	}
	catch (...)
	{
		delete root;
		throw;
	}
}

class CasePaths
{
public:
	CasePaths(const string& pathList);
	CasePaths(const vector<string>& pathList);
	bool					matches(const string& caseName, bool allowPrefix = false) const;

private:
	const vector<string>	m_casePatterns;
};

CasePaths::CasePaths (const string& pathList)
	: m_casePatterns(de::splitString(pathList, ','))
{
}

CasePaths::CasePaths(const vector<string>& pathList)
	: m_casePatterns(pathList)
{
}

// Match a single path component against a pattern component that may contain *-wildcards.
bool matchWildcards(string::const_iterator	patternStart,
					string::const_iterator	patternEnd,
					string::const_iterator	pathStart,
					string::const_iterator	pathEnd,
					bool					allowPrefix)
{
	string::const_iterator	pattern	= patternStart;
	string::const_iterator	path	= pathStart;

	while (pattern != patternEnd && path != pathEnd && *pattern == *path)
	{
		++pattern;
		++path;
	}

	if (pattern == patternEnd)
		return (path == pathEnd);
	else if (*pattern == '*')
	{
		for (; path != pathEnd; ++path)
		{
			if (matchWildcards(pattern + 1, patternEnd, path, pathEnd, allowPrefix))
				return true;
		}

		if (matchWildcards(pattern + 1, patternEnd, pathEnd, pathEnd, allowPrefix))
			return true;
	}
	else if (path == pathEnd && allowPrefix)
		return true;

	return false;
}

#if defined(TCU_HIERARCHICAL_CASEPATHS)
// Match a list of pattern components to a list of path components. A pattern
// component may contain *-wildcards. A pattern component "**" matches zero or
// more whole path components.
static bool patternMatches(vector<string>::const_iterator	patternStart,
						   vector<string>::const_iterator	patternEnd,
						   vector<string>::const_iterator	pathStart,
						   vector<string>::const_iterator	pathEnd,
						   bool								allowPrefix)
{
	vector<string>::const_iterator	pattern	= patternStart;
	vector<string>::const_iterator	path	= pathStart;

	while (pattern != patternEnd && path != pathEnd && *pattern != "**" &&
		   (*pattern == *path || matchWildcards(pattern->begin(), pattern->end(),
												path->begin(), path->end(), false)))
	{
		++pattern;
		++path;
	}

	if (path == pathEnd && (allowPrefix || pattern == patternEnd))
		return true;
	else if (pattern != patternEnd && *pattern == "**")
	{
		for (; path != pathEnd; ++path)
			if (patternMatches(pattern + 1, patternEnd, path, pathEnd, allowPrefix))
				return true;
		if (patternMatches(pattern + 1, patternEnd, path, pathEnd, allowPrefix))
			return true;
	}

	return false;
}
#endif

bool CasePaths::matches (const string& caseName, bool allowPrefix) const
{
	const vector<string> components = de::splitString(caseName, '.');

	for (size_t ndx = 0; ndx < m_casePatterns.size(); ++ndx)
	{
#if defined(TCU_HIERARCHICAL_CASEPATHS)
		const vector<string> patternComponents = de::splitString(m_casePatterns[ndx], '.');

		if (patternMatches(patternComponents.begin(), patternComponents.end(),
						   components.begin(), components.end(), allowPrefix))
			return true;
#else
		if (matchWildcards(m_casePatterns[ndx].begin(), m_casePatterns[ndx].end(),
						   caseName.begin(), caseName.end(), allowPrefix))
			return true;
#endif
	}

	return false;
}

/*--------------------------------------------------------------------*//*!
 * \brief Construct command line
 * \note CommandLine is not fully initialized until parse() has been called.
 *//*--------------------------------------------------------------------*/
CommandLine::CommandLine (void)
	: m_logFlags	(0)
{
}

/*--------------------------------------------------------------------*//*!
 * \brief Construct command line from standard argc, argv pair.
 *
 * Calls parse() with given arguments
 * \param archive application's assets
 * \param argc Number of arguments
 * \param argv Command line arguments
 *//*--------------------------------------------------------------------*/
CommandLine::CommandLine (int argc, const char* const* argv)
	: m_logFlags	(0)
{
	if (argc > 1)
	{
		int loop = 1;		// skip application name
		while (true)
		{
			m_initialCmdLine += std::string(argv[loop++]);
			if (loop >= argc)
				break;
			m_initialCmdLine += " ";
		}
	}

	if (!parse(argc, argv))
		throw Exception("Failed to parse command line");
}

/*--------------------------------------------------------------------*//*!
 * \brief Construct command line from string.
 *
 * Calls parse() with given argument.
 * \param archive application's assets
 * \param cmdLine Full command line string.
 *//*--------------------------------------------------------------------*/
CommandLine::CommandLine (const std::string& cmdLine)
	: m_initialCmdLine	(cmdLine)
{
	if (!parse(cmdLine))
		throw Exception("Failed to parse command line");
}

CommandLine::~CommandLine (void)
{
}

void CommandLine::clear (void)
{
	m_cmdLine.clear();
	m_logFlags = 0;
}

const de::cmdline::CommandLine& CommandLine::getCommandLine (void) const
{
	return m_cmdLine;
}

const std::string& CommandLine::getInitialCmdLine(void) const
{
	return m_initialCmdLine;
}

void CommandLine::registerExtendedOptions (de::cmdline::Parser& parser)
{
	DE_UNREF(parser);
}

/*--------------------------------------------------------------------*//*!
 * \brief Parse command line from standard argc, argv pair.
 * \note parse() must be called exactly once.
 * \param argc Number of arguments
 * \param argv Command line arguments
 *//*--------------------------------------------------------------------*/
bool CommandLine::parse (int argc, const char* const* argv)
{
	DebugOutStreambuf	sbuf;
	std::ostream		debugOut	(&sbuf);
	de::cmdline::Parser	parser;

	opt::registerOptions(parser);
	opt::registerLegacyOptions(parser);
	registerExtendedOptions(parser);

	clear();

	if (!parser.parse(argc-1, argv+1, &m_cmdLine, std::cerr))
	{
		debugOut << "\n" << de::FilePath(argv[0]).getBaseName() << " [options]\n\n";
		parser.help(debugOut);

		clear();
		return false;
	}

	if (!m_cmdLine.getOption<opt::LogImages>())
		m_logFlags |= QP_TEST_LOG_EXCLUDE_IMAGES;

	if (!m_cmdLine.getOption<opt::LogShaderSources>())
		m_logFlags |= QP_TEST_LOG_EXCLUDE_SHADER_SOURCES;

	if (!m_cmdLine.getOption<opt::LogFlush>())
		m_logFlags |= QP_TEST_LOG_NO_FLUSH;

	if ((m_cmdLine.hasOption<opt::CasePath>()?1:0) +
		(m_cmdLine.hasOption<opt::CaseList>()?1:0) +
		(m_cmdLine.hasOption<opt::CaseListFile>()?1:0) +
		(m_cmdLine.hasOption<opt::CaseListResource>()?1:0) +
		(m_cmdLine.getOption<opt::StdinCaseList>()?1:0) > 1)
	{
		debugOut << "ERROR: multiple test case list options given!\n" << std::endl;
		clear();
		return false;
	}

	return true;
}

/*--------------------------------------------------------------------*//*!
 * \brief Parse command line from string.
 * \note parse() must be called exactly once.
 * \param cmdLine Full command line string.
 *//*--------------------------------------------------------------------*/
bool CommandLine::parse (const std::string& cmdLine)
{
	deCommandLine* parsedCmdLine = deCommandLine_parse(cmdLine.c_str());
	if (!parsedCmdLine)
		throw std::bad_alloc();

	bool isOk = false;
	try
	{
		isOk = parse(parsedCmdLine->numArgs, parsedCmdLine->args);
	}
	catch (...)
	{
		deCommandLine_destroy(parsedCmdLine);
		throw;
	}

	deCommandLine_destroy(parsedCmdLine);
	return isOk;
}

const char*				CommandLine::getLogFileName					(void) const	{ return m_cmdLine.getOption<opt::LogFilename>().c_str();					}
deUint32				CommandLine::getLogFlags					(void) const	{ return m_logFlags;														}
RunMode					CommandLine::getRunMode						(void) const	{ return m_cmdLine.getOption<opt::RunMode>();								}
const char*				CommandLine::getCaseListExportFile			(void) const	{ return m_cmdLine.getOption<opt::ExportFilenamePattern>().c_str();			}
WindowVisibility		CommandLine::getVisibility					(void) const	{ return m_cmdLine.getOption<opt::Visibility>();							}
bool					CommandLine::isWatchDogEnabled				(void) const	{ return m_cmdLine.getOption<opt::WatchDog>();								}
bool					CommandLine::isCrashHandlingEnabled			(void) const	{ return m_cmdLine.getOption<opt::CrashHandler>();							}
int						CommandLine::getBaseSeed					(void) const	{ return m_cmdLine.getOption<opt::BaseSeed>();								}
int						CommandLine::getTestIterationCount			(void) const	{ return m_cmdLine.getOption<opt::TestIterationCount>();					}
int						CommandLine::getSurfaceWidth				(void) const	{ return m_cmdLine.getOption<opt::SurfaceWidth>();							}
int						CommandLine::getSurfaceHeight				(void) const	{ return m_cmdLine.getOption<opt::SurfaceHeight>();							}
SurfaceType				CommandLine::getSurfaceType					(void) const	{ return m_cmdLine.getOption<opt::SurfaceType>();							}
ScreenRotation			CommandLine::getScreenRotation				(void) const	{ return m_cmdLine.getOption<opt::ScreenRotation>();						}
int						CommandLine::getGLConfigId					(void) const	{ return m_cmdLine.getOption<opt::GLConfigID>();							}
int						CommandLine::getCLPlatformId				(void) const	{ return m_cmdLine.getOption<opt::CLPlatformID>();							}
const std::vector<int>&	CommandLine::getCLDeviceIds					(void) const	{ return m_cmdLine.getOption<opt::CLDeviceIDs>();							}
int						CommandLine::getVKDeviceId					(void) const	{ return m_cmdLine.getOption<opt::VKDeviceID>();							}
int						CommandLine::getVKDeviceGroupId				(void) const	{ return m_cmdLine.getOption<opt::VKDeviceGroupID>();						}
bool					CommandLine::isValidationEnabled			(void) const	{ return m_cmdLine.getOption<opt::Validation>();							}
bool					CommandLine::printValidationErrors			(void) const	{ return m_cmdLine.getOption<opt::PrintValidationErrors>();					}
bool					CommandLine::isOutOfMemoryTestEnabled		(void) const	{ return m_cmdLine.getOption<opt::TestOOM>();								}
bool					CommandLine::isShadercacheEnabled			(void) const	{ return m_cmdLine.getOption<opt::ShaderCache>();							}
const char*				CommandLine::getShaderCacheFilename			(void) const	{ return m_cmdLine.getOption<opt::ShaderCacheFilename>().c_str();			}
bool					CommandLine::isShaderCacheTruncateEnabled	(void) const	{ return m_cmdLine.getOption<opt::ShaderCacheTruncate>();					}
int						CommandLine::getOptimizationRecipe			(void) const	{ return m_cmdLine.getOption<opt::Optimization>();							}
bool					CommandLine::isSpirvOptimizationEnabled		(void) const	{ return m_cmdLine.getOption<opt::OptimizeSpirv>();							}
bool					CommandLine::isRenderDocEnabled				(void) const	{ return m_cmdLine.getOption<opt::RenderDoc>();								}
const char*				CommandLine::getWaiverFileName				(void) const	{ return m_cmdLine.getOption<opt::WaiverFile>().c_str();					}
const std::vector<int>&	CommandLine::getCaseFraction				(void) const	{ return m_cmdLine.getOption<opt::CaseFraction>();							}
const char*				CommandLine::getCaseFractionMandatoryTests	(void) const	{ return m_cmdLine.getOption<opt::CaseFractionMandatoryTests>().c_str();	}
const char*				CommandLine::getArchiveDir					(void) const	{ return m_cmdLine.getOption<opt::ArchiveDir>().c_str();					}
tcu::TestRunnerType		CommandLine::getRunnerType					(void) const	{ return m_cmdLine.getOption<opt::RunnerType>();							}

const char* CommandLine::getGLContextType (void) const
{
	if (m_cmdLine.hasOption<opt::GLContextType>())
		return m_cmdLine.getOption<opt::GLContextType>().c_str();
	else
		return DE_NULL;
}
const char* CommandLine::getGLConfigName (void) const
{
	if (m_cmdLine.hasOption<opt::GLConfigName>())
		return m_cmdLine.getOption<opt::GLConfigName>().c_str();
	else
		return DE_NULL;
}

const char* CommandLine::getGLContextFlags (void) const
{
	if (m_cmdLine.hasOption<opt::GLContextFlags>())
		return m_cmdLine.getOption<opt::GLContextFlags>().c_str();
	else
		return DE_NULL;
}

const char* CommandLine::getCLBuildOptions (void) const
{
	if (m_cmdLine.hasOption<opt::CLBuildOptions>())
		return m_cmdLine.getOption<opt::CLBuildOptions>().c_str();
	else
		return DE_NULL;
}

const char* CommandLine::getEGLDisplayType (void) const
{
	if (m_cmdLine.hasOption<opt::EGLDisplayType>())
		return m_cmdLine.getOption<opt::EGLDisplayType>().c_str();
	else
		return DE_NULL;
}

const char* CommandLine::getEGLWindowType (void) const
{
	if (m_cmdLine.hasOption<opt::EGLWindowType>())
		return m_cmdLine.getOption<opt::EGLWindowType>().c_str();
	else
		return DE_NULL;
}

const char* CommandLine::getEGLPixmapType (void) const
{
	if (m_cmdLine.hasOption<opt::EGLPixmapType>())
		return m_cmdLine.getOption<opt::EGLPixmapType>().c_str();
	else
		return DE_NULL;
}

static bool checkTestGroupName (const CaseTreeNode* root, const char* groupPath)
{
	const CaseTreeNode* node = findNode(root, groupPath);
	return node && node->hasChildren();
}

static bool checkTestCaseName (const CaseTreeNode* root, const char* casePath)
{
	const CaseTreeNode* node = findNode(root, casePath);
	return node && !node->hasChildren();
}

de::MovePtr<CaseListFilter> CommandLine::createCaseListFilter (const tcu::Archive& archive) const
{
	return de::MovePtr<CaseListFilter>(new CaseListFilter(m_cmdLine, archive));
}

bool CaseListFilter::checkTestGroupName (const char* groupName) const
{
	bool result = false;
	if (m_casePaths)
		result = m_casePaths->matches(groupName, true);
	else if (m_caseTree)
		result = ( groupName[0] == 0 || tcu::checkTestGroupName(m_caseTree, groupName) );
	else
		return true;
	if (!result && m_caseFractionMandatoryTests.get() != DE_NULL)
		result = m_caseFractionMandatoryTests->matches(groupName, true);
	return result;
}

bool CaseListFilter::checkTestCaseName (const char* caseName) const
{
	bool result = false;
	if (m_casePaths)
		result = m_casePaths->matches(caseName, false);
	else if (m_caseTree)
		result = tcu::checkTestCaseName(m_caseTree, caseName);
	else
		return true;
	if (!result && m_caseFractionMandatoryTests.get() != DE_NULL)
		result = m_caseFractionMandatoryTests->matches(caseName, false);
	return result;
}

bool CaseListFilter::checkCaseFraction (int i, const std::string& testCaseName) const
{
	return	m_caseFraction.size() != 2 ||
		((i % m_caseFraction[1]) == m_caseFraction[0]) ||
		(m_caseFractionMandatoryTests.get()!=DE_NULL && m_caseFractionMandatoryTests->matches(testCaseName));
}

CaseListFilter::CaseListFilter (void)
	: m_caseTree	(DE_NULL)
	, m_runnerType	(tcu::RUNNERTYPE_ANY)
{
}

CaseListFilter::CaseListFilter (const de::cmdline::CommandLine& cmdLine, const tcu::Archive& archive)
	: m_caseTree	(DE_NULL)
	, m_runnerType	(cmdLine.getOption<opt::RunnerType>())
{
	if (cmdLine.hasOption<opt::CaseList>())
	{
		std::istringstream str(cmdLine.getOption<opt::CaseList>());

		m_caseTree = parseCaseList(str);
	}
	else if (cmdLine.hasOption<opt::CaseListFile>())
	{
		std::ifstream in(cmdLine.getOption<opt::CaseListFile>().c_str(), std::ios_base::binary);

		if (!in.is_open() || !in.good())
			throw Exception("Failed to open case list file '" + cmdLine.getOption<opt::CaseListFile>() + "'");

		m_caseTree = parseCaseList(in);
	}
	else if (cmdLine.hasOption<opt::CaseListResource>())
	{
		// \todo [2016-11-14 pyry] We are cloning potentially large buffers here. Consider writing
		//						   istream adaptor for tcu::Resource.
		de::UniquePtr<Resource>	caseListResource	(archive.getResource(cmdLine.getOption<opt::CaseListResource>().c_str()));
		const int				bufferSize			= caseListResource->getSize();
		std::vector<char>		buffer				((size_t)bufferSize);

		if (buffer.empty())
			throw Exception("Empty case list resource");

		caseListResource->read(reinterpret_cast<deUint8*>(&buffer[0]), bufferSize);

		{
			std::istringstream	in	(std::string(&buffer[0], (size_t)bufferSize));

			m_caseTree = parseCaseList(in);
		}
	}
	else if (cmdLine.getOption<opt::StdinCaseList>())
	{
		m_caseTree = parseCaseList(std::cin);
	}
	else if (cmdLine.hasOption<opt::CasePath>())
		m_casePaths = de::MovePtr<const CasePaths>(new CasePaths(cmdLine.getOption<opt::CasePath>()));

	m_caseFraction = cmdLine.getOption<opt::CaseFraction>();

	if (m_caseFraction.size() == 2 &&
		(m_caseFraction[0] < 0 || m_caseFraction[1] <= 0 || m_caseFraction[0] >= m_caseFraction[1] ))
		throw Exception("Invalid case fraction. First element must be non-negative and less than second element. Second element must be greater than 0.");

	if (m_caseFraction.size() != 0 && m_caseFraction.size() != 2)
		throw Exception("Invalid case fraction. Must have two components.");

	if (m_caseFraction.size() == 2)
	{
		std::string					caseFractionMandatoryTestsFilename = cmdLine.getOption<opt::CaseFractionMandatoryTests>();

		if (!caseFractionMandatoryTestsFilename.empty())
		{
			std::ifstream fileStream(caseFractionMandatoryTestsFilename.c_str(), std::ios_base::binary);
			if (!fileStream.is_open() || !fileStream.good())
				throw Exception("Failed to open case fraction mandatory test list: '" + caseFractionMandatoryTestsFilename + "'");

			std::vector<std::string>	cfPaths;
			std::string					line;

			while (std::getline(fileStream, line))
			{
				line.erase(std::remove(std::begin(line), std::end(line), '\r'), std::end(line));
				cfPaths.push_back(line);
			}
			if (!cfPaths.empty())
			{
				m_caseFractionMandatoryTests = de::MovePtr<const CasePaths>(new CasePaths(cfPaths));
				if (m_caseTree != DE_NULL)
				{
					fileStream.clear();
					fileStream.seekg(0, fileStream.beg);
					parseCaseList(m_caseTree, fileStream, false);
				}
			}
		}
	}
}

CaseListFilter::~CaseListFilter (void)
{
	delete m_caseTree;
}

} // tcu