/*-------------------------------------------------------------------------
 * 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>
#include <unordered_map>

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(LogDecompiledSpirv, bool);
DE_DECLARE_COMMAND_LINE_OPT(LogEmptyLoginfo, 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(LogCompact, 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(ShaderCacheIPC, 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);
DE_DECLARE_COMMAND_LINE_OPT(TerminateOnFail, bool);
DE_DECLARE_COMMAND_LINE_OPT(TerminateOnDeviceLost, bool);
DE_DECLARE_COMMAND_LINE_OPT(SubProcess, bool);
DE_DECLARE_COMMAND_LINE_OPT(SubprocessTestCount, int);
DE_DECLARE_COMMAND_LINE_OPT(SubprocessConfigFile, std::string);
DE_DECLARE_COMMAND_LINE_OPT(ServerAddress, std::string);
DE_DECLARE_COMMAND_LINE_OPT(CommandPoolMinSize, int);
DE_DECLARE_COMMAND_LINE_OPT(CommandBufferMinSize, int);
DE_DECLARE_COMMAND_LINE_OPT(CommandDefaultSize, int);
DE_DECLARE_COMMAND_LINE_OPT(PipelineDefaultSize, int);
DE_DECLARE_COMMAND_LINE_OPT(PipelineCompilerPath, std::string);
DE_DECLARE_COMMAND_LINE_OPT(PipelineCompilerDataDir, std::string);
DE_DECLARE_COMMAND_LINE_OPT(PipelineCompilerArgs, std::string);
DE_DECLARE_COMMAND_LINE_OPT(PipelineCompilerOutputFile, std::string);
DE_DECLARE_COMMAND_LINE_OPT(PipelineCompilerLogFile, std::string);
DE_DECLARE_COMMAND_LINE_OPT(PipelineCompilerFilePrefix, std::string);
DE_DECLARE_COMMAND_LINE_OPT(VkLibraryPath, std::string);
DE_DECLARE_COMMAND_LINE_OPT(ApplicationParametersInputFile, std::string);
DE_DECLARE_COMMAND_LINE_OPT(QuietStdout, bool);
DE_DECLARE_COMMAND_LINE_OPT(ComputeOnly, bool);

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::NamedValue;
    using de::cmdline::Option;

    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},
                                                                        {"amber-verify", RUNMODE_VERIFY_AMBER_COHERENCY}};
    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<QuietStdout>("q", "quiet", "Suppress messages to standard output")
        << 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, write list of test cases into a file, or verify amber capability coherency",
                           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<LogDecompiledSpirv>(DE_NULL, "deqp-log-decompiled-spirv",
                                      "Enable or disable logging of decompiled spir-v", s_enableNames, "enable")
        << Option<LogEmptyLoginfo>(DE_NULL, "deqp-log-empty-loginfo", "Logging of empty shader compile/link log info",
                                   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<LogCompact>(DE_NULL, "deqp-log-compact", "Enable or disable the compact version of the log",
                              s_enableNames, "disable")
        << 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<ShaderCacheIPC>(DE_NULL, "deqp-shadercache-ipc", "Should shader cache use inter process comms",
                                  s_enableNames, "disable")
        << 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")
        << Option<TerminateOnFail>(DE_NULL, "deqp-terminate-on-fail", "Terminate the run on first failure",
                                   s_enableNames, "disable")
        << Option<TerminateOnDeviceLost>(DE_NULL, "deqp-terminate-on-device-lost",
                                         "Terminate the run on a device lost error", s_enableNames, "disable")
        << Option<SubProcess>(DE_NULL, "deqp-subprocess",
                              "Inform app that it works as subprocess (Vulkan SC only, do not use manually)",
                              s_enableNames, "disable")
        << Option<SubprocessTestCount>(
               DE_NULL, "deqp-subprocess-test-count",
               "Define default number of tests performed in subprocess for specific test cases(Vulkan SC only)",
               "65536")
        << Option<SubprocessConfigFile>(DE_NULL, "deqp-subprocess-cfg-file",
                                        "Config file defining number of tests performed in subprocess for specific "
                                        "test branches (Vulkan SC only)",
                                        "")
        << Option<ServerAddress>(DE_NULL, "deqp-server-address",
                                 "Server address (host:port) responsible for shader compilation (Vulkan SC only)", "")
        << Option<CommandPoolMinSize>(DE_NULL, "deqp-command-pool-min-size",
                                      "Define minimum size of the command pool (in bytes) to use (Vulkan SC only)", "0")
        << Option<CommandBufferMinSize>(DE_NULL, "deqp-command-buffer-min-size",
                                        "Define minimum size of the command buffer (in bytes) to use (Vulkan SC only)",
                                        "0")
        << Option<CommandDefaultSize>(DE_NULL, "deqp-command-default-size",
                                      "Define default single command size (in bytes) to use (Vulkan SC only)", "256")
        << Option<PipelineDefaultSize>(DE_NULL, "deqp-pipeline-default-size",
                                       "Define default pipeline size (in bytes) to use (Vulkan SC only)", "16384")
        << Option<PipelineCompilerPath>(DE_NULL, "deqp-pipeline-compiler",
                                        "Path to offline pipeline compiler (Vulkan SC only)", "")
        << Option<PipelineCompilerDataDir>(DE_NULL, "deqp-pipeline-dir",
                                           "Offline pipeline data directory (Vulkan SC only)", "")
        << Option<PipelineCompilerArgs>(DE_NULL, "deqp-pipeline-args",
                                        "Additional compiler parameters (Vulkan SC only)", "")
        << Option<PipelineCompilerOutputFile>(DE_NULL, "deqp-pipeline-file",
                                              "Output file with pipeline cache (Vulkan SC only, do not use manually)",
                                              "")
        << Option<PipelineCompilerLogFile>(DE_NULL, "deqp-pipeline-logfile",
                                           "Log file for pipeline compiler (Vulkan SC only, do not use manually)", "")
        << Option<PipelineCompilerFilePrefix>(
               DE_NULL, "deqp-pipeline-prefix",
               "Prefix for input pipeline compiler files (Vulkan SC only, do not use manually)", "")
        << Option<VkLibraryPath>(DE_NULL, "deqp-vk-library-path",
                                 "Path to Vulkan library (e.g. loader library vulkan-1.dll)", "")
        << Option<ApplicationParametersInputFile>(DE_NULL, "deqp-app-params-input-file",
                                                  "File that provides a default set of application parameters")
        << Option<ComputeOnly>(DE_NULL, "deqp-compute-only",
                               "Perform tests for devices implementing compute-only functionality", s_enableNames,
                               "disable");
}

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");
}

} // namespace opt

// Used to store hashes of test case names
typedef uint64_t test_case_hash_t;

// Source: https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp
// MurmurHash2, 64-bit versions, by Austin Appleby
static uint64_t MurmurHash64B(const void *key, int len, uint64_t seed)
{
    const uint32_t m = 0x5bd1e995;
    const int r      = 24;

    uint32_t h1 = uint32_t(seed) ^ len;
    uint32_t h2 = uint32_t(seed >> 32);

    // Ensure that unaligned accesses to data are allowed.
#ifdef WIN32
    typedef __declspec(align(1)) uint32_t uint32_t_unaligned;
#else
    typedef __attribute__((aligned(1))) uint32_t uint32_t_unaligned;
#endif
    const uint32_t_unaligned *data = (const uint32_t_unaligned *)key;

    while (len >= 8)
    {
        uint32_t k1 = *data++;
        k1 *= m;
        k1 ^= k1 >> r;
        k1 *= m;
        h1 *= m;
        h1 ^= k1;
        len -= 4;

        uint32_t k2 = *data++;
        k2 *= m;
        k2 ^= k2 >> r;
        k2 *= m;
        h2 *= m;
        h2 ^= k2;
        len -= 4;
    }

    if (len >= 4)
    {
        uint32_t k1 = *data++;
        k1 *= m;
        k1 ^= k1 >> r;
        k1 *= m;
        h1 *= m;
        h1 ^= k1;
        len -= 4;
    }

    switch (len)
    {
    case 3:
        h2 ^= ((unsigned char *)data)[2] << 16;
        // fall through
    case 2:
        h2 ^= ((unsigned char *)data)[1] << 8;
        // fall through
    case 1:
        h2 ^= ((unsigned char *)data)[0];
        h2 *= m;
    };

    h1 ^= h2 >> 18;
    h1 *= m;
    h2 ^= h1 >> 22;
    h2 *= m;
    h1 ^= h2 >> 17;
    h1 *= m;
    h2 ^= h1 >> 19;
    h2 *= m;

    uint64_t h = h1;

    h = (h << 32) | h2;

    return h;
}

/*--------------------------------------------------------------------*//*!
 * \brief Generates an hash for the test case name part provided.
 * If a hashCollisionDetectionMap is passed, will detect hash
 * collisions using that map. hashCollisionDetectionMap can be NULL.
 * As an example, the standard std::hash<std::string> on a 32-bit
 * machine will collide with 'random_298' and 'subgroupand_int16_t_mesh_requiredsubgroupsize'
 *//*--------------------------------------------------------------------*/
