import vulkan as nvk
, vkTypes
, vulkan_record 
, vulkan_utils 
, ../utils/[lets
           , etc
           ]
, bitops
, strutils
, vkTypes
, swapchain
, gpu
, ../wain/waindow_object
, dbg
, device
, semaphores
, buffer 

import ../memory/[ utils 

                ]

const 
 needed_validation_layers* = ["VK_LAYER_KHRONOS_validation"]

proc check_validation_layers(
                            ) =
 
 when defined windows: 
  vkEnumerateInstanceLayerProperties = cast[proc( pPropertyCount: ptr uint32 
                                               , pProperties: ptr VkLayerProperties 
                                               ): VkResult {.stdcall.}]
  (vkGetProcAddress("vkEnumerateInstanceLayerProperties")) 

 var layer_count: uint32 = 0
 discard vkEnumerateInstanceLayerProperties( addr layer_count
                                           , nil
                                           )
 
 var layers = newSeq[VkLayerProperties] (layer_count)
 discard vkEnumerateInstanceLayerProperties( addr layer_count
                                           , addr layers[0]
                                           )

 for validated_layer in needed_validation_layers:
  var found = false
  for layer in layers:
   if cstring(unsafeAddr layer.layerName) == validated_layer:
    found = true
    break
  if not found:
   echo validated_layer & " layer is not supported"

proc create_vulkan_instance*(vulkan_record: var Vulkan_Record
                            ) =

 var
  validationFeatureEnables: array[3, VkValidationFeatureEnableEXT] = 
   [ VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT
   , VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT
   , VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT
   ]
  
  validationFeatures = VkValidationFeaturesEXT( sType: VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT
                                              , pNext: nil
                                              , enabledValidationFeatureCount: 3
                                              , pEnabledValidationFeatures: addr validationFeatureEnables[0]
                                              , disabledValidationFeatureCount: 0
                                              , pDisabledValidationFeatures: nil
                                              )
  
  instance: VkInstance
  
  appInfo = newVkApplicationInfo(
   pApplicationName = "p0",
   applicationVersion = vkMakeVersion(1, 0, 0),
   pEngineName = "engine_p0",
   engineVersion = vkMakeVersion(1, 0, 0),
   apiVersion = vkApiVersion1_3
  )
   
 var 
  exts = allocCStringArray(["VK_KHR_surface"
                           , "VK_EXT_debug_utils"
                           ]
                          )
 
 when defined linux: 
   exts[2] = "VK_KHR_xcb_surface"
 
 when defined windows: 
   exts[2] = "VK_KHR_win32_surface"
 
 var 
  instanceCreateInfo = newVkInstanceCreateInfo( pApplicationInfo = appInfo.addr
                                              , enabledExtensionCount = 3 #TODO make dynamic 
                                              , ppEnabledExtensionNames = exts
                                              #, enabledLayerCount = 0
                                              #, ppEnabledLayerNames = val
                                              , pNext = addr validationFeatures
                                              )
  vulkan_create_instance_result = vkCreateInstance( instanceCreateInfo.addr
                                                  , nil
                                                  , addr instance
                                                  ) 

 #echo repr vulkan_create_instance_result 
 vulkan_record.instance = instance

 when defined windows:  
  vkEnumerateInstanceExtensionProperties = cast[proc(pLayerName: cstring , pPropertyCount: ptr uint32 , pProperties: ptr VkExtensionProperties ): VkResult {.stdcall.}]
  (vkGetProcAddress("vkEnumerateInstanceExtensionProperties"))
 
 var extensionCount: uint32 = 0
 discard vkEnumerateInstanceExtensionProperties( nil
                                                , extensionCount.addr
                                                , nil
                                                )
 var extensions = newSeq[VkExtensionProperties] ( extensionCount )
 discard vkEnumerateInstanceExtensionProperties( nil
                                                , extensionCount.addr
                                                , extensions[0].addr
                                                )

 var 
  debugUtilsMessenger: VkDebugUtilsMessengerEXT
  debugUtilsMessengerCI: VkDebugUtilsMessengerCreateInfoEXT
 
 debugUtilsMessengerCI.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT
 debugUtilsMessengerCI.messageSeverity = 
  VkDebugUtilsMessageSeverityFlagsEXT bitor( VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT.int
                                           , VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT.int
                                           , VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT.int)
 debugUtilsMessengerCI.messageType = 
  VkDebugUtilsMessageTypeFlagsEXT bitor( VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT.int
                                       , VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT.int
                                       , VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT.int)
 debugUtilsMessengerCI.pfnUserCallback = debugUtilsMessengerCallback
 
 vkCreateDebugUtilsMessengerEXT = cast[proc(instance: VkInstance, pCreateInfo: ptr VkDebugUtilsMessengerCreateInfoEXT , pAllocator: ptr VkAllocationCallbacks , pMessenger: ptr VkDebugUtilsMessengerEXT ): VkResult {.stdcall.}]
 (vkGetInstanceProcAddr(vulkan_record.instance, "vkCreateDebugUtilsMessengerEXT"))
 
 discard vkCreateDebugUtilsMessengerEXT(vulkan_record.instance, addr debugUtilsMessengerCI, nil, addr debugUtilsMessenger) 

