{.experimental: "codeReordering".}

import vulkan
, vkTypes
, depth_stencil
#, math
, bitops
#, ./utils/etc
, glm

import ../wain/[waindow_object
                 ]

type 
 Swapchain_Buffer* = ref object 
  image*: VkImage
  image_view*: VkImageView
  
 Swapchain* = ref object    
  handle*: VkSwapchainKHR
  oldSwapchain*: VkSwapchainKHR               
  current_extent*: VkExtent2D
  surface*: VkSurfaceKHR
  images*: seq[VkImage]
  buffers*: seq[Swapchain_Buffer]
  
  #auxillary info
  image_count*: uint32 # defalut = 3
  surfaceCaps*: VkSurfaceCapabilitiesKHR
  imageUsage*: VkImageUsageFlags
  preTransform*: VkSurfaceTransformFlagBitsKHR
  compositeAlpha*: VkCompositeAlphaFlagBitsKHR
  the_presentation_mode*: VkPresentModeKHR
  colorFormat*: VkFormat
  colorSpace*: VkColorSpaceKHR
  depth_format*: VkFormat
  presentModeCount*: uint32
  presentModes*: seq[VkPresentModeKHR]
  surfaceFormat*: VkSurfaceFormatKHR
  arrayLayers*: uint32
  formats*: seq[VkSurfaceFormatKHR]
  surfaceFormats*: seq[VkSurfaceFormatKHR]


proc check_surface_support*( gpu: VkPhysicalDevice
                           , sc: var SwapChain
                           , theQni: var uint32
                           , instance: VkInstance
                           ) =
 var
  queueCount : uint32
  formatCount: uint32
  supportsPresent: seq[VkBool32]
  gqni: uint32 = uint32.high
  pqni: uint32 = uint32.high
 
 when defined windows: 
  vkGetPhysicalDeviceQueueFamilyProperties = cast[proc(physicalDevice: VkPhysicalDevice, pQueueFamilyPropertyCount: ptr uint32 , pQueueFamilyProperties: ptr VkQueueFamilyProperties ): void {.stdcall.}]
   (vkGetInstanceProcAddr( instance
                         , "vkGetPhysicalDeviceQueueFamilyProperties"
                         )
   )
  
  vkGetPhysicalDeviceSurfaceSupportKHR = cast[proc(physicalDevice: VkPhysicalDevice, queueFamilyIndex: uint32, surface: VkSurfaceKHR, pSupported: ptr VkBool32 ): VkResult {.stdcall.}]
  (vkGetInstanceProcAddr(instance
                      , "vkGetPhysicalDeviceSurfaceSupportKHR"
                      )
  )

  vkGetPhysicalDeviceSurfaceFormatsKHR = cast[proc(physicalDevice: VkPhysicalDevice, surface: VkSurfaceKHR, pSurfaceFormatCount: ptr uint32 , pSurfaceFormats: ptr VkSurfaceFormatKHR ): VkResult {.stdcall.}]
  (vkGetInstanceProcAddr(instance
                      , "vkGetPhysicalDeviceSurfaceFormatsKHR"
                      )
  )
 
 vkGetPhysicalDeviceQueueFamilyProperties(gpu, queueCount.addr, nil)
 var queueProperties = newSeq[VkQueueFamilyProperties](queueCount)
 vkGetPhysicalDeviceQueueFamilyProperties(gpu, queueCount.addr, queueProperties[0].addr)
 
 if queueCount == 0: quit "ERROR: No Queue Families were found by `vkGetPhysicalDeviceQueueFamilyProperties`. \n       Which means Vulkan has no way to actually submit things to the GPU to draw."
 supportsPresent.setLen queueCount

 for q in 0 ..< queueCount:
  discard vkGetPhysicalDeviceSurfaceSupportKHR( gpu
                                              , q.uint32
                                              , sc.surface
                                              , supportsPresent[q].addr
                                              )
 
 
 # find a q that can present and do graphics
 for i in 0..<queueCount:
  if bitand(queueProperties[i].queueFlags.int, VK_QUEUE_GRAPHICS_BIT.int) != 0:
   if gqni == uint32.high: gqni = i     
   if supportsPresent[i].bool == true: 
    gqni = i
    pqni = i

 # no Q supports presenting and graphics  
 # find a separate one
 if pqni == uint32.high:
  for i in 0..queueCount:
   if supportsPresent[i].bool == true: pqni = i
  
 if gqni == uint32.high or
    pqni == uint32.high: quit "ERROR: No valid Queue was found, so no GPU work can be done."
 
 if pqni != gqni: 
  echo "WARNING: The present queue and graphics queue are not the same."
  echo "WARNING: The present queue and graphics queue are not the same."
  echo "WARNING: The present queue and graphics queue are not the same."
  echo "WARNING: The present queue and graphics queue are not the same."
 
 theQni = gqni
 
 #Find supported surface formats

 discard vkGetPhysicalDeviceSurfaceFormatsKHR(gpu, sc.surface, formatCount.addr, nil)
 if formatCount == 0: quit "ERROR: No surface formats found by `vkGetPhysicalDeviceSurfaceFormatsKHR` \n       Which Vulkan needs in order to organize and construct a frame for the GPU "
 sc.formats.setLen formatCount
 discard vkGetPhysicalDeviceSurfaceFormatsKHR(gpu, sc.surface, formatCount.addr, addr sc.formats[0])
 
 # The surface has no preferred format, so we assume VK_FORMAT_B8G8R8A8_UNORM
 # TODO: Something better?
 if formatCount == 1 and sc.formats[0].format == VK_FORMAT_UNDEFINED:
   sc.colorFormat = VK_FORMAT_B8G8R8A8_UNORM
   sc.colorSpace = sc.formats[0].colorSpace
 
 else: 
  var found: bool
  for sformat in sc.formats:
   if sformat.format == VK_FORMAT_B8G8R8A8_UNORM:
    sc.colorFormat = sformat.format
    sc.colorSpace = sformat.colorSpace
    found = true
    break

  if not found: 
   echo "WARNING: NOT FOUND: VK_FORMAT_B8G8R8A8_UNORM"
   echo "USING: ", $sc.formats[0].format
   echo "USING: ", $sc.formats[0].colorSpace

   sc.colorFormat = sc.formats[0].format
   sc.colorSpace = sc.formats[0].colorSpace 

proc create_swapchain*( device: VkDevice 
                      , sc: var Swapchain 
                      , gpu: VkPhysicalDevice
                      , current_viewport: var VkViewport
                      ) =

 
 var oldswap = sc.oldSwapchain
 var gpu_surface_capabilities_result = vkGetPhysicalDeviceSurfaceCapabilitiesKHR( gpu
                                                  , sc.surface
                                                  , sc.surfaceCaps.addr
                                                  ) 
 
 #echo "gpu_surface_capa result: " & $gpu_surface_capabilities_result
 discard gpu.vkGetPhysicalDeviceSurfacePresentModesKHR( sc.surface
                                                      , addr sc.presentModeCount
                                                      , nil
                                                      )
 assert(sc.presentModeCount > 0)
 
 sc.presentModes.setLen sc.presentModeCount
 discard gpu.vkGetPhysicalDeviceSurfacePresentModesKHR( sc.surface
                                                      , addr sc.presentModeCount
                                                      , addr sc.presentModes[0]
                                                      )
 
 sc.the_presentation_mode = VK_PRESENT_MODE_MAILBOX_KHR
 
 # vulkan special value -> the size of the surface will be set by the swapchain
 if sc.surfaceCaps.currentExtent.width == uint 0xFFFFFFFF: 
  echo "0xFFFFFFFFFFFFFFFFFF"
  #sc.extent.width = .theWindowWidth
  #sc.extent.height = .theWindowHeight
 
 else:
  sc.current_extent = sc.surfaceCaps.currentExtent 
  current_viewport = VkViewport( width: float32 sc.surfaceCaps.currentExtent.width
                               , height: (float32 sc.surfaceCaps.currentExtent.height)
                               , minDepth: 0.0f
                               , maxDepth: 1.0f
                               )
  #rec.swapchain.current_extent.width = int sc.surfaceCaps.currentExtent.width
  #rec.swapchain.current_extent.height = int sc.surfaceCaps.currentExtent.height
 
 var 
  desired_number_of_swapchain_images = sc.surfaceCaps.minImageCount + 1 #(?) 3
  compositeAlpha:VkCompositeAlphaFlagBitsKHR = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR
  compositeAlphaFlags = [ VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR
                        , VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR
                        , VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR
                        , VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR
                        ]

 # determine number of images  
 if sc.surfaceCaps.maxImageCount > 0 and
    desired_number_of_swapchain_images > sc.surfaceCaps.maxImageCount: 
  desired_number_of_swapchain_images = sc.surfaceCaps.maxImageCount 
 
  # find transform of the surface
 if bitand( sc.surfaceCaps.supportedTransforms.int
          , VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR.int
          ) == 1:
  sc.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR
 
 else:
  echo "ERROR: bitand(scSupportDetails.capabilities.supportedTransforms.int, VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR.int) == 1 -> FAILED"
  sc.preTransform = sc.surfaceCaps.currentTransform
 
 # find alpha format
 for flag in compositeAlphaFlags:
  if bitand(sc.surfaceCaps.supportedCompositeAlpha.int, flag.int) == 1: 
   compositeAlpha = flag 
   break
 
 var createInfo = VkSwapchainCreateInfoKHR(
    sType: VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR
   , surface: sc.surface
    , minImageCount: desired_number_of_swapchain_images
   , imageFormat: sc.colorFormat
   , imageColorSpace: sc.colorSpace
   , imageExtent: sc.current_extent
   , imageUsage: VkImageUsageFlags bitor( VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT.int
                                        , VK_IMAGE_USAGE_TRANSFER_SRC_BIT.int
                                        , VK_IMAGE_USAGE_TRANSFER_DST_BIT.int
                                        )
   , preTransform: VkSurfaceTransformFlagBitsKHR sc.preTransform
   , imageArrayLayers: 1
   , imageSharingMode: VK_SHARING_MODE_EXCLUSIVE
   , queueFamilyIndexCount: 0
   , presentMode: VK_PRESENT_MODE_FIFO_KHR #TODO sc.the_presentation_mode # need to do this proper later
   , oldSwapChain: oldswap
   , clipped: VKBool32 VK_TRUE
   , compositeAlpha: compositeAlpha 
  )
 
 var scres = vkCreateSwapChainKHR( device
                                 , addr createInfo
                                 , nil
                                 , addr sc.handle
                                 ) 

 if scres != VK_SUCCESS: quit $scres
 
 
 discard vkGetSwapchainImagesKHR( device
                                , sc.handle
                                , addr sc.image_count
                                , nil
                                )
 
 sc.images.setLen sc.image_count

 discard vkGetSwapchainImagesKHR( device
                                , sc.handle
                                , addr sc.image_count
                                , addr sc.images[0]
                                )
 
 sc.buffers.setLen sc.image_count
 
 for buffer in sc.buffers.mitems:
  buffer = Swapchain_Buffer()

 sc.colorFormat = VK_FORMAT_B8G8R8A8_UNORM
 sc.image_count = sc.image_count

 for i in 0 ..< sc.image_count:
  var createInfo = VkImageViewCreateInfo( sType: VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO
                                          , viewType: VK_IMAGE_VIEW_TYPE_2D
                                          , format: sc.colorFormat
                                          , flags: VkImageViewCreateFlags 0
                                          , components: VkComponentMapping( r: VkComponentSwizzle.VK_COMPONENT_SWIZZLE_R
                                                                          , g: VkComponentSwizzle.VK_COMPONENT_SWIZZLE_G
                                                                          , b: VkComponentSwizzle.VK_COMPONENT_SWIZZLE_B
                                                                          , a: VkComponentSwizzle.VK_COMPONENT_SWIZZLE_A
                                                                          )
                                          , image: sc.images[i]
                                          , subresourceRange: VkImageSubresourceRange(
                                             aspectMask: VkImageAspectFlags(VK_IMAGE_ASPECT_COLOR_BIT)
                                             , baseMipLevel: 0
                                             , levelCount: 1
                                             , baseArrayLayer: 0
                                             , layerCount: 1
                                            )
                                          )

  sc.buffers[i].image = sc.images[i]
  create_info.image = sc.buffers[i].image
  
  var 
   image_view_result = vkCreateImageView( device
                                        , createInfo.addr
                                        , nil
                                        , addr sc.buffers[i].image_view
                                        ) 

  assert image_view_result == VK_SUCCESS, "vkCreateImageView failed"

 if (sc.oldSwapchain.int != VkSwapchainKHR(0).int):
  for i in 0 ..< sc.image_count: vkDestroyImageView( device
                                                  , sc.buffers[i].image_view
                                                  , nil
                                                  )
  vkDestroySwapchainKHR( device
                       , sc.oldSwapchain
                       , nil
                       ) 

#[ proc anImageView*( device: VkDevice
                 , sc: var SwapChain
                 , rebuilding: bool = false
                 ) = 
 
 if rebuilding: 
  sc.images.setLen 0
 
 discard vkGetSwapchainImagesKHR(device, sc.handle, addr sc.image_count, nil)
 sc.images.setLen sc.image_count
 discard vkGetSwapchainImagesKHR(device, sc.handle, sc.image_count.addr, addr sc.images[0])
  
 sc.buffers.setLen sc.image_count
 for b in 0..<sc.buffers.len:
  sc.buffers[b] = SwapChain_Buffer()

 sc.colorFormat = VK_FORMAT_B8G8R8A8_UNORM
 sc.image_count = sc.image_count
 for i in 0 ..< sc.image_count: 
  var createInfo = VkImageViewCreateInfo( sType: VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO
                                          , viewType: VK_IMAGE_VIEW_TYPE_2D
                                          , format: sc.colorFormat
                                          , flags: VkImageViewCreateFlags 0
                                          , components: VkComponentMapping( r: VK_COMPONENT_SWIZZLE_IDENTITY
                                                                          , g: VK_COMPONENT_SWIZZLE_IDENTITY
                                                                          , b: VK_COMPONENT_SWIZZLE_IDENTITY
                                                                          , a: VK_COMPONENT_SWIZZLE_IDENTITY
                                                                          )
                                          , subresourceRange: VkImageSubresourceRange(
                                             aspectMask: VkImageAspectFlags(VK_IMAGE_ASPECT_COLOR_BIT)
                                             , baseMipLevel: 0
                                             , levelCount: 1
                                             , baseArrayLayer: 0
                                             , layerCount: 1
                                            )
                                          )

  sc.buffers[i].image = sc.images[i]
  createInfo.image = sc.buffers[i].image
  if vkCreateImageView( device
                      , createInfo.addr
                      , nil
                      , sc.buffers[i].view.addr
                      ) != VK_SUCCESS: quit("failed to create image view") ]#

