{.experimental: "codeReordering".}
{.deadCodeElim: on.}

#[
  THINGS TO FIX 
  : why uboVS as a ref object fucks up vertice uploading
  : #TODO: decouple rec.vk_device from rec and "properly" implement it
]#

import vulkan as nvk
, ../utils/lets
, bitops
, vkTypes

, ../memory/[utils]

#from depth_stencil import Depth_Stencil

proc `<`*(x,y: VkDeviceSize): bool = return x.int < y.int

proc `>`*(x,y: VkDeviceSize): bool = return x.int > y.int

proc `==`*(x,y: VkDeviceSize): bool = return x.int == y.int

proc getQueueFamilyIndex*( qfp: seq[VkQueueFamilyProperties], qFlags: VkQueueFlagBits): uint32 = 
 if bitand(qFlags.int, VK_QUEUE_GRAPHICS_BIT.int) == 1:
  for i, x in qfp: 
   if bitand(qfp[i].queueFlags.int, qFlags.int) == 1: return i.uint32
  
 elif bitand(qFlags.int, VK_QUEUE_TRANSFER_BIT.int) == 1:
  for i, x in qfp: 
   if bitand(qfp[i].queueFlags.int, qFlags.int) == 1: return i.uint32
  
 elif bitand(qFlags.int, VK_QUEUE_COMPUTE_BIT.int) == 1:
  for i, x in qfp: 
   if bitand(qfp[i].queueFlags.int, qFlags.int) == 1: return i.uint32
  
 else:
  for i, x in qfp: 
   if bitand(qfp[i].queueFlags.int, qFlags.int) == 1: return i.uint32

proc chooseSwapSurfaceFormat*(availableFormats: seq[VkSurfaceFormatKHR]): VkSurfaceFormatKHR =
  for availableFormat in availableFormats:
    if availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM and
      availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR:
      return availableFormat
  availableFormats[0]

proc chooseSwapPresentMode*(availablePresentModes: seq[VkPresentModeKHR]): VkPresentModeKHR =
  for availablePresentMode in availablePresentModes:
    if availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR:
      return availablePresentMode

  VK_PRESENT_MODE_FIFO_KHR

proc chooseSwapExtent*(capabilities: VkSurfaceCapabilitiesKHR): VkExtent2D =
  if capabilities.currentExtent.width != uint32.high:
    return capabilities.currentExtent
  else:
   # result = VkExtent2D(width: WIDTH, height: HEIGHT)
    result.width = max( capabilities.minImageExtent.width, min(capabilities.maxImageExtent.width, result.width))
    result.height = max(capabilities.minImageExtent.height, min(capabilities.maxImageExtent.height, result.height) )

proc createShaderModule*(device: VkDevice, code: string): VkShaderModule =
  var createInfo = VkShaderModuleCreateInfo(
    sType: VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
    codeSize: code.len.uint32,
    pCode: cast[ptr uint32](code[0].addr)
  )
  if vkCreateShaderModule(device, createInfo.addr, nil, result.addr) != VK_SUCCESS:
    quit("failed to create shader module")

proc createCommandPool*( vk_device: VkDevice
                       , graphics_queue_index: uint32
                       ): VkCommandPool =
 var
  poolInfo = VkCommandPoolCreateInfo( sType: VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO
                                    , queueFamilyIndex: graphics_queue_index # rec.qfi.graphics
                                    , flags: VkCommandPoolCreateFlags VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT# optional
                                    )
 
 if vkCreateCommandPool( vk_device
                       , poolInfo.addr
                       , nil
                       , addr result 
                       ) != VK_SUCCESS:
  quit("failed to create command pool")

proc getCommandBuffers*( device: VkDevice
                       , command_pool: VkCommandPool
                       , start: bool 
                       ): VkCommandBuffer =
 var
  allocInfo = VkCommandBufferAllocateInfo( sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO
                                         , commandPool: command_pool
                                         , level: VK_COMMAND_BUFFER_LEVEL_PRIMARY
                                         , commandBufferCount: 1
                                         )

 discard vkAllocateCommandBuffers(device, allocInfo.addr, result.addr)
 if start: 
  var cmdBufInfo: VkCommandBufferBeginInfo
  cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO
  discard vkBeginCommandBuffer(result, addr cmdBufInfo)
 
 result