proc load_vulkan_device_extensions( instance: VkInstance
                                                           , device: VkDevice 
                                                           ) = 
 vkGetDeviceProcAddr = cast[proc(device: VkDevice, pName: cstring ): PFN_vkVoidFunction {.stdcall.}]
                                         ( vkGetInstanceProcAddr( instance
                                                                                 , "vkGetDeviceProcAddr"
                                                                                 )
                                         )

 vkCreateBuffer = cast[ proc( device: VkDevice
                                              , pCreateInfo: ptr VkBufferCreateInfo 
                                              , pAllocator: ptr VkAllocationCallbacks 
                                              , pBuffer: ptr VkBuffer 
                                              ): VkResult  {.stdcall.}
                                    ] (vkGetDeviceProcAddr( device
                                                                           , "vkCreateBuffer"
                                      )                                    )

 vkGetBufferMemoryRequirements  =cast[ proc(device: VkDevice
                                                                           , buffer: VkBuffer
                                                                           , pMemoryRequirements: ptr VkMemoryRequirements 
                                                                           ): void {.stdcall.}]
                                                                           ((vkGetDeviceProcAddr( device
                                                                                                                 , "vkGetBufferMemoryRequirements"
                                                                           )                                    ))
 vkBindBufferMemory = cast[proc(device: VkDevice, buffer: VkBuffer, memory: VkDeviceMemory, memoryOffset: VkDeviceSize): VkResult {.stdcall.}]
                                            (vkGetDeviceProcAddr( device
                                                                                 , "vkBindBufferMemory"
                                                                                 ))                                                                            
proc setup_vulkan*( wain: var Waindow
                  ): Vulkan_Record =
 
 result = Vulkan_Record( swapchain: Swapchain()
                       ) 
 vkLoad1_0()

 result.create_vulkan_instance()
 echo "Created vulkan_instance!"


 loadVK_KHR_surface(result.instance)
 loadVK_KHR_swapchain(result.instance)
 result.gpu = result.instance.get_gpu
 result.gpu.handle.getSupportedDepthFormat result.swapchain.depthFormat
 result.createLogicalDevice
 result.vk_device = result.logical_device

 load_vulkan_device_extensions(result.instance
                                                    , result.vk_device
                                                    )
 #result.current_descriptor_pool = create_descriptor_pool( result.vk_device )
  
 when defined windows: 
  vkGetDeviceQueue = cast[proc(device: VkDevice, queueFamilyIndex: uint32, queueIndex: uint32, pQueue: ptr VkQueue ): void {.stdcall.}]
  (vkGetInstanceProcAddr(result.instance
                       , "vkGetDeviceQueue"
                       )
  )
 #TODO: properly check and set default(?) q index for the family
 vkGetDeviceQueue( result.vk_device
                 , result.qfi.graphics
                 , 0
                 , addr result.queue
                 ) 

 #TODO: put this in a function
 when defined linux: 
  vkCreateXcbSurfaceKHR = cast[proc(instance: VkInstance, pCreateInfo: ptr VkXcbSurfaceCreateInfoKHR , pAllocator: ptr VkAllocationCallbacks , pSurface: ptr VkSurfaceKHR ): VkResult {.stdcall.}](vkGetProc("vkCreateXcbSurfaceKHR"))
  vkGetPhysicalDeviceXcbPresentationSupportKHR = cast[proc(physicalDevice: VkPhysicalDevice, queueFamilyIndex: uint32, connection: ptr xcb_connection_t , visual_id: xcb_visualid_t): VkBool32 {.stdcall.}](vkGetProc("vkGetPhysicalDeviceXcbPresentationSupportKHR"))
  
  var surfaceCreateInfo: VkXcbSurfaceCreateInfoKHR
  surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR
  
  surfaceCreateInfo.connection = cast[ptr nvk.xcb_connection_t](wain.conn)
  surfaceCreateInfo.window = cast[nvk.xcb_window_t](wain.window)
  var surface_res = vkCreateXcbSurfaceKHR( result.instance
                                         , addr surfaceCreateInfo
                                         , nil
                                         , addr result.swapchain.surface
                                         )
  assert surface_res == VK_SUCCESS, "surface_res failed: " & $surface_res
 
 when defined windows: 
  vkCreateWin32SurfaceKHR = cast[proc(instance: VkInstance, pCreateInfo: ptr VkWin32SurfaceCreateInfoKHR , pAllocator: ptr VkAllocationCallbacks , pSurface: ptr VkSurfaceKHR ): VkResult {.stdcall.}]
  (vkGetInstanceProcAddr(result.instance
                      , "vkCreateWin32SurfaceKHR"
                      )
  )
  
  vkGetPhysicalDeviceWin32PresentationSupportKHR = cast[proc(physicalDevice: VkPhysicalDevice, queueFamilyIndex: uint32): VkBool32 {.stdcall.}]
  (vkGetInstanceProcAddr(result.instance
                      , "vkGetPhysicalDeviceWin32PresentationSupportKHR"
                      )
  )
  
  var surfaceCreateInfo: VkWin32SurfaceCreateInfoKHR
  surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR
  
  surfaceCreateInfo.hinstance = cast[nvk.HINSTANCE] ( addr wain.h_instance )
  surfaceCreateInfo.hwnd = cast[nvk.HWND] (wain.hwnd)
  
  var surface_res = vkCreateWin32SurfaceKHR( result.instance
                                           , addr surfaceCreateInfo
                                           , nil
                                           , addr result.swapchain.surface
                                           )
  assert surface_res == VK_SUCCESS, "ERROR: vkCreateWin32SurfaceKHR is: " & $surface_res
  
 result.gpu.handle.check_surface_support( result.swapchain
                                         , result.qni
                                         , result.instance
                                         )
 
 create_swapchain( result.vk_device
                 , result.swapchain
                 , result.gpu.handle
                 , result.current_viewport
                 )   
 
 
 #TODO: actually properly check device extensions exist on the used GPU
 result.gpu.handle.check_gpu_extensions
 result.create_logical_command_pool

 result.draw_command_buffers = result.vk_device.create_command_buffers( result.command_pool
                                                                      )

 result.master_vertex_buffer = a_vulkan_buffer( result.vk_device
                                                                           , result.gpu.memory_properties
                                                                           ,  VkBufferUsageFlags bitor( VK_BUFFER_USAGE_VERTEX_BUFFER_BIT.ord
                                                                                                                       , VK_BUFFER_USAGE_TRANSFER_DST_BIT.ord
                                                                                                                       )
                                                                           , VkMemoryPropertyFlags VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
                                                                           , MiB_vkds(256)
                                                                           )
   
 result.master_index_buffer = a_vulkan_buffer( result.vk_device
                                                                         , result.gpu.memory_properties
                                                                         ,  VkBufferUsageFlags bitor( VK_BUFFER_USAGE_INDEX_BUFFER_BIT.ord
                                                                                                                     , VK_BUFFER_USAGE_TRANSFER_DST_BIT.ord
                                                                                                                     )
                                                                         , VkMemoryPropertyFlags VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
                                                                         , MiB_vkds(128)
                                                                         )
                                                                         
 result.master_uniform_buffer = a_vulkan_buffer( result.vk_device
                                                                             , result.gpu.memory_properties
                                                                             ,  VkBufferUsageFlags VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT
                                                                             , VkMemoryPropertyFlags ( VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT.ord or 
                                                                                                       VK_MEMORY_PROPERTY_HOST_COHERENT_BIT.ord
                                                                                                     )
                                                                             , MiB_vkds(256)
                                                                             )

