/*
 * Copyright 2023 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#ifndef GraphiteVulkanWindowContext_DEFINED
#define GraphiteVulkanWindowContext_DEFINED

#include "include/core/SkTypes.h"

#ifdef SK_VULKAN

#include "include/core/SkSurface.h"
#include "include/private/base/SkTemplates.h"
#include "tools/gpu/vk/VkTestUtils.h"
#include "tools/window/WindowContext.h"

namespace skgpu {
struct VulkanInterface;
}
namespace skwindow::internal {
class GraphiteVulkanWindowContext : public WindowContext {
public:
    ~GraphiteVulkanWindowContext() override;

    sk_sp<SkSurface> getBackbufferSurface() override;

    bool isValid() override { return fDevice != VK_NULL_HANDLE; }

    void resize(int w, int h) override { this->createSwapchain(w, h); }

    void setDisplayParams(std::unique_ptr<const DisplayParams> params) override {
        this->destroyContext();
        fDisplayParams = std::move(params);
        this->initializeContext();
    }

    /* Platform specific function that creates a VkSurfaceKHR for a window */
    using CreateVkSurfaceFn = std::function<VkSurfaceKHR(VkInstance)>;
    /* Platform specific function that determines whether presentation will succeed. */
    using CanPresentFn = sk_gpu_test::CanPresentFn;

    GraphiteVulkanWindowContext(std::unique_ptr<const DisplayParams>,
                                CreateVkSurfaceFn,
                                CanPresentFn,
                                PFN_vkGetInstanceProcAddr);

private:
    void initializeContext();
    void destroyContext();

    bool createSwapchain(int width, int height);
    bool populateSwapchainImages(VkFormat, VkImageUsageFlags, VkSharingMode);

    /**
     * Swap backbuffers/frames, presenting the next available image.
     */
    void onSwapBuffers() override;

    /**
     * Define private method to submit work to the GPU (rather than using Window::submitToGpu) in
     * order to properly configure semaphores. Submits work to render the image at
     * fCurrentImageIndex. Return false upon failure.
     */
    bool submitToGpu();

    /**
     * Reset the container of images (and destroy each image's associated rendering semaphore).
     */
    void resetSwapchainImages();

    /**
     * Define a struct which represents a swapchain image to be presented. Each SwapchainImage
     * contains the native VkImage, its image layout while not being used as a color attachment
     * (which we initialize as VK_IMAGE_LAYOUT_UNDEFINED), a semaphore for signaling render
     * completion, and an SkSurface the client renders to.
     */
    struct SwapchainImage {
        VkImage fVkImage = VK_NULL_HANDLE;
        VkImageLayout fImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
        VkSemaphore fRenderCompletionSemaphore = VK_NULL_HANDLE;
        sk_sp<SkSurface> fSurface = nullptr; /* May not be based on rts */
    };

    skia_private::AutoTArray<SwapchainImage> fImages;
    uint32_t fCurrentImageIndex; /* Index of image currently being presented */
    VkSemaphore fAcquireSemaphore = VK_NULL_HANDLE; /* Semaphore to signal image acquisition */
    sk_sp<const skgpu::VulkanInterface> fInterface;

    /* Vulkan driver structs + info */
    VkInstance fInstance = VK_NULL_HANDLE;
    VkPhysicalDevice fPhysicalDevice = VK_NULL_HANDLE;
    VkDevice fDevice = VK_NULL_HANDLE;
    VkDebugUtilsMessengerEXT fDebugMessenger = VK_NULL_HANDLE;
    VkSurfaceKHR fDeviceSurface;
    VkSwapchainKHR fSwapchain;
    uint32_t fGraphicsQueueIndex;
    VkQueue fGraphicsQueue;
    uint32_t fPresentQueueIndex;
    VkQueue fPresentQueue;

    /* Create functions */
    CreateVkSurfaceFn fCreateVkSurfaceFn;
    CanPresentFn fCanPresentFn;
    PFN_vkGetInstanceProcAddr fGetInstanceProcAddr = nullptr;

    /* WSI interface functions */
    PFN_vkDestroySurfaceKHR fDestroySurfaceKHR = nullptr;
    PFN_vkGetPhysicalDeviceSurfaceSupportKHR fGetPhysicalDeviceSurfaceSupportKHR = nullptr;
    PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR fGetPhysicalDeviceSurfaceCapabilitiesKHR =
            nullptr;
    PFN_vkGetPhysicalDeviceSurfaceFormatsKHR fGetPhysicalDeviceSurfaceFormatsKHR = nullptr;
    PFN_vkGetPhysicalDeviceSurfacePresentModesKHR fGetPhysicalDeviceSurfacePresentModesKHR =
            nullptr;
    PFN_vkCreateSwapchainKHR fCreateSwapchainKHR = nullptr;
    PFN_vkDestroySwapchainKHR fDestroySwapchainKHR = nullptr;
    PFN_vkGetSwapchainImagesKHR fGetSwapchainImagesKHR = nullptr;
    PFN_vkAcquireNextImageKHR fAcquireNextImageKHR = nullptr;
    PFN_vkQueuePresentKHR fQueuePresentKHR = nullptr;

    PFN_vkDestroyInstance fDestroyInstance = nullptr;
    PFN_vkDeviceWaitIdle fDeviceWaitIdle = nullptr;
    PFN_vkDestroyDebugUtilsMessengerEXT fDestroyDebugUtilsMessengerEXT = nullptr;
    PFN_vkQueueWaitIdle fQueueWaitIdle = nullptr;
    PFN_vkDestroyDevice fDestroyDevice = nullptr;
    PFN_vkGetDeviceQueue fGetDeviceQueue = nullptr;
};

}  // namespace skwindow::internal

#endif  // SK_VULKAN

#endif  // GraphiteVulkanWindowContext_DEFINED
