/* Copyright (c) 2015-2016 The Khronos Group Inc.
 * Copyright (c) 2015-2016 Valve Corporation
 * Copyright (c) 2015-2016 LunarG, Inc.
 * Copyright (C) 2015-2016 Google Inc.
 *
 * 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.
 *
 * Author: Ian Elliott <ian@lunarg.com>
 * Author: Ian Elliott <ianelliott@google.com>
 */

#ifndef SWAPCHAIN_H
#define SWAPCHAIN_H

#include "vulkan/vk_layer.h"
#include "vk_layer_config.h"
#include "vk_layer_logging.h"
#include <vector>
#include <unordered_map>

using namespace std;

// Swapchain ERROR codes
enum SWAPCHAIN_ERROR {
    SWAPCHAIN_INVALID_HANDLE,             // Handle used that isn't currently valid
    SWAPCHAIN_NULL_POINTER,               // Pointer set to NULL, instead of being a valid pointer
    SWAPCHAIN_EXT_NOT_ENABLED_BUT_USED,   // Did not enable WSI extension, but called WSI function
    SWAPCHAIN_DEL_OBJECT_BEFORE_CHILDREN, // Called vkDestroyDevice() before vkDestroySwapchainKHR()
    SWAPCHAIN_CREATE_UNSUPPORTED_SURFACE, // Called vkCreateSwapchainKHR() with a pCreateInfo->surface that wasn't seen as supported
                                          // by vkGetPhysicalDeviceSurfaceSupportKHR for the device
    SWAPCHAIN_CREATE_SWAP_WITHOUT_QUERY,  // Called vkCreateSwapchainKHR() without calling a query (e.g.
                                          // vkGetPhysicalDeviceSurfaceCapabilitiesKHR())
    SWAPCHAIN_CREATE_SWAP_BAD_MIN_IMG_COUNT,     // Called vkCreateSwapchainKHR() with out-of-bounds minImageCount
    SWAPCHAIN_CREATE_SWAP_OUT_OF_BOUNDS_EXTENTS, // Called vkCreateSwapchainKHR() with out-of-bounds imageExtent
    SWAPCHAIN_CREATE_SWAP_EXTENTS_NO_MATCH_WIN, // Called vkCreateSwapchainKHR() with imageExtent that doesn't match window's extent
    SWAPCHAIN_CREATE_SWAP_BAD_PRE_TRANSFORM,    // Called vkCreateSwapchainKHR() with a non-supported preTransform
    SWAPCHAIN_CREATE_SWAP_BAD_COMPOSITE_ALPHA,  // Called vkCreateSwapchainKHR() with a non-supported compositeAlpha
    SWAPCHAIN_CREATE_SWAP_BAD_IMG_ARRAY_LAYERS, // Called vkCreateSwapchainKHR() with a non-supported imageArrayLayers
    SWAPCHAIN_CREATE_SWAP_BAD_IMG_USAGE_FLAGS,  // Called vkCreateSwapchainKHR() with a non-supported imageUsageFlags
    SWAPCHAIN_CREATE_SWAP_BAD_IMG_COLOR_SPACE,  // Called vkCreateSwapchainKHR() with a non-supported imageColorSpace
    SWAPCHAIN_CREATE_SWAP_BAD_IMG_FORMAT,       // Called vkCreateSwapchainKHR() with a non-supported imageFormat
    SWAPCHAIN_CREATE_SWAP_BAD_IMG_FMT_CLR_SP,   // Called vkCreateSwapchainKHR() with a non-supported imageColorSpace
    SWAPCHAIN_CREATE_SWAP_BAD_PRESENT_MODE,     // Called vkCreateSwapchainKHR() with a non-supported presentMode
    SWAPCHAIN_CREATE_SWAP_BAD_SHARING_MODE,     // Called vkCreateSwapchainKHR() with a non-supported imageSharingMode
    SWAPCHAIN_CREATE_SWAP_BAD_SHARING_VALUES,   // Called vkCreateSwapchainKHR() with bad values when imageSharingMode is
                                                // VK_SHARING_MODE_CONCURRENT
    SWAPCHAIN_CREATE_SWAP_DIFF_SURFACE, // Called vkCreateSwapchainKHR() with pCreateInfo->oldSwapchain that has a different surface
                                        // than pCreateInfo->surface
    SWAPCHAIN_DESTROY_SWAP_DIFF_DEVICE, // Called vkDestroySwapchainKHR() with a different VkDevice than vkCreateSwapchainKHR()
    SWAPCHAIN_APP_ACQUIRES_TOO_MANY_IMAGES, // vkAcquireNextImageKHR() asked for more images than are available
    SWAPCHAIN_INDEX_TOO_LARGE,          // Index is too large for swapchain
    SWAPCHAIN_INDEX_NOT_IN_USE,         // vkQueuePresentKHR() given index that is not acquired by app
    SWAPCHAIN_BAD_BOOL,                 // VkBool32 that doesn't have value of VK_TRUE or VK_FALSE (e.g. is a non-zero form of true)
    SWAPCHAIN_PRIOR_COUNT,              // Query must be called first to get value of pCount, then called second time
    SWAPCHAIN_INVALID_COUNT,            // Second time a query called, the pCount value didn't match first time
    SWAPCHAIN_WRONG_STYPE,              // The sType for a struct has the wrong value
    SWAPCHAIN_WRONG_NEXT,               // The pNext for a struct is not NULL
    SWAPCHAIN_ZERO_VALUE,               // A value should be non-zero
    SWAPCHAIN_INCOMPATIBLE_ALLOCATOR,   // pAllocator must be compatible (i.e. NULL or not) when object is created and destroyed
    SWAPCHAIN_DID_NOT_QUERY_QUEUE_FAMILIES,     // A function using a queueFamilyIndex was called before
                                                // vkGetPhysicalDeviceQueueFamilyProperties() was called
    SWAPCHAIN_QUEUE_FAMILY_INDEX_TOO_LARGE,     // A queueFamilyIndex value is not less than pQueueFamilyPropertyCount returned by
                                                // vkGetPhysicalDeviceQueueFamilyProperties()
    SWAPCHAIN_SURFACE_NOT_SUPPORTED_WITH_QUEUE, // A surface is not supported by a given queueFamilyIndex, as seen by
                                                // vkGetPhysicalDeviceSurfaceSupportKHR()
    SWAPCHAIN_NO_SYNC_FOR_ACQUIRE,      // vkAcquireNextImageKHR should be called with a valid semaphore and/or fence
    SWAPCHAIN_GET_SUPPORTED_DISPLAYS_WITHOUT_QUERY,     // vkGetDisplayPlaneSupportedDisplaysKHR should be called after querying 
                                                        // device display plane properties
    SWAPCHAIN_PLANE_INDEX_TOO_LARGE,    // a planeIndex value is larger than what vkGetDisplayPlaneSupportedDisplaysKHR returns
};