static test_case_hash_t hashTestNodeName(const std::string &name,
                                         std::unordered_map<test_case_hash_t, std::string> *hashCollisionDetectionMap)
{
    test_case_hash_t hash = MurmurHash64B(name.c_str(), (int)name.length(), 1);
    if (hashCollisionDetectionMap != nullptr)
    {
        auto search = hashCollisionDetectionMap->find(hash);
        if (search != hashCollisionDetectionMap->end())
        {
            if (search->second != name)
            {
                print("There was an hash collision between '%s' and '%s'\n", search->second.c_str(), name.c_str());
                throw std::runtime_error("Hash collision detected!");
            }
        }
        hashCollisionDetectionMap->insert({hash, name});
    }
    return hash;
}

// \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)(uint8_t)(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 test_case_hash_t hash) : m_hash(hash)
    {
    }
    ~CaseTreeNode(void);

    test_case_hash_t getHash(void) const
    {
        return m_hash;
    }
    bool hasChildren(void) const
    {
        return !m_children.empty();
    }

    bool hasChild(test_case_hash_t hash) const;
    CaseTreeNode *getChild(test_case_hash_t hash) const;

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

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

    enum
    {
        NOT_FOUND = -1
    };

    int findChildNdx(test_case_hash_t hash) const;

    test_case_hash_t m_hash;
    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(test_case_hash_t hash) const
{
    for (int ndx = 0; ndx < (int)m_children.size(); ++ndx)
    {
        if (m_children[ndx]->getHash() == hash)
            return ndx;
    }
    return NOT_FOUND;
}