proc createPipeLineCache*( vk_device: VKDevice                         
                         ): VkPipelineCache =
 
 var pipelineCacheCreateInfo: VkPipelineCacheCreateInfo
 pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO

 discard vkCreatePipelineCache( vk_device
                              , addr pipelineCacheCreateInfo
                              , nil
                              , addr result 
                              )
 
proc flushCommandBuffer*( device: VkDevice
                        , q: VkQueue
                        , command_pool: VkCommandPool
                        , commandBuffer: var VkCommandBuffer
                        ) = 
  discard vkEndCommandBuffer commandBuffer

  var
   submitInfo: VkSubmitInfo
   fenceCreateInfo: VkFenceCreateInfo
   fence: VkFence

  submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO
  submitInfo.commandBufferCount = 1
  submitInfo.pCommandBuffers = addr commandBuffer #????????

  # Create fence to ensure that the command buffer has finished executing
  fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO
  fenceCreateInfo.flags = VkFenceCreateFlags 0

  discard vkCreateFence(device, addr fenceCreateInfo, nil, addr fence)

  # Submit to the queue
  discard vkQueueSubmit(q, 1, addr submitInfo, fence)
  # # Wait for the fence to signal that command buffer has finished executing 
  discard vkWaitForFences(device, 1.uint32, addr fence, VkBool32 VK_TRUE, high uint64) # in nanoseconds

  vkDestroyFence(device, fence, nil)
  vkFreeCommandBuffers(device, command_pool, 1, addr commandBuffer)

proc createOneCommandBuffer*( device: VkDevice
                          , pool: VkCommandPool 
                          , level: VkCommandBufferLevel = VK_COMMAND_BUFFER_LEVEL_PRIMARY
                          , begin: bool = false
                          ): VkCommandBuffer =
 
 var allocInfo = VkCommandBufferAllocateInfo( sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO
                                            , commandPool: pool
                                            , level: level
                                            , commandBufferCount: 1
                                            )

 discard vkAllocateCommandBuffers(device, addr allocInfo, addr result)
 
 if begin: 
  var cmdBufferBeginInfo: VkCommandBufferBeginInfo
  cmdBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO
  discard vkBeginCommandBuffer(result, addr cmdBufferBeginInfo)
 result