// The following is for logging error messages:
const char * swapchain_layer_name = "Swapchain";

#define LAYER_NAME (char *) "Swapchain"

// NOTE: The following struct's/typedef's are for keeping track of
// info that is used for validating the WSI extensions.

// Forward declarations:
struct SwpInstance;
struct SwpSurface;
struct SwpPhysicalDevice;
struct SwpDevice;
struct SwpSwapchain;
struct SwpImage;
struct SwpQueue;

// Create one of these for each VkInstance:
struct SwpInstance {
    // The actual handle for this VkInstance:
    VkInstance instance;

    // Remember the VkSurfaceKHR's that are created for this VkInstance:
    unordered_map<VkSurfaceKHR, SwpSurface *> surfaces;

    // When vkEnumeratePhysicalDevices is called, the VkPhysicalDevice's are
    // remembered:
    unordered_map<const void *, SwpPhysicalDevice *> physicalDevices;

    // Set to true if VK_KHR_SURFACE_EXTENSION_NAME was enabled for this VkInstance:
    bool surfaceExtensionEnabled;

    // Set to true if VK_KHR_DISPLAY_EXTENSION_NAME was enabled for this VkInstance:
    bool displayExtensionEnabled;

// TODO: Add additional booleans for platform-specific extensions:
#ifdef VK_USE_PLATFORM_ANDROID_KHR
    // Set to true if VK_KHR_ANDROID_SURFACE_EXTENSION_NAME was enabled for this VkInstance:
    bool androidSurfaceExtensionEnabled;
#endif // VK_USE_PLATFORM_ANDROID_KHR
#ifdef VK_USE_PLATFORM_MIR_KHR
    // Set to true if VK_KHR_MIR_SURFACE_EXTENSION_NAME was enabled for this VkInstance:
    bool mirSurfaceExtensionEnabled;
#endif // VK_USE_PLATFORM_MIR_KHR
#ifdef VK_USE_PLATFORM_WAYLAND_KHR
    // Set to true if VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME was enabled for this VkInstance:
    bool waylandSurfaceExtensionEnabled;
#endif // VK_USE_PLATFORM_WAYLAND_KHR
#ifdef VK_USE_PLATFORM_WIN32_KHR
    // Set to true if VK_KHR_WIN32_SURFACE_EXTENSION_NAME was enabled for this VkInstance:
    bool win32SurfaceExtensionEnabled;
#endif // VK_USE_PLATFORM_WIN32_KHR
#ifdef VK_USE_PLATFORM_XCB_KHR
    // Set to true if VK_KHR_XCB_SURFACE_EXTENSION_NAME was enabled for this VkInstance:
    bool xcbSurfaceExtensionEnabled;
#endif // VK_USE_PLATFORM_XCB_KHR
#ifdef VK_USE_PLATFORM_XLIB_KHR
    // Set to true if VK_KHR_XLIB_SURFACE_EXTENSION_NAME was enabled for this VkInstance:
    bool xlibSurfaceExtensionEnabled;
#endif // VK_USE_PLATFORM_XLIB_KHR
};