inline bool CaseTreeNode::hasChild(test_case_hash_t hash) const
{
    return findChildNdx(hash) != NOT_FOUND;
}

inline CaseTreeNode *CaseTreeNode::getChild(test_case_hash_t hash) const
{
    const int ndx = findChildNdx(hash);
    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 (;;)
    {
        test_case_hash_t hash = hashTestNodeName(std::string(curPath, curPath + curLen), nullptr);
        curNode               = curNode->getChild(hash);

        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,
                          std::unordered_map<test_case_hash_t, string> &hashCollisionDetectionMap)
{
    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)
            {
                test_case_hash_t hash        = hashTestNodeName(curName, &hashCollisionDetectionMap);
                CaseTreeNode *const newChild = new CaseTreeNode(hash);

                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 parseSimpleCaseList(vector<CaseTreeNode *> &nodeStack, std::istream &in, bool reportDuplicates,
                                std::unordered_map<test_case_hash_t, string> &hashCollisionDetectionMap)
{
    // \note Algorithm assumes that cases are sorted by groups, but will
    //         function fine, albeit more slowly, if that is not the case.
    int stackPos = 0;
    string curName;

    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");

            test_case_hash_t hash = hashTestNodeName(curName, &hashCollisionDetectionMap);
            if (!nodeStack[stackPos]->hasChild(hash))
            {
                CaseTreeNode *const newChild = new CaseTreeNode(hash);

                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);

            test_case_hash_t hash = hashTestNodeName(curName, &hashCollisionDetectionMap);
            if (!nodeStack[stackPos + 1] || nodeStack[stackPos + 1]->getHash() != hash)
            {
                CaseTreeNode *curGroup = nodeStack[stackPos]->getChild(hash);

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

                    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]->getHash() == hash);

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

static void parseCaseList(CaseTreeNode *root, std::istream &in, bool reportDuplicates,
                          std::unordered_map<test_case_hash_t, string> &hashCollisionDetectionMap)
{
    vector<CaseTreeNode *> nodeStack(8, root);
    parseSimpleCaseList(nodeStack, in, reportDuplicates, hashCollisionDetectionMap);
}

static void parseGroupFile(CaseTreeNode *root, std::istream &inGroupList, const tcu::Archive &archive,
                           bool reportDuplicates,
                           std::unordered_map<test_case_hash_t, string> &hashCollisionDetectionMap)
{
    // read whole file and remove all '\r'
    std::string buffer(std::istreambuf_iterator<char>(inGroupList), {});
    buffer.erase(std::remove(buffer.begin(), buffer.end(), '\r'), buffer.end());

    vector<CaseTreeNode *> nodeStack(8, root);
    std::stringstream namesStream(buffer);
    std::string fileName;

    while (std::getline(namesStream, fileName))
    {
        de::FilePath groupPath(fileName);
        de::UniquePtr<Resource> groupResource(archive.getResource(groupPath.normalize().getPath()));
        const int groupBufferSize(groupResource->getSize());
        std::vector<char> groupBuffer(static_cast<size_t>(groupBufferSize));

        groupResource->read(reinterpret_cast<uint8_t *>(&groupBuffer[0]), groupBufferSize);
        if (groupBuffer.empty())
            throw Exception("Empty case list resource");

        std::istringstream groupIn(std::string(groupBuffer.begin(), groupBuffer.end()));
        parseSimpleCaseList(nodeStack, groupIn, reportDuplicates, hashCollisionDetectionMap);
    }
}

static CaseTreeNode *parseCaseList(std::istream &in, const tcu::Archive &archive, const char *path = DE_NULL)
{
    std::unordered_map<test_case_hash_t, std::string> hashCollisionDetectionMap{};
    auto rootName            = "";
    test_case_hash_t hash    = hashTestNodeName(rootName, &hashCollisionDetectionMap);
    CaseTreeNode *const root = new CaseTreeNode(hash);
    try
    {
        if (in.peek() == '{')
            parseCaseTrie(root, in, hashCollisionDetectionMap);
        else
        {
            // if we are reading cases from file determine if we are
            // reading group file or plain list of cases; this is done by
            // reading single line and checking if it ends with ".txt"
            bool readGroupFile = false;
            if (path)
            {
                // read the first line and make sure it doesn't contain '\r'
                std::string line;
                std::getline(in, line);
                line.erase(std::remove(line.begin(), line.end(), '\r'), line.end());

                const std::string ending = ".txt";
                readGroupFile =
                    (line.length() > ending.length()) && std::equal(ending.rbegin(), ending.rend(), line.rbegin());

                // move to the beginning of the file to parse first line too
                in.seekg(0, in.beg);
            }

            if (readGroupFile)
                parseGroupFile(root, in, archive, true, hashCollisionDetectionMap);
            else
                parseCaseList(root, in, true, hashCollisionDetectionMap);
        }

        {
            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 == '*')
    {
        string::const_iterator patternNext = pattern + 1;
        if (patternNext != patternEnd)
        {
            for (; path != pathEnd; ++path)
            {
                if (*patternNext == *path)
                    if (matchWildcards(patternNext, 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
{
#if defined(TCU_HIERARCHICAL_CASEPATHS)
    const vector<string> components = de::splitString(caseName, '.');
#endif

    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_appName(), m_logFlags(0), m_hadHelpSpecified(false)
{
}

/*--------------------------------------------------------------------*//*!
 * \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_appName(argv[0])
    , m_logFlags(0)
    , m_hadHelpSpecified(false)
{
    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))
    {
        if (m_hadHelpSpecified)
            exit(EXIT_SUCCESS);
        else
            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_appName(), m_initialCmdLine(cmdLine), m_hadHelpSpecified(false)
{
    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::getApplicationName(void) const
{
    return m_appName;
}

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);

        // We need to save this to avoid exiting with error later, and before the clear() call that wipes its value.
        m_hadHelpSpecified = m_cmdLine.helpSpecified();

        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.getOption<opt::LogCompact>())
        m_logFlags |= QP_TEST_LOG_COMPACT;

    if (!m_cmdLine.getOption<opt::LogEmptyLoginfo>())
        m_logFlags |= QP_TEST_LOG_EXCLUDE_EMPTY_LOGINFO;

    if (m_cmdLine.getOption<opt::SubProcess>())
        m_logFlags |= QP_TEST_LOG_NO_INITIAL_OUTPUT;

    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;
    }

    if (m_cmdLine.getArgs().size() > 0)
    {
        debugOut << "ERROR: arguments not starting with '-' or '--' are not supported by this application!\n"
                 << std::endl;

        debugOut << "\n" << de::FilePath(argv[0]).getBaseName() << " [options]\n\n";
        parser.help(debugOut);

        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;
}

bool CommandLine::quietMode(void) const
{
    return m_cmdLine.getOption<opt::QuietStdout>();
}
const char *CommandLine::getLogFileName(void) const
{
    return m_cmdLine.getOption<opt::LogFilename>().c_str();
}
uint32_t 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::isLogDecompiledSpirvEnabled(void) const
{
    return m_cmdLine.getOption<opt::LogDecompiledSpirv>();
}
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>();
}
bool CommandLine::isShaderCacheIPCEnabled(void) const
{
    return m_cmdLine.getOption<opt::ShaderCacheIPC>();
}
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>();
}
bool CommandLine::isTerminateOnFailEnabled(void) const
{
    return m_cmdLine.getOption<opt::TerminateOnFail>();
}
bool CommandLine::isTerminateOnDeviceLostEnabled(void) const
{
    return m_cmdLine.getOption<opt::TerminateOnDeviceLost>();
}
bool CommandLine::isSubProcess(void) const
{
    return m_cmdLine.getOption<opt::SubProcess>();
}
int CommandLine::getSubprocessTestCount(void) const
{
    return m_cmdLine.getOption<opt::SubprocessTestCount>();
}
int CommandLine::getCommandPoolMinSize(void) const
{
    return m_cmdLine.getOption<opt::CommandPoolMinSize>();
}
int CommandLine::getCommandBufferMinSize(void) const
{
    return m_cmdLine.getOption<opt::CommandBufferMinSize>();
}
int CommandLine::getCommandDefaultSize(void) const
{
    return m_cmdLine.getOption<opt::CommandDefaultSize>();
}
int CommandLine::getPipelineDefaultSize(void) const
{
    return m_cmdLine.getOption<opt::PipelineDefaultSize>();
}
bool CommandLine::isComputeOnly(void) const
{
    return m_cmdLine.getOption<opt::ComputeOnly>();
}

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;
}

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

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

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

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

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

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

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

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

const char *CommandLine::getVkLibraryPath(void) const
{
    if (m_cmdLine.hasOption<opt::VkLibraryPath>())
        return (m_cmdLine.getOption<opt::VkLibraryPath>() != "") ? m_cmdLine.getOption<opt::VkLibraryPath>().c_str() :
                                                                   DE_NULL;
    else
        return DE_NULL;
}

const char *CommandLine::getAppParamsInputFilePath(void) const
{
    if (m_cmdLine.hasOption<opt::ApplicationParametersInputFile>())
        return m_cmdLine.getOption<opt::ApplicationParametersInputFile>().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)
{
    if (cmdLine.getOption<opt::RunMode>() == RUNMODE_VERIFY_AMBER_COHERENCY)
    {
        m_runnerType = RUNNERTYPE_AMBER;
    }
    else
    {
        m_runnerType = cmdLine.getOption<opt::RunnerType>();
    }

    if (cmdLine.hasOption<opt::CaseList>())
    {
        std::istringstream str(cmdLine.getOption<opt::CaseList>());

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

        if (!in.is_open() || !in.good())
            throw Exception("Failed to open case list file '" + caseListFile + "'");

        m_caseTree = parseCaseList(in, archive, caseListFile.c_str());
    }
    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<uint8_t *>(&buffer[0]), bufferSize);

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

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

    if (!cmdLine.getOption<opt::SubProcess>())
        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);
                    std::unordered_map<test_case_hash_t, std::string> hashCollisionDetectionMap{};
                    parseCaseList(m_caseTree, fileStream, false, hashCollisionDetectionMap);
                }
            }
        }
    }
}

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

} // namespace tcu
