// Copyright (C) 2020 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.

#include <gtest/gtest.h>

#include <vector>

#include "OSWindow.h"
#include "aemu/base/system/System.h"
#include "host-common/testing/MockGraphicsAgentFactory.h"
#include "virgl_hw.h"
#include "gfxstream/virtio-gpu-gfxstream-renderer-unstable.h"
#include "gfxstream/virtio-gpu-gfxstream-renderer.h"

using android::base::sleepMs;

class GfxStreamBackendTest : public ::testing::Test {
private:
 static void sWriteFence(void* cookie, struct stream_renderer_fence* fence) {
     uint32_t current = *(uint32_t*)cookie;
     if (current < fence->fence_id) *(uint64_t*)(cookie) = fence->fence_id;
 }

protected:
    uint32_t cookie;
    static const bool useWindow;
    std::vector<stream_renderer_param> streamRendererParams;
    std::vector<stream_renderer_param> minimumRequiredParams;
    static constexpr uint32_t width = 256;
    static constexpr uint32_t height = 256;
    static std::unique_ptr<OSWindow> window;
    static constexpr int rendererFlags = STREAM_RENDERER_FLAGS_USE_GLES_BIT;
    static constexpr int surfacelessFlags = STREAM_RENDERER_FLAGS_USE_SURFACELESS_BIT;

    GfxStreamBackendTest()
        : cookie(0),
          streamRendererParams{{STREAM_RENDERER_PARAM_USER_DATA,
                                static_cast<uint64_t>(reinterpret_cast<uintptr_t>(&cookie))},
                               {STREAM_RENDERER_PARAM_FENCE_CALLBACK,
                                static_cast<uint64_t>(reinterpret_cast<uintptr_t>(&sWriteFence))},
                               {STREAM_RENDERER_PARAM_RENDERER_FLAGS, surfacelessFlags},
                               {STREAM_RENDERER_PARAM_WIN0_WIDTH, width},
                               {STREAM_RENDERER_PARAM_WIN0_HEIGHT, height}},
          minimumRequiredParams{{STREAM_RENDERER_PARAM_USER_DATA,
                                 static_cast<uint64_t>(reinterpret_cast<uintptr_t>(&cookie))},
                                {STREAM_RENDERER_PARAM_FENCE_CALLBACK,
                                 static_cast<uint64_t>(reinterpret_cast<uintptr_t>(&sWriteFence))},
                                {STREAM_RENDERER_PARAM_RENDERER_FLAGS, surfacelessFlags}} {}

    static void SetUpTestSuite() {
        android::emulation::injectGraphicsAgents(android::emulation::MockGraphicsAgentFactory());
        if (useWindow) {
            window.reset(CreateOSWindow());
        }
    }

    static void TearDownTestSuite() { window.reset(nullptr); }

    void SetUp() override {
        android::base::setEnvironmentVariable("ANDROID_GFXSTREAM_EGL", "1");
        if (useWindow) {
            window->initialize("GfxStreamBackendTestWindow", width, height);
            window->setVisible(true);
            window->messageLoop();
        }
    }

    void TearDown() override {
        // Ensure background threads aren't mid-initialization.
        sleepMs(100);
        if (useWindow) {
            window->destroy();
        }
        stream_renderer_teardown();
    }
};

std::unique_ptr<OSWindow> GfxStreamBackendTest::window = nullptr;

const bool GfxStreamBackendTest::useWindow =
        android::base::getEnvironmentVariable("ANDROID_EMU_TEST_WITH_WINDOW") == "1";

TEST_F(GfxStreamBackendTest, Init) {
    stream_renderer_init(streamRendererParams.data(), streamRendererParams.size());
}

TEST_F(GfxStreamBackendTest, InitOpenGLWindow) {
    if (!useWindow) {
        return;
    }

    std::vector<stream_renderer_param> glParams = streamRendererParams;
    for (auto& param: glParams) {
        if (param.key == STREAM_RENDERER_PARAM_RENDERER_FLAGS) {
            param.value = rendererFlags;
        }
    }
    stream_renderer_init(glParams.data(), glParams.size());
    gfxstream_backend_setup_window(window->getFramebufferNativeWindow(), 0, 0,
                                       width, height, width, height);
}