// Create one of these for each VkSurfaceKHR:
struct SwpSurface {
    // The actual handle for this VkSurfaceKHR:
    VkSurfaceKHR surface;

    // VkInstance that this VkSurfaceKHR is associated with:
    SwpInstance *pInstance;

    // When vkCreateSwapchainKHR is called, the VkSwapchainKHR's are
    // remembered:
    unordered_map<VkSwapchainKHR, SwpSwapchain *> swapchains;

    // 'true' if pAllocator was non-NULL when vkCreate*SurfaceKHR was called:
    bool usedAllocatorToCreate;

    // Value of pQueueFamilyPropertyCount that was returned by the
    // vkGetPhysicalDeviceQueueFamilyProperties() function:
    uint32_t numQueueFamilyIndexSupport;
    // Array of VkBool32's that is intialized by the
    // vkGetPhysicalDeviceSurfaceSupportKHR() function.  First call for a given
    // surface allocates and initializes this array to false for all
    // queueFamilyIndex's (and sets numQueueFamilyIndexSupport to non-zero).
    // All calls set the entry for a given queueFamilyIndex:
    VkBool32 *pQueueFamilyIndexSupport;
};

// Create one of these for each VkPhysicalDevice within a VkInstance:
struct SwpPhysicalDevice {
    // The actual handle for this VkPhysicalDevice:
    VkPhysicalDevice physicalDevice;

    // Corresponding VkDevice (and info) to this VkPhysicalDevice:
    SwpDevice *pDevice;

    // VkInstance that this VkPhysicalDevice is associated with:
    SwpInstance *pInstance;

    // Records results of vkGetPhysicalDeviceQueueFamilyProperties()'s
    // numOfQueueFamilies parameter when pQueueFamilyProperties is NULL:
    bool gotQueueFamilyPropertyCount;
    uint32_t numOfQueueFamilies;

    // Record all surfaces that vkGetPhysicalDeviceSurfaceSupportKHR() was
    // called for:
    unordered_map<VkSurfaceKHR, SwpSurface *> supportedSurfaces;

    // TODO: Record/use this info per-surface, not per-device, once a
    // non-dispatchable surface object is added to WSI:
    // Results of vkGetPhysicalDeviceSurfaceCapabilitiesKHR():
    bool gotSurfaceCapabilities;
    VkSurfaceCapabilitiesKHR surfaceCapabilities;

    // TODO: Record/use this info per-surface, not per-device, once a
    // non-dispatchable surface object is added to WSI:
    // Count and VkSurfaceFormatKHR's returned by vkGetPhysicalDeviceSurfaceFormatsKHR():
    uint32_t surfaceFormatCount;
    VkSurfaceFormatKHR *pSurfaceFormats;

