//
// Copyright 2015 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// deqp_libtester_main.cpp: Entry point for tester DLL.

#include <cstdio>
#include <iostream>

#include "angle_deqp_libtester.h"
#include "common/angleutils.h"
#include "deMath.h"
#include "deUniquePtr.hpp"
#include "platform/Platform.h"
#include "system_utils.h"
#include "tcuApp.hpp"
#include "tcuCommandLine.hpp"
#include "tcuDefs.hpp"
#include "tcuPlatform.hpp"
#include "tcuRandomOrderExecutor.h"
#include "tcuResource.hpp"
#include "tcuTestLog.hpp"

#if (DE_OS == DE_OS_WIN32)
#    include <Windows.h>
#elif (DE_OS == DE_OS_UNIX) || (DE_OS == DE_OS_OSX)
#    include <sys/stat.h>
#    include <sys/types.h>
#    include <sys/unistd.h>
#elif (DE_OS == DE_OS_ANDROID)
#    include <sys/stat.h>
#endif

tcu::Platform *CreateANGLEPlatform(angle::LogErrorFunc logErrorFunc);

namespace
{

tcu::Platform *g_platform            = nullptr;
tcu::CommandLine *g_cmdLine          = nullptr;
tcu::DirArchive *g_archive           = nullptr;
tcu::TestLog *g_log                  = nullptr;
tcu::TestContext *g_testCtx          = nullptr;
tcu::TestPackageRoot *g_root         = nullptr;
tcu::RandomOrderExecutor *g_executor = nullptr;

const char *g_dEQPDataSearchDirs[] = {
    "../../../third_party/deqp/src/data",
    "../../sdcard/chromium_tests_root/third_party/angle/third_party/deqp/src/data",
    "../../third_party/angle/third_party/deqp/src/data",
    "../../third_party/deqp/src/data",
    "../third_party/deqp/src/data",
    "data",
    "third_party/deqp/src/data",
};

// TODO(jmadill): upstream to dEQP?
#if (DE_OS == DE_OS_WIN32)
deBool deIsDir(const char *filename)
{
    WIN32_FILE_ATTRIBUTE_DATA fileInformation;

    BOOL result = GetFileAttributesExA(filename, GetFileExInfoStandard, &fileInformation);
    if (result)
    {
        DWORD attribs = fileInformation.dwFileAttributes;
        return (attribs != INVALID_FILE_ATTRIBUTES) && ((attribs & FILE_ATTRIBUTE_DIRECTORY) > 0);
    }

    return false;
}
#elif (DE_OS == DE_OS_UNIX) || (DE_OS == DE_OS_OSX) || (DE_OS == DE_OS_ANDROID)
deBool deIsDir(const char *filename)
{
    struct stat st;
    int result = stat(filename, &st);
    return result == 0 && ((st.st_mode & S_IFDIR) == S_IFDIR);
}
#else
#    error TODO(jmadill): support other platforms
#endif

bool FindDataDir(std::string *dataDirOut)
{
    for (auto searchDir : g_dEQPDataSearchDirs)
    {
        if (deIsDir((std::string(searchDir) + "/gles2").c_str()))
        {
            *dataDirOut = searchDir;
            return true;
        }

        std::stringstream dirStream;
        dirStream << angle::GetExecutableDirectory() << "/" << searchDir;
        std::string dataDir = dirStream.str();
        dirStream << "/gles2";
        std::string searchPath = dirStream.str();

        if (deIsDir(searchPath.c_str()))
        {
            *dataDirOut = dataDir;
            return true;
        }
    }

    return false;
}

std::string GetLogFileName(std::string deqpDataDir)
{
#if (DE_OS == DE_OS_ANDROID)
    // On Android executable dir is not writable, so use data dir instead
    return deqpDataDir + "/" + g_cmdLine->getLogFileName();
#else
    return g_cmdLine->getLogFileName();
#endif
}

}  // anonymous namespace

ANGLE_LIBTESTER_EXPORT bool deqp_libtester_init_platform(int argc,
                                                         const char *argv[],
                                                         void *logErrorFunc)
{
    try
    {
#if (DE_OS != DE_OS_WIN32)
        // Set stdout to line-buffered mode (will be fully buffered by default if stdout is pipe).
        setvbuf(stdout, DE_NULL, _IOLBF, 4 * 1024);
#endif
        g_platform = CreateANGLEPlatform(reinterpret_cast<angle::LogErrorFunc>(logErrorFunc));

        if (!deSetRoundingMode(DE_ROUNDINGMODE_TO_NEAREST_EVEN))
        {
            std::cout << "Failed to set floating point rounding mode." << std::endl;
            return false;
        }

        std::string deqpDataDir;
        if (!FindDataDir(&deqpDataDir))
        {
            std::cout << "Failed to find dEQP data directory." << std::endl;
            return false;
        }

        g_cmdLine = new tcu::CommandLine(argc, argv);
        g_archive = new tcu::DirArchive(deqpDataDir.c_str());
        g_log     = new tcu::TestLog(GetLogFileName(deqpDataDir).c_str(), g_cmdLine->getLogFlags());
        g_testCtx = new tcu::TestContext(*g_platform, *g_archive, *g_log, *g_cmdLine, DE_NULL);
        g_root     = new tcu::TestPackageRoot(*g_testCtx, tcu::TestPackageRegistry::getSingleton());
        g_executor = new tcu::RandomOrderExecutor(*g_root, *g_testCtx);
    }
    catch (const std::exception &e)
    {
        tcu::die("%s", e.what());
        return false;
    }

    return true;
}

// Exported to the tester app.
ANGLE_LIBTESTER_EXPORT int deqp_libtester_main(int argc, const char *argv[])
{
    if (!deqp_libtester_init_platform(argc, argv, nullptr))
    {
        tcu::die("Could not initialize the dEQP platform");
    }

    try
    {
        de::UniquePtr<tcu::App> app(new tcu::App(*g_platform, *g_archive, *g_log, *g_cmdLine));

        // Main loop.
        for (;;)
        {
            if (!app->iterate())
                break;
        }
    }
    catch (const std::exception &e)
    {
        deqp_libtester_shutdown_platform();
        tcu::die("%s", e.what());
    }

    deqp_libtester_shutdown_platform();
    return 0;
}

ANGLE_LIBTESTER_EXPORT void deqp_libtester_shutdown_platform()
{
    delete g_executor;
    g_executor = nullptr;
    delete g_root;
    g_root = nullptr;
    delete g_testCtx;
    g_testCtx = nullptr;
    delete g_log;
    g_log = nullptr;
    delete g_archive;
    g_archive = nullptr;
    delete g_cmdLine;
    g_cmdLine = nullptr;
    delete g_platform;
    g_platform = nullptr;
}

ANGLE_LIBTESTER_EXPORT TestResult deqp_libtester_run(const char *caseName)
{
    const char *emptyString = "";
    if (g_platform == nullptr && !deqp_libtester_init_platform(1, &emptyString, nullptr))
    {
        tcu::die("Failed to initialize platform.");
    }

    try
    {
        // Poll platform events
        const bool platformOk = g_platform->processEvents();

        if (platformOk)
        {
            const tcu::TestStatus &result = g_executor->execute(caseName);
            switch (result.getCode())
            {
                case QP_TEST_RESULT_PASS:
                    return TestResult::Pass;
                case QP_TEST_RESULT_NOT_SUPPORTED:
                    std::cout << "Not supported! " << result.getDescription() << std::endl;
                    return TestResult::NotSupported;
                case QP_TEST_RESULT_QUALITY_WARNING:
                    std::cout << "Quality warning! " << result.getDescription() << std::endl;
                    return TestResult::Pass;
                case QP_TEST_RESULT_COMPATIBILITY_WARNING:
                    std::cout << "Compatiblity warning! " << result.getDescription() << std::endl;
                    return TestResult::Pass;
                default:
                    return TestResult::Fail;
            }
        }
        else
        {
            std::cout << "Aborted test!" << std::endl;
        }
    }
    catch (const std::exception &e)
    {
        std::cout << "Exception running test: " << e.what() << std::endl;
        return TestResult::Exception;
    }

    return TestResult::Fail;
}
