/**
 ******************************************************************************
 * Xenia : Xbox 360 Emulator Research Project                                 *
 ******************************************************************************
 * Copyright 2014 Ben Vanik. All rights reserved.                             *
 * Released under the BSD license - see LICENSE in the root for more details. *
 ******************************************************************************
 */

#ifndef XENIA_UI_VULKAN_VULKAN_DEVICE_H_
#define XENIA_UI_VULKAN_VULKAN_DEVICE_H_

#include <memory>
#include <mutex>
#include <string>
#include <vector>

#include "xenia/ui/vulkan/vulkan.h"
#include "xenia/ui/vulkan/vulkan_util.h"

namespace xe {
namespace ui {
namespace vulkan {

class VulkanInstance;

// Wrapper and utilities for VkDevice.
// Prefer passing this around over a VkDevice and casting as needed to call
// APIs.
class VulkanDevice {
 public:
  VulkanDevice(VulkanInstance* instance);
  ~VulkanDevice();

  VkDevice handle = nullptr;

  operator VkDevice() const { return handle; }
  operator VkPhysicalDevice() const { return device_info_.handle; }

  // Declares a layer to verify and enable upon initialization.
  // Must be called before Initialize.
  void DeclareRequiredLayer(std::string name, uint32_t min_version,
                            bool is_optional) {
    required_layers_.push_back({name, min_version, is_optional});
  }

  // Declares an extension to verify and enable upon initialization.
  // Must be called before Initialize.
  void DeclareRequiredExtension(std::string name, uint32_t min_version,
                                bool is_optional) {
    required_extensions_.push_back({name, min_version, is_optional});
  }

  // Initializes the device, querying and enabling extensions and layers and
  // preparing the device for general use.
  // If initialization succeeds it's likely that no more failures beyond runtime
  // issues will occur.
  bool Initialize(DeviceInfo device_info);

  bool HasEnabledExtension(const char* name);

  uint32_t queue_family_index() const { return queue_family_index_; }
  std::mutex& primary_queue_mutex() { return queue_mutex_; }
  // Access to the primary queue must be synchronized with primary_queue_mutex.
  VkQueue primary_queue() const { return primary_queue_; }
  const DeviceInfo& device_info() const { return device_info_; }

  // Acquires a queue for exclusive use by the caller.
  // The queue will not be touched by any other code until it's returned with
  // ReleaseQueue.
  // Not all devices support queues or only support a limited number. If this
  // returns null the primary_queue should be used with the
  // primary_queue_mutex.
  // This method is thread safe.
  VkQueue AcquireQueue(uint32_t queue_family_index);
  // Releases a queue back to the device pool.
  // This method is thread safe.
  void ReleaseQueue(VkQueue queue, uint32_t queue_family_index);

  void DbgSetObjectName(uint64_t object, VkDebugReportObjectTypeEXT object_type,
                        std::string name);

  void DbgMarkerBegin(VkCommandBuffer command_buffer, std::string name,
                      float r = 0.0f, float g = 0.0f, float b = 0.0f,
                      float a = 0.0f);
  void DbgMarkerEnd(VkCommandBuffer command_buffer);

  void DbgMarkerInsert(VkCommandBuffer command_buffer, std::string name,
                       float r = 0.0f, float g = 0.0f, float b = 0.0f,
                       float a = 0.0f);

  // True if RenderDoc is attached and available for use.
  bool is_renderdoc_attached() const;
  // Begins capturing the current frame in RenderDoc, if it is attached.
  // Must be paired with EndRenderDocCapture. Multiple frames cannot be
  // captured at the same time.
  void BeginRenderDocFrameCapture();
  // Ends a capture.
  void EndRenderDocFrameCapture();

  // Allocates memory of the given size matching the required properties.
  VkDeviceMemory AllocateMemory(
      const VkMemoryRequirements& requirements,
      VkFlags required_properties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);

 private:
  VulkanInstance* instance_ = nullptr;

  std::vector<Requirement> required_layers_;
  std::vector<Requirement> required_extensions_;
  std::vector<const char*> enabled_extensions_;

  bool debug_marker_ena_ = false;
  PFN_vkDebugMarkerSetObjectNameEXT pfn_vkDebugMarkerSetObjectNameEXT_ =
      nullptr;
  PFN_vkCmdDebugMarkerBeginEXT pfn_vkCmdDebugMarkerBeginEXT_ = nullptr;
  PFN_vkCmdDebugMarkerEndEXT pfn_vkCmdDebugMarkerEndEXT_ = nullptr;
  PFN_vkCmdDebugMarkerInsertEXT pfn_vkCmdDebugMarkerInsertEXT_ = nullptr;

  DeviceInfo device_info_;
  uint32_t queue_family_index_ = 0;
  std::mutex queue_mutex_;
  VkQueue primary_queue_ = nullptr;
  std::vector<std::vector<VkQueue>> free_queues_;
};

}  // namespace vulkan
}  // namespace ui
}  // namespace xe

#endif  // XENIA_UI_VULKAN_VULKAN_DEVICE_H_
