{.experimental: "codeReordering".}

import vulkan
, vkTypes
, vulkan_utils
, ../utils/[lets
            ]
, bitops

type 
 Buffer_Object = object of RootObj
  device_memory*: VkDeviceMemory
  vk_buffer*: VkBuffer
  size*: VkDeviceSize
  data*: ptr char
  current_data_offset*: Natural
 Buffer* = ref object of Buffer_Object

#[
  Buffer& result
                 , VkDevice device
				 , const VkPhysicalDeviceMemoryProperties& memoryProperties
				 , size_t size
				 , VkBufferUsageFlags usage
				 , VkMemoryPropertyFlags memoryFlags
]#
proc a_vulkan_buffer*( vk_device: VkDevice
                                   , gpu_memory_properties: VkPhysicalDeviceMemoryProperties
                                   , buffer_usage_flags: VkBufferUsageFlags
                                   , memory_flags: VkMemoryPropertyFlags
                                   , allocation_size: VkDeviceSize
                                   ): Buffer = 

 result = Buffer( size: allocation_size
                )

 var
  mem_reqs: VkMemoryRequirements
  buffer_info = VkBufferCreateInfo( sType: VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO
                                                                                      , size: allocation_size
                                                                                      )
  
  allocation_info = VkMemoryAllocateInfo(sType: VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO
                                                                                                       )
  
  memory_allocation_flag_info = 
   VkMemoryAllocateFlagsInfo( sType: VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO_KHR
                                                )

 assert vkCreateBuffer( vk_device
                                    , addr buffer_info
                                    , nil
                                    , addr result.vk_buffer
                                    ) == VK_SUCCESS

 vkGetBufferMemoryRequirements( vk_device
                                                        , result.vk_buffer
                                                        , addr mem_reqs
                                                        )
 
 allocation_info.allocationSize = mem_reqs.size
 allocation_info.memoryTypeIndex = 
  gpu_memory_properties.find_memory_with_property( mem_reqs.memoryTypeBits
                                                                                       , memory_flags
                                                                                       )
 

 if bitand( buffer_usage_flags.int
           , VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT.ord
           ) == 1:
  allocation_info.pNext = addr memory_allocation_flag_info
  memory_allocation_flag_info.flags = VkMemoryAllocateFlags VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT
  memory_allocation_flag_info.deviceMask = 1

 discard vkAllocateMemory( vk_device
                                           , addr allocation_info
                                           , nil
                                           , addr result.device_memory
                                           )
 
 discard vkBindBufferMemory( vk_device
                           , result.vk_buffer
                           , result.device_memory
                           , VkDeviceSize 0
                           )
 
 if bitand( memory_flags.int
          , VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT.ord
          ) == 1:
  assert vkMapMemory( vk_device 
                                    , result.device_memory 
                                    , VkDeviceSize 0
                                    , allocation_size
                                    , VkMemoryMapFlags 0
                                    , cast[ptr pointer] (result.data)
                                    ) ==  VK_SUCCESS
 
#[  result.descriptor.buffer = result.vk_buffer
 result.descriptor.offset = VkDeviceSize 0
 result.descriptor.range  = VkDeviceSize VK_WHOLE_SIZE 
 ]#
 return result

#[ proc fillDescr*( buffer: Buffer
               , size: VkDeviceSize = VkDeviceSize VK_WHOLE_SIZE
               , offset: VkDeviceSize = vk0
               ) = 
 buffer.descriptor.offset = offset
 buffer.descriptor.buffer = buffer.vkbuffer
 buffer.descriptor.range = size ]#

# Map a memory range of this buffer. If successful, mapped points to the specified buffer range.
# @param size (Optional) Size of the memory range to map. Pass VK_WHOLE_SIZE to map the complete buffer range.
# @param offset (Optional) Byte offset from beginning
proc map_memory*( vk_device: VkDevice 
            , memory: VkDeviceMemory
            , data: var pointer
            , size: VkDeviceSize = vkWholeSize
            , offset: VkDeviceSize = vk0
            ) =

 discard vkMapMemory( vk_device
                    , memory
                    , vk0
                    , size
                    , VkMemoryMapFlags 0
                    , addr data
                    )

# vkUnmapMemory can't fail (?)
proc unMapMem*( buffer: var Buffer
              , data: var pointer
              ) = 
 if not data.isNil: data = nil

# # Copies the specified data to the mapped buffer
# # @param data Pointer to the data to copy
# # @param size Size of the data to copy in machine units
# proc copyTo*( data: pointer
#             , buffer: Buffer
#             , size: Natural
#             ) =
#  assert not buffer.mapped.isNil
#  copyMem(data, addr buffer.mapped, size)

proc aVkMappedMemoryRange*( memory: VkDeviceMemory
                          , offset: VkDeviceSize
                          , size: VkDeviceSize
                          , pNext: pointer = nil
                          , sType: VkStructureType = VkStructureTypeMappedMemoryRange
                          ): VkMappedMemoryRange =
  result.sType = sType
  result.memory = memory
  result.offset = offset
  result.pNext = pNext
  result.size = size

# flush a memory range of the buffer to make it visible to VkDevice
# Only required for non-coherent memory (?)
proc flushMem*( vk_device: VkDevice
              , buffer: Buffer
              , data: pointer
              , offset: VkDeviceSize
              , size: VkDeviceSize
              ) =

 var r = aVkMappedMemoryRange( memory = buffer.device_memory
                             , offset = offset
                             , size = size
                             )
 discard vkFlushMappedMemoryRanges(vk_device,  1.uint32, addr r)

# Invalidates a memory range of the buffer 
# to make it visible to the host
# Only required for non-coherent memory (?)
proc invalidate*( vk_device: VkDevice
              , buffer: Buffer
              , size: VkDeviceSize
              , offset: VkDeviceSize
              ) =

 var r = aVkMappedMemoryRange( memory = buffer.device_memory
                             , offset = offset
                             , size = size
                             )
 
 discard vkInvalidateMappedMemoryRanges(vk_device, 1, addr r)

# Release all Vulkan resources held by this buffer
proc destroy*( vk_device: VkDevice
             , buffer: var Buffer
             ) = 
 vkDestroyBuffer(vk_device, buffer.vkbuffer, nil)
 vkFreeMemory(vk_device, buffer.device_memory, nil)


#[ proc fill_descriptor*( buffer: var Buffer
               , size: VkDeviceSize = VkDeviceSize VK_WHOLE_SIZE
               , offset: VkDeviceSize = vk0
               ) = 
 buffer.descriptor.offset = offset
 buffer.descriptor.buffer = buffer.vkbuffer
 buffer.descriptor.range = size ]#

proc create_command_buffers*( device: VkDevice
                            , pool: VkCommandPool 
                            , level: VkCommandBufferLevel = VK_COMMAND_BUFFER_LEVEL_PRIMARY
                            , amount: uint32 = 2
                            ): seq[VkCommandBuffer] =
 
 result.setLen amount
 var allocInfo = VkCommandBufferAllocateInfo( sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO
                                            , commandPool: pool
                                            , level: level
                                            , commandBufferCount: amount
                                            )

 discard vkAllocateCommandBuffers( device
                                 , addr allocInfo
                                 , addr result[0]
                                 )