proc setImageLayout*( cmdbuffer: VkCommandBuffer
                   , image: VkImage
                   , oldImageLayout: VkImageLayout
                   , newImageLayout: VkImageLayout
                   , subresourceRange: VkImageSubresourceRange
                   , srcStageMask: VkPipelineStageFlags
                   , dstStageMask: VkPipelineStageFlags
                   ) = 
 
 var
  # Initialize an image memory barrier with no image transfer ownership
  imageMemoryBarrier = VkImageMemoryBarrier( sType: VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER
                                           , srcQueueFamilyIndex: VK_QUEUE_FAMILY_IGNORED
                                           , dstQueueFamilyIndex: VK_QUEUE_FAMILY_IGNORED
                                           , oldLayout: oldImageLayout
                                           , newLayout: newImageLayout
                                           , image: image
                                           , subresourceRange: subresourceRange
                                           )

 # Source layouts (old)
 # Source access mask controls actions that have to be finished on the old layout
 # before it will be transitioned to the new layout
 case oldImageLayout:
 
  of VK_IMAGE_LAYOUT_UNDEFINED:
   # Image layout is undefined (or does not matter)
   # Only valid as initial layout
   # No flags required, listed only for completeness
   imageMemoryBarrier.srcAccessMask = VkAccessFlags 0
 
  of VK_IMAGE_LAYOUT_PREINITIALIZED:
   # Image is preinitialized
   # Only valid as initial layout for linear images, preserves memory contents
   # Make sure host writes have been finished
   imageMemoryBarrier.srcAccessMask = VkAccessFlags VK_ACCESS_HOST_WRITE_BIT
 
  of VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
   # Image is a color attachment
   # Make sure any writes to the color buffer have been finished
   imageMemoryBarrier.srcAccessMask = VkAccessFlags VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
  
  of VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
   # Image is a depth/stencil attachment  
   # Make sure any writes to the depth/stencil buffer have been finished
   imageMemoryBarrier.srcAccessMask = VkAccessFlags VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT
  
  of VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
   # Image is a transfer source
   # Make sure any reads from the image have been finished
   imageMemoryBarrier.srcAccessMask = VkAccessFlags VK_ACCESS_TRANSFER_READ_BIT
   
  of VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
   # Image is a transfer destination
   # Make sure any writes to the image have been finished
   imageMemoryBarrier.srcAccessMask = VkAccessFlags VK_ACCESS_TRANSFER_WRITE_BIT
  
  of VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
   # Image is read by a shader
   # Make sure any shader reads from the image have been finished
   imageMemoryBarrier.srcAccessMask = VkAccessFlags VK_ACCESS_SHADER_READ_BIT

  else: discard # Other source layouts aren't handled (yet)

 # Target layouts (new)
 # Destination access mask controls the dependency for the new image layout
 case newImageLayout:

  of VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
   # Image will be used as a transfer destination
   # Make sure any writes to the image have been finished
   imageMemoryBarrier.dstAccessMask = VkAccessFlags VK_ACCESS_TRANSFER_WRITE_BIT

  of VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
   # Image will be used as a transfer source
   # Make sure any reads from the image have been finished
   imageMemoryBarrier.dstAccessMask = VkAccessFlags VK_ACCESS_TRANSFER_READ_BIT

  of VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
   # Image will be used as a color attachment
   # Make sure any writes to the color buffer have been finished
   imageMemoryBarrier.dstAccessMask = VkAccessFlags VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT

  of VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
   # Image layout will be used as a depth/stencil attachment
   # Make sure any writes to depth/stencil buffer have been finished
   imageMemoryBarrier.dstAccessMask = VkAccessFlags bitor(imageMemoryBarrier.dstAccessMask.ord, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT.ord)

  of VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
   # Image will be read in a shader (sampler, input attachment)
   # Make sure any writes to the image have been finished
   if (imageMemoryBarrier.srcAccessMask.int == 0).bool:
    imageMemoryBarrier.srcAccessMask = VkAccessFlags bitor(VK_ACCESS_HOST_WRITE_BIT.ord, VK_ACCESS_TRANSFER_WRITE_BIT.ord)
   imageMemoryBarrier.dstAccessMask = VkAccessFlags VK_ACCESS_SHADER_READ_BIT
  
  else: discard #Other source layouts aren't handled (yet)

 # Put barrier inside setup command buffer
 vkCmdPipelineBarrier( cmdbuffer
                    , srcStageMask
                    , dstStageMask
                    , VkDependencyFlags 0
                    , 0
                    , nil
                    , 0
                    , nil
                    , 1
                    , addr imageMemoryBarrier)

proc beginSingleTimeCommands*( vk_device: VkDevice 
                             , command_pool: VkCommandPool
                             ): VkCommandBuffer = 
 var 
  commandBuffer: VkCommandBuffer
  beginInfo = VkCommandBufferBeginInfo( sType:VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO
                                      , flags: VkCommandBufferUsageFlags VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
                                      )
  allocInfo = VkCommandBufferAllocateInfo( sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO
                                         , level: VK_COMMAND_BUFFER_LEVEL_PRIMARY
                                         , commandPool: command_pool
                                         , commandBufferCount: 1
                                         )

 discard vkAllocateCommandBuffers( vk_device
                                 , addr allocInfo
                                 , addr commandBuffer
                                 )
 
 discard vkBeginCommandBuffer(commandBuffer, addr beginInfo)

proc endSingleTimeCommands*( vk_device: VkDevice 
                           , cmdBuffer: VkCommandBuffer
                           , graphics_queue: VkQueue
                           , command_pool: VkCommandPool
                           , fence: VkFence = VkFence 0
                           , cmdBufferCount: uint32 = 1
                           ) = 

 var submitInfo = VkSubmitInfo( sType: VK_STRUCTURE_TYPE_SUBMIT_INFO
                              , commandBufferCount: cmdBufferCount
                              , pCommandBuffers: addr cmdBuffer
                              )

 discard vkEndCommandBuffer(cmdBuffer
                           )
 
 discard vkQueueSubmit( graphics_queue
                      , uint32 1
                      , addr submitInfo
                      , fence
                      )
 
 discard vkQueueWaitIdle graphics_queue
 
 vkFreeCommandBuffers( vk_device
                     , command_pool 
                     , 1
                     , addr cmdBuffer
                     )
 