TEST_F(GfxStreamBackendTest, SimpleFlush) {
    stream_renderer_init(streamRendererParams.data(), streamRendererParams.size());

    const uint32_t res_id = 8;
    struct stream_renderer_resource_create_args create_resource_args = {
        .handle = res_id,
        .target = 2,  // PIPE_TEXTURE_2D
        .format = VIRGL_FORMAT_R8G8B8A8_UNORM,
        .bind = VIRGL_BIND_SAMPLER_VIEW | VIRGL_BIND_SCANOUT | VIRGL_BIND_SHARED,
        .width = width,
        .height = height,
        .depth = 1,
        .array_size = 1,
        .last_level = 0,
        .nr_samples = 0,
        .flags = 0,
    };
    EXPECT_EQ(stream_renderer_resource_create(&create_resource_args, NULL, 0), 0);
    // R8G8B8A8 is used, so 4 bytes per pixel
    auto fb = std::make_unique<uint32_t[]>(width * height);
    EXPECT_NE(fb, nullptr);
    stream_renderer_flush(res_id);
}

// Tests compile and link only.
TEST_F(GfxStreamBackendTest, DISABLED_ApiCallLinkTest) {
    stream_renderer_init(streamRendererParams.data(), streamRendererParams.size());

    const uint32_t res_id = 8;
    struct stream_renderer_resource_create_args create_resource_args = {
        .handle = res_id,
        .target = 2,  // PIPE_TEXTURE_2D
        .format = VIRGL_FORMAT_R8G8B8A8_UNORM,
        .bind = VIRGL_BIND_SAMPLER_VIEW | VIRGL_BIND_SCANOUT | VIRGL_BIND_SHARED,
        .width = width,
        .height = height,
        .depth = 1,
        .array_size = 1,
        .last_level = 0,
        .nr_samples = 0,
        .flags = 0,
    };
    EXPECT_EQ(stream_renderer_resource_create(&create_resource_args, NULL, 0), 0);
    struct stream_renderer_command cmd;
    cmd.ctx_id = 0;
    cmd.cmd = nullptr;
    cmd.cmd_size = 0;

    // R8G8B8A8 is used, so 4 bytes per pixel
    auto fb = std::make_unique<uint32_t[]>(width * height);
    EXPECT_NE(fb, nullptr);
    stream_renderer_flush(res_id);
    stream_renderer_resource_unref(0);
    stream_renderer_context_create(0, 0, NULL, 0);
    stream_renderer_context_destroy(0);
    stream_renderer_submit_cmd(&cmd);
    stream_renderer_transfer_read_iov(0, 0, 0, 0, 0, 0, 0, 0, 0);
    stream_renderer_transfer_write_iov(0, 0, 0, 0, 0, 0, 0, 0, 0);

    stream_renderer_get_cap_set(0, 0, 0);
    stream_renderer_fill_caps(0, 0, 0);

    stream_renderer_resource_attach_iov(0, 0, 0);
    stream_renderer_resource_detach_iov(0, 0, 0);
    stream_renderer_create_fence(NULL);
    stream_renderer_ctx_attach_resource(0, 0);
    stream_renderer_ctx_detach_resource(0, 0);
    stream_renderer_resource_get_info(0, 0);
}

TEST_F(GfxStreamBackendTest, MinimumRequiredParameters) {
    // Only the minimum required parameters.
    int initResult =
        stream_renderer_init(minimumRequiredParams.data(), minimumRequiredParams.size());
    EXPECT_EQ(initResult, 0);
}

TEST_F(GfxStreamBackendTest, MissingRequiredParameter) {
    for (size_t i = 0; i < minimumRequiredParams.size(); ++i) {
        // For each parameter, remove it and try to init, expecting failure.
        std::vector<stream_renderer_param> insufficientParams = minimumRequiredParams;
        insufficientParams.erase(insufficientParams.begin() + i);
        int initResult = stream_renderer_init(insufficientParams.data(), insufficientParams.size());
        EXPECT_NE(initResult, 0);

        // Ensure background threads aren't mid-initialization.
        sleepMs(100);
        stream_renderer_teardown();
    }

    // Initialize once more for the teardown function.
    int initResult = stream_renderer_init(streamRendererParams.data(), streamRendererParams.size());
    EXPECT_EQ(initResult, 0);
}