    // TODO: Record/use this info per-surface, not per-device, once a
    // non-dispatchable surface object is added to WSI:
    // Count and VkPresentModeKHR's returned by vkGetPhysicalDeviceSurfacePresentModesKHR():
    uint32_t presentModeCount;
    VkPresentModeKHR *pPresentModes;

    // Count returned by vkGetPhysicalDeviceDisplayPlanePropertiesKHR():
    uint32_t displayPlanePropertyCount;
    bool gotDisplayPlanePropertyCount;
};

// Create one of these for each VkDevice within a VkInstance:
struct SwpDevice {
    // The actual handle for this VkDevice:
    VkDevice device;

    // Corresponding VkPhysicalDevice (and info) to this VkDevice:
    SwpPhysicalDevice *pPhysicalDevice;

    // Set to true if VK_KHR_SWAPCHAIN_EXTENSION_NAME was enabled:
    bool swapchainExtensionEnabled;

    // Set to true if VK_KHR_DISPLAY_SWAPCHAIN_EXTENSION_NAME was enabled:
    bool displaySwapchainExtensionEnabled;

    // When vkCreateSwapchainKHR is called, the VkSwapchainKHR's are
    // remembered:
    unordered_map<VkSwapchainKHR, SwpSwapchain *> swapchains;

    // When vkGetDeviceQueue is called, the VkQueue's are remembered:
    unordered_map<VkQueue, SwpQueue *> queues;
};

// Create one of these for each VkImage within a VkSwapchainKHR:
struct SwpImage {
    // The actual handle for this VkImage:
    VkImage image;

    // Corresponding VkSwapchainKHR (and info) to this VkImage:
    SwpSwapchain *pSwapchain;

    // true if application acquired this image from vkAcquireNextImageKHR(),
    // and hasn't yet called vkQueuePresentKHR() for it; otherwise false:
    bool acquiredByApp;
};

// Create one of these for each VkSwapchainKHR within a VkDevice:
struct SwpSwapchain {
    // The actual handle for this VkSwapchainKHR:
    VkSwapchainKHR swapchain;

    // Corresponding VkDevice (and info) to this VkSwapchainKHR:
    SwpDevice *pDevice;

    // Corresponding VkSurfaceKHR to this VkSwapchainKHR:
    SwpSurface *pSurface;

    // When vkGetSwapchainImagesKHR is called, the VkImage's are
    // remembered:
    uint32_t imageCount;
    unordered_map<int, SwpImage> images;

    // 'true' if pAllocator was non-NULL when vkCreateSwapchainKHR was called:
    bool usedAllocatorToCreate;
};

// Create one of these for each VkQueue within a VkDevice:
struct SwpQueue {
    // The actual handle for this VkQueue:
    VkQueue queue;

    // Corresponding VkDevice (and info) to this VkSwapchainKHR:
    SwpDevice *pDevice;

    // Which queueFamilyIndex this VkQueue is associated with:
    uint32_t queueFamilyIndex;
};

struct layer_data {
    VkInstance instance;

    debug_report_data *report_data;
    std::vector<VkDebugReportCallbackEXT> logging_callback;
    VkLayerDispatchTable *device_dispatch_table;
    VkLayerInstanceDispatchTable *instance_dispatch_table;

    // The following are for keeping track of the temporary callbacks that can
    // be used in vkCreateInstance and vkDestroyInstance:
    uint32_t num_tmp_callbacks;
    VkDebugReportCallbackCreateInfoEXT *tmp_dbg_create_infos;
    VkDebugReportCallbackEXT *tmp_callbacks;

    // NOTE: The following are for keeping track of info that is used for
    // validating the WSI extensions.
    std::unordered_map<void *, SwpInstance> instanceMap;
    std::unordered_map<VkSurfaceKHR, SwpSurface> surfaceMap;
    std::unordered_map<void *, SwpPhysicalDevice> physicalDeviceMap;
    std::unordered_map<void *, SwpDevice> deviceMap;
    std::unordered_map<VkSwapchainKHR, SwpSwapchain> swapchainMap;
    std::unordered_map<void *, SwpQueue> queueMap;

    layer_data()
        : report_data(nullptr), device_dispatch_table(nullptr), instance_dispatch_table(nullptr), num_tmp_callbacks(0),
          tmp_dbg_create_infos(nullptr), tmp_callbacks(nullptr){};
};

#endif // SWAPCHAIN_H