proc createImageView*( device: VkDevice
                     , image: VkImage
                     , format: VkFormat
                     ): VkImageVIew = 
 var viewInfo = VkImageViewCreateInfo( sType: VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO
                           , image: image
                           , viewType: VK_IMAGE_VIEW_TYPE_2D
                           , format: format
                           , subresourceRange: VkImageSubresourceRange( aspectMask: VkImageAspectFlags VK_IMAGE_ASPECT_COLOR_BIT
                                                                       , baseMipLevel: 0
                                                                       , levelCount: 1
                                                                       , baseArrayLayer: 0
                                                                       , layerCount: 1
                                                                       )
                           )
 discard vkCreateImageView(device, addr viewInfo, nil, addr result)
 result

# TODO: Samplers can be shared. Decide on parameters and reuse samplers
# when the same parameters are passed.
proc createSampler*(device: VkDevice): VkSampler = 
 var samplerInfo = VkSamplerCreateInfo( sType: VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO
                                    , magFilter: VK_FILTER_LINEAR
                                    , minFilter: VK_FILTER_LINEAR
                                    , addressModeU: VK_SAMPLER_ADDRESS_MODE_REPEAT
                                    , addressModeV: VK_SAMPLER_ADDRESS_MODE_REPEAT
                                    , addressModeW: VK_SAMPLER_ADDRESS_MODE_REPEAT
                                    , anisotropyEnable: vktrue
                                    , maxAnisotropy: 16
                                    , borderColor: VK_BORDER_COLOR_INT_OPAQUE_BLACK
                                    , unnormalizedCoordinates: vkfalse
                                    , compareEnable:  vkfalse
                                    , compareOp: VK_COMPARE_OP_ALWAYS
                                    , mipmapMode: VK_SAMPLER_MIPMAP_MODE_LINEAR
                                    )

 discard vkCreateSampler(device, addr samplerInfo,  nil, addr result)
 result

proc newVkFenceCreateInfo*(sType: VkStructureType = VkStructureTypeFenceCreateInfo, pNext: pointer = nil, flags: VkFenceCreateFlags = 0.VkFenceCreateFlags): VkFenceCreateInfo =
  result.sType = sType
  result.pNext = pNext
  result.flags = flags
  
proc createSyncPrims*( vk_device: VkDevice 
                     , fences: var seq[VkFence]
                     , draw_command_buffers: var seq[VkCommandBuffer]
                    
                     ) =
 var createInfo = newVkFenceCreateInfo() 
 
 #echo rec.draw_command_buffers.len
 fences.setLen draw_command_buffers.len
 for i,fence in fences: 
  discard vkCreateFence( vk_device
                       , addr createInfo
                       , nil
                       , addr fences[i] 
                       )

proc aVkDescriptorImageInfo*( sampler: VkSampler
                           , imageView: VkImageView
                           , imageLayout: VkImageLayout
                           ): VkDescriptorImageInfo = 
 result = VkDescriptorImageInfo()
 result.sampler = sampler
 result.imageView = imageView
 result.imageLayout = imageLayout

proc aVkWriteDescriptorSet*( descSet: VkDescriptorSet
                          ,  descType: VkDescriptorType
                          ,  binding: uint32
                          ,  bufferInfo: VkDescriptorBufferInfo
                          ,  descriptorCount: uint32 = 1  
                          ): VkWriteDescriptorSet = 
 
 result = VkWriteDescriptorSet()
 result.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET
 result.dstSet = descSet
 result.descriptorType = descType
 result.dstBinding = binding
 result.pBufferInfo = cast[ptr ptr VkDescriptorBufferInfo] (addr bufferInfo) # LMAO?????
 result.descriptorCount = descriptorCount
                      
proc aVkDescriptorSetLayoutBinding*( descType: VkDescriptorType
                                  , stageFlags: VkShaderStageFlags
                                  , binding: uint32
                                  , descCount: uint32 = 1): VkDescriptorSetLayoutBinding = 
 result = VkDescriptorSetLayoutBinding()
 result.descriptorType = descType
 result.stageFlags = stageFlags
 result.binding = binding
 result.descriptorCount = descCount

proc aVkBufferCreateInfo*( usage: VkBufferUsageFlags
                         , size: VkDeviceSize = vk0
                         ): VkBufferCreateInfo = 
 
 result = VkBufferCreateInfo(sType: VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO
                            , usage: usage
                            , size: size
                            )

proc Vkds*[T](t: T): VKDeviceSize {.inline.}= VKDeviceSize t