#[  result.master_vertex_buffer = a_vulkan_buffer( result.vk_device
                                                                           , result.gpu.memory_properties
                                                                           ,  VkBufferUsageFlags bitor( VK_BUFFER_USAGE_VERTEX_BUFFER_BIT.ord
                                                                                                      , VK_BUFFER_USAGE_TRANSFER_DST_BIT.ord
                                                                                                      )
                                                                           , VkMemoryPropertyFlags VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
                                                                           , MiB_vkds(256)
                                                                            ) ]#

 #[ master_index_buffer = a_vulkan_buffer(

                                                                  )

 master_uniform_buffer = a_vulkan_buffer(

                                                                  )
 ]#

proc suballocate_vertex_buffer*( vulkan_record: var Vulkan_Record
                                                   , vertex_data: pointer
                                                   ) = 

 copymem( vertex_data 
                , addr vulkan_record.master_vertex_buffer.data[vulkan_record.master_vertex_buffer.current_data_offset]
                , Natural vertex_data.sizeof
                ) 
 # memcpy( &dst[dstIdx], &src[srcIdx], numElementsToCopy * sizeof( Element ) );
 vulkan_record.master_vertex_buffer.current_data_offset += vertex_data.sizeof

proc suballocate_index_buffer*( vulkan_record: var Vulkan_Record
                                                 , index_data: pointer
                                                 ) = 

 copymem( index_data 
                , addr vulkan_record.master_index_buffer.data[vulkan_record.master_index_buffer.current_data_offset]
                , Natural index_data.sizeof
                ) 
 # memcpy( &dst[dstIdx], &src[srcIdx], numElementsToCopy * sizeof( Element ) );
 vulkan_record.master_index_buffer.current_data_offset += index_data.sizeof
   
proc suballocate_uniform_buffer*( vulkan_record: var Vulkan_Record
                                                    , uniform_buffer_data: pointer
                                                    ) = 

 # memcpy( &dst[dstIdx], &src[srcIdx], numElementsToCopy * sizeof( Element ) );
 copyMem( uniform_buffer_data
        , addr vulkan_record.master_uniform_buffer.data[vulkan_record.master_uniform_buffer.current_data_offset]
        , sizeof uniform_buffer_data
        ) 
 vulkan_record.master_uniform_buffer.current_data_offset += uniform_buffer_data.sizeof