proc create_frame_buffers*( vk_device: VkDevice 
                                     , frame_buffers: var seq[VkFramebuffer]
                                     , render_pass: var VkRenderPass
                                     , swapchain: var Swapchain
                                     , depth_stencil: var Depth_Stencil
                                     , rebuilding = false
                                     ) =
 
 if rebuilding:
  frame_buffers.setLen 0

 frame_buffers.setLen swapchain.image_count
 #echo frame_buffers.len
 
 for i in 0 ..< frame_buffers.len:
  var attachments: array[2, VkImageView]

  # Depth/Stencil attachment is the same for all frame buffers
  attachments[0] = swapchain.buffers[i].image_view
  attachments[1] = depth_stencil.view
  
  var frameBufferCreateInfo = VkFramebufferCreateInfo(
      sType: VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO
    , pNext: nil
    , render_pass: render_pass
    , attachmentCount: uint32 attachments.len
    , pAttachments: addr attachments[0]
    , width: uint32  swapchain.current_extent.width 
    , height: uint32 swapchain.current_extent.height 
    , layers: 1
    )
  
  assert vkCreateFramebuffer( vk_device
                               , addr frameBufferCreateInfo
                               , nil
                               , addr frame_buffers[i]
                               ) == VK_SUCCESS
 #echo repr frame_buffers

#TODO: we need to somehow get Scene in this without recursive dep errors
# so we can rebuild render_Pass_Begin_info toooo
proc rebuild*( vk_device: VkDevice 
             , frame_buffers: var seq[VkFramebuffer]
             , render_pass: var VkRenderPass
             , swapchain: var Swapchain
             , depth_stencil: var Depth_Stencil
             , gpu_memory_properties: VkPhysicalDeviceMemoryProperties
             ) = 
             
 vk_device.destroyDepthStencil depth_stencil
 #[ depth_stencil = create_depth_stencil( vk_device
                                  , swapchain.color_format
                                  , swapchain.current_extent
                                  , gpu_memory_properties
                                  ) ]#

 frame_buffers.setLen 0
 
 frame_buffers.setLen swapchain.image_count
 for i in 0 ..< frame_buffers.len:
  var attachments: array[2, VkImageView]

  # Depth/Stencil attachment is the same for all frame buffers
  attachments[0] = swapchain.buffers[i].image_view
  attachments[1] = depth_stencil.view 
  
  var frameBufferCreateInfo = VkFramebufferCreateInfo(
      sType: VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO
    , pNext: nil
    , render_pass: render_pass
    , attachmentCount: 2
    , pAttachments: addr attachments[0]
    , width:  swapchain.current_extent.width
    , height: swapchain.current_extent.height
    , layers: 1
    )
  
  if vkCreateFramebuffer( vk_device
                        , addr frameBufferCreateInfo
                        , nil
                        , addr frame_buffers[i]
                        ) != VK_SUCCESS: quit "bad framebiuffer create"
  
 #[ for shape in scene.shapes.mitems:
  #rec.setupDescrSetLayout shape
  shapes.prepPipeline( rec
                     , scene.render_pass
                     , shape.graphicsPipeline
                     , shape.hollow
                     , shape.kind
                     )
 
 for text in scene.texts.mitems:
  rec.setupDescrSetLayout text
  
  textM.prepPipeline( rec
                    , render_pass 
                    , text.graphicsPipeline
                    ) ]#

 #rec.build scene