#[
  static i32 find_memory_index(vulkan_context *context
                                                 , u32 type_filter
                                                 , u32 property_flags) 
  {
    for (u32 i = 0; i < memory_properties.memoryTypeCount; ++i) {
        // Check each memory type to see if its bit is set to 1.
        if ( type_filter & (1 << i) && (memory_properties.memoryTypes[i].propertyFlags & property_flags) ==
                property_flags
           ) {
            return i;
        }
    }

    KWARN("Unable to find suitable memory type!");
    return -1;
}
]#
proc `==`*(x, y: VkMemoryPropertyFlags): bool =  x.int == y.int 
proc `and`*(x, y: VkMemoryPropertyFlags): int =  x.int and y.int 

proc find_memory_with_property*( memory_properties: VkPhysicalDeviceMemoryProperties
                                                      , the_type_bits_filter: var uint32
                                                      , required_memory_properties: VkMemoryPropertyFlags 
                                                      ): uint32 =

 for memory_index in 0 ..< memoryProperties.memoryTypeCount:
  var 
    is_required_memory_property =  bitand( the_type_bits_filter.int
                                                                    , (1 shl memory_index)
                                                                    )  >= 1
    the_memory_indexs_property_flag = memory_properties.memoryTypes[memory_index].propertyFlags.int

  #echo "RMP: ", is_required_memory_property, " <>  (",  bitand( the_type_bits_filter.int, (1 shl memory_index)), ") ",  the_memory_indexs_property_flag 
  
  if  is_required_memory_property and 
    bitand( the_memory_indexs_property_flag
               , required_memory_properties.int
               ) == required_memory_properties.int:
   return memory_index
     
 quit("find_memory_with_property: no matching memory type could be found:")

proc create_image*( vk_device: VkDevice 
                  , gpu_memory_properties: VkPhysicalDeviceMemoryProperties
                  , width: uint32
                  , height: uint32
                  , depth: uint32
                  , format: VkFormat
                  , tiling: VkImageTiling
                  , usage: VkImageUsageFlags
                  , properties: VkMemoryPropertyFlags
                  ): BoundImage = 

 var  
  allocInfo = VkMemoryAllocateInfo(sType: VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO)
  image: VKImage
  memReqs: VkMemoryRequirements 
  imageMem: VkDeviceMemory
  imageci =  VkimageCreateInfo( sType: VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO
                               , imageType: VK_IMAGE_TYPE_2D
                               , extent: VkExtent3D( width: width
                                                   , height: height
                                                   , depth: 1
                                                   )
                               , mipLevels: 1
                               , arrayLayers: 1
                               , format: format
                               , tiling: tiling
                               , initialLayout: VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
                               , usage: usage
                               , samples: VK_SAMPLE_COUNT_1_BIT
                               , sharingMode: VK_SHARING_MODE_EXCLUSIVE
                               #[ , imageCreateMaxMipLevels: 1
                               , imageCreateMaxArrayLayers: 1 
                               , imageCreateMaxExtent: VkExtent3D( width: width
                                                                 , height: height
                                                                , depth: depth

                                                                 )
                               , imageCreateSampleCounts: VK_SAMPLE_COUNT_1_BIT ]#
                               )

 discard vkCreateImage( vk_device
                      , addr imageci
                      , nil
                      , addr image
                      )
 
 vkGetImageMemoryRequirements( vk_device
                             , image
                             , addr memReqs
                             )
 
 allocInfo.allocationSize = memReqs.size
 allocInfo.memoryTypeIndex = find_memory_with_property( gpu_memory_properties
                                                      , memReqs.memoryTypeBits
                                                      , properties
                                                      )
             
 discard vkBindImageMemory( vk_device
                          , image
                          , imageMem
                          , vk0
                          )

#TODO: This should be per pipeline correct?
proc create_descriptor_pool*( vk_device: VkDevice 
                           ): VkDescriptorPool = 
 
 var
  sizes = [ VkDescriptorPoolSize( typee: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER
                                , descriptorCount: 1
                                )
          
          , VkDescriptorPoolSize( typee: VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
                                , descriptorCount: 1
                                )
          ]

  # Create the global descriptor pool
  # All descriptors used in this example are allocated from this pool
  descriptorPoolInfo = VkDescriptorPoolCreateInfo( sType :  VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO
                                                 , pNext : nil
                                                 , poolSizeCount : 2
                                                 , pPoolSizes : addr sizes[0]
                                                 , maxSets: 4 # Set the max. number of descriptor sets that can be requested from this pool
                                                )
 
 discard vkCreateDescriptorPool( vk_device
                               , addr descriptorPoolInfo
                               , nil
                               , addr result
                               )
 
 result