/*
 * Copyright 2022 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * This runs *all* the tests registered via the Test registry in series. If one or more fail, those
 * error messages will be printed out and a non-zero exit code will be returned. Otherwise, the
 * exit code will be 0.
 */

#include "include/core/SkString.h"
#include "include/core/SkTypes.h"
#include "include/private/base/SkDebug.h"
#include "tests/Test.h"
#include "tests/TestHarness.h"
#include "tools/flags/CommandLineFlags.h"
#include "tools/testrunners/common/TestRunner.h"

#if defined(SK_GANESH)
#include "include/gpu/ganesh/GrContextOptions.h"
#include "include/gpu/ganesh/GrDirectContext.h"
#include "include/gpu/ganesh/GrTypes.h"
#include "include/private/gpu/ganesh/GrTypesPriv.h"
#include "tools/ganesh/TestContext.h"
#include "tools/gpu/ContextType.h"
#endif

#include <ctime>
#include <cwchar>
#include <functional>
#include <iomanip>
#include <sstream>
#include <string>

struct tm;

static DEFINE_string(skip, "", "Space-separated list of test cases (regexps) to skip.");
static DEFINE_string(
        match,
        "",
        "Space-separated list of test cases (regexps) to run. Will run all tests if omitted.");

// Set in //bazel/devicesrc but consumed by other C++ test runners.
static DEFINE_string(key, "", "Ignored by this test runner.");
static DEFINE_string(cpuName, "", "Ignored by this test runner.");
static DEFINE_string(gpuName, "", "Ignored by this test runner.");

// Set in //bazel/devicesrc but only consumed by adb_test_runner.go. We cannot use the
// DEFINE_string macro because the flag name includes dashes.
[[maybe_unused]] static bool unused =
        SkFlagInfo::CreateStringFlag("device-specific-bazel-config",
                                     nullptr,
                                     new CommandLineFlags::StringArray(),
                                     nullptr,
                                     "Ignored by this test runner.",
                                     nullptr);

class BazelReporter : public skiatest::Reporter {
public:
    void reportFailed(const skiatest::Failure& failure) override {
        TestRunner::Log("FAIL: %s", failure.toString().c_str());
        fFailed = true;
    }
    bool allowExtendedTest() const override { return false; }
    bool verbose() const override { return false; }
    bool ok() { return !fFailed; }

private:
    bool fFailed = false;
};

#if defined(SK_GANESH)
namespace skiatest {
bool IsGLContextType(skgpu::ContextType type) {
    return skgpu::ganesh::ContextTypeBackend(type) == GrBackendApi::kOpenGL;
}
bool IsVulkanContextType(skgpu::ContextType type) {
    return skgpu::ganesh::ContextTypeBackend(type) == GrBackendApi::kVulkan;
}
bool IsMetalContextType(skgpu::ContextType type) {
    return skgpu::ganesh::ContextTypeBackend(type) == GrBackendApi::kMetal;
}
bool IsDirect3DContextType(skgpu::ContextType type) {
    return skgpu::ganesh::ContextTypeBackend(type) == GrBackendApi::kDirect3D;
}
bool IsMockContextType(skgpu::ContextType type) { return type == skgpu::ContextType::kMock; }

skgpu::ContextType compiledInContextTypes[] = {
#if defined(SK_GL)
// Use "native" instead of explicitly trying both OpenGL and OpenGL ES. Do not use GLES on
// desktop since tests do not account for not fixing skbug.com/40033921
#if defined(SK_BUILD_FOR_UNIX) || defined(SK_BUILD_FOR_WIN) || defined(SK_BUILD_FOR_MAC)
        skgpu::ContextType::kGL,
#else
        skgpu::ContextType::kGLES,
#endif
#endif  // defined(SK_GL)
#if defined(SK_VULKAN)
        skgpu::ContextType::kVulkan,
#endif
#if defined(SK_DAWN)
        skgpu::ContextType::kDawn,
#endif
        // TODO(kjlubick) Other Ganesh backends
        skgpu::ContextType::kMock,
};

// The macros defined in Test.h eventually call into this function. For each GPU backend that is
// compiled in, we run the testFn with a freshly created
void RunWithGaneshTestContexts(GrContextTestFn* testFn,
                               ContextTypeFilterFn* filter,
                               Reporter* reporter,
                               const GrContextOptions& options) {
    sk_gpu_test::GrContextFactory factory(options);

    for (skgpu::ContextType ctxType : compiledInContextTypes) {
        if (filter && !(*filter)(ctxType)) {
            continue;
        }

        sk_gpu_test::ContextInfo ctxInfo = factory.getContextInfo(ctxType);
        if (ctxInfo.directContext()) {
            (*testFn)(reporter, ctxInfo);
            // In case the test changed the current context make sure we move it back before
            // calling flush.
            ctxInfo.testContext()->makeCurrent();
            // Sync so any release/finished procs get called.
            ctxInfo.directContext()->flushAndSubmit(GrSyncCpu::kYes);
        } else {
            TestRunner::Log("Unable to make direct context for Ganesh test.");
            SkASSERT(false);
            return;
        }
    }
}

}  // namespace skiatest
#endif  // #if defined(SK_GANESH)

TestHarness CurrentTestHarness() { return TestHarness::kBazelUnitTestRunner; }

void maybeRunTest(const char* name, std::function<void()> testFn) {
    if (!TestRunner::ShouldRunTestCase(name, FLAGS_match, FLAGS_skip)) {
        TestRunner::Log("Skipping %s", name);
        return;
    }

    TestRunner::Log("Running %s", name);
    testFn();
    TestRunner::Log("\tDone");
}

int main(int argc, char** argv) {
    TestRunner::InitAndLogCmdlineArgs(argc, argv);

    CommandLineFlags::Parse(argc, argv);

    BazelReporter reporter;
    for (skiatest::Test test : skiatest::TestRegistry::Range()) {
        if (test.fTestType == skiatest::TestType::kCPU) {
            maybeRunTest(test.fName, [&]() { test.cpu(&reporter); });
        }
    }

#if defined(SK_GANESH)
    GrContextOptions grCtxOptions;
    // TODO(kjlubick) DM has grContextOptions set via flags. Should this runner have that too?
    grCtxOptions.fExecutor = nullptr;
    grCtxOptions.fAllowPathMaskCaching = true;
    grCtxOptions.fFailFlushTimeCallbacks = false;
    grCtxOptions.fAllPathsVolatile = false;
    grCtxOptions.fGpuPathRenderers = GpuPathRenderers::kDefault;
    grCtxOptions.fDisableDriverCorrectnessWorkarounds = false;
    grCtxOptions.fResourceCacheLimitOverride = -1;
    grCtxOptions.fReduceOpsTaskSplitting = GrContextOptions::Enable::kNo;
    for (skiatest::Test test : skiatest::TestRegistry::Range()) {
        if (test.fTestType == skiatest::TestType::kGanesh) {
            maybeRunTest(test.fName, [&]() { test.ganesh(&reporter, grCtxOptions); });
        }
    }
#endif

    // TODO(kjlubick) Graphite support

    if (reporter.ok()) {
        TestRunner::Log("PASS");
        return 0;
    }
    TestRunner::Log("FAIL");
    return 1;
}
