#pragma once

#include <optional>
#include <vector>

#include "dxvk_bind_mask.h"
#include "dxvk_graphics_state.h"
#include "dxvk_pipelayout.h"
#include "dxvk_shader.h"
#include "dxvk_stats.h"

namespace dxvk {
  
  class DxvkDevice;
  class DxvkPipelineManager;
  struct DxvkPipelineStats;


  /**
   * \brief Shaders used in compute pipelines
   */
  struct DxvkComputePipelineShaders {
    Rc<DxvkShader> cs;

    bool eq(const DxvkComputePipelineShaders& other) const {
      return cs == other.cs;
    }

    size_t hash() const {
      return DxvkShader::getCookie(cs);
    }
  };


  /**
   * \brief Compute pipeline instance
   */
  struct DxvkComputePipelineInstance {
    DxvkComputePipelineInstance() { }
    DxvkComputePipelineInstance(VkPipeline handle_)
    : handle(handle_) { }

    VkPipeline handle = VK_NULL_HANDLE;
  };
  
  
  /**
   * \brief Compute pipeline
   * 
   * Stores a compute pipeline object and the corresponding
   * pipeline layout. Unlike graphics pipelines, compute
   * pipelines do not need to be recompiled against any sort
   * of pipeline state.
   */
  class DxvkComputePipeline {
    
  public:
    
    DxvkComputePipeline(
            DxvkDevice*                 device,
            DxvkPipelineManager*        pipeMgr,
            DxvkComputePipelineShaders  shaders,
            DxvkShaderPipelineLibrary*  library);

    ~DxvkComputePipeline();
    
    /**
     * \brief Shaders used by the pipeline
     * \returns Shaders used by the pipeline
     */
    const DxvkComputePipelineShaders& shaders() const {
      return m_shaders;
    }
    
    /**
     * \brief Queries pipeline layout
     * \returns Pipeline layout
     */
    const DxvkPipelineBindings* getLayout() const {
      return &m_layout;
    }

    /**
     * \brief Queries spec constant mask
     *
     * This only includes user spec constants.
     * \returns Bit mask of used spec constants
     */
    uint32_t getSpecConstantMask() const {
      constexpr uint32_t globalMask = (1u << MaxNumSpecConstants) - 1;
      return m_shaders.cs->metadata().specConstantMask & globalMask;
    }
    
    /**
     * \brief Retrieves pipeline handle
     * 
     * \param [in] state Pipeline state
     * \returns Pipeline handle
     */
    VkPipeline getPipelineHandle(
      const DxvkComputePipelineStateInfo& state);
    
    /**
     * \brief Compiles a pipeline
     * 
     * Asynchronously compiles the given pipeline
     * and stores the result for future use.
     * \param [in] state Pipeline state
     */
    void compilePipeline(
      const DxvkComputePipelineStateInfo& state);

    /**
     * \brief Debug name
     *
     * Consists of the compute shader's debug name.
     * \returns Debug name
     */
    const char* debugName() const {
      return m_debugName.c_str();
    }

  private:
    
    DxvkDevice*                 m_device = nullptr;
    DxvkPipelineStats*          m_stats = nullptr;

    DxvkShaderPipelineLibrary*  m_library = nullptr;
    std::optional<VkPipeline>   m_libraryHandle;

    DxvkComputePipelineShaders  m_shaders;
    DxvkPipelineBindings        m_layout;
    
    std::string                 m_debugName;

    alignas(CACHE_LINE_SIZE)
    dxvk::mutex                             m_mutex;
    DxvkPipelineVariantTable<
      DxvkComputePipelineStateInfo,
      DxvkComputePipelineInstance>          m_pipelines;
    
    DxvkComputePipelineInstance* createInstance(
      const DxvkComputePipelineStateInfo& state);
    
    DxvkComputePipelineInstance* findInstance(
      const DxvkComputePipelineStateInfo& state);
    
    VkPipeline createPipeline(
      const DxvkComputePipelineStateInfo& state) const;
    
    void destroyPipeline(
            VkPipeline                    pipeline);

    void logPipelineState(
            LogLevel                      level,
      const DxvkComputePipelineStateInfo& state) const;

    std::string createDebugName() const;

  };
  
}
