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

#TODO: REBUILD render_Pass_begin_info on window size change 

import ../vk/[ vulkan_record
             , vulkan_utils
             , vulkan 
             , depth_stencil
             , swapchain 
             , buffer
             ]

, scene_object
, scene_record

, ../deshn_entity/[ being 
                 
                  ]

, ../drawable/[ shape_object
              , text_types
              , plane
              ]

, ../wain/[ waindow_object
            ]

, std/[ tables 
      , bitops 
      ]

, glm

proc a_scene*( vk_record: var Vulkan_Record
             ): Scene =
 result = Scene()
 result.entities = newTable[string, int]()


proc theIds*(scene: Scene): seq[int] =
 for t in scene.shapes: result.add t.id

proc theNames*(scene: Scene): seq[string] =
 for t in scene.shapes: result.add t.name

proc the_shape*( scene: var Scene
              , id: int
              , name: string
              ): var Shape = 

 for t in scene.shapes.mitems:
  if t.id == id: return t
 
 quit("ERROR: Entity[SHAPE] not found: " & name & ": id" & $id)

proc the_shape*( scene: var Scene
              , name: string
              ): var Shape = 
 scene.the_shape scene.entities[name], name

proc the_deshn_being*( scene: var Scene
                     , id: int
                     , name: string
                     ): var Deshn_Being = 

 for t in scene.deshn_beings.mitems:
  if t.id == id: return t
 
 quit("ERROR: Entity[SHAPE] not found: " & name & ": id" & $id)

proc the_deshn_being*( scene: var Scene
                     , name: string
                     ): var Deshn_Being = 
 scene.the_deshn_being scene.entities[name], name


#[ proc the_text*( scene: Scene
              , id: int
              , name: string
              ): var SDFText = 
              
 #WARNING]: Cannot prove that 'result' is initialized.   This will become a compile time error in the future. [ProveInit]
 for t in scene.texts.mitems: 
  if t.id == id: return t
 
 quit("ERROR: Entity[SDFTEXT] not found: " & name & " / id:" & $id)

proc the_text*( scene: Scene
              , name: string
              ): var SDFText = scene.the_text scene.entities[name], name ]#

# # TODO: need drawable-independent command buffers 
# proc build*( vk_record: Vulkan_Record
#            , scene: var Scene
#            ) =

proc the_plane*( scene: var Scene
               , id: int
               , name: string
               ): var Plane = 

 for t in scene.planes.mitems:
  if t.id == id: return t
 
 quit("ERROR: Entity[SHAPE] not found: " & name & ": id" & $id)

proc the_plane*( scene: var Scene
               , name: string
               ): var Plane = 
 scene.the_plane scene.entities[name], name
 
proc add*( scene: var Scene
         , shape: Shape
         ) = 
 #echo "adding: ", sh.name, " ", sh.id
 scene.shapes.add shape
 scene.entities[shape.name] = shape.id
 scene.current_entity_id += 1

 scene.the_vertices.add shape.vertices
 scene.the_indices.add shape.indices

#[ proc add*( scene: var Scene
         , text: SDFText
         ) = 
 scene.texts.add text
 scene.entities[text.name] = scene.current_entity_id
 scene.current_entity_id += 1
 
 for text_vert in text.vertices:
  scene.the_vertices.add text_vert.pos.x
  scene.the_vertices.add text_vert.pos.y
  scene.the_vertices.add text_vert.pos.z
 
  scene.the_vertices.add text_vert.uv.x
  scene.the_vertices.add text_vert.uv.y
 
 scene.the_indices.add  text.indices
 ]#
proc add*( scene: var Scene
         , deshn_being: Deshn_Being
         ) = 
 #echo "adding: ", sh.name, " ", sh.id
 scene.deshn_beings.add deshn_being
 scene.entities[deshn_being.name] = deshn_being.id
 scene.current_entity_id += 1

 scene.add deshn_being.shape

proc add*( scene: var Scene
         , plane: Plane 
         ) = 
 #echo "adding: ", sh.name, " ", sh.id
 scene.planes.add plane
 scene.entities[plane.name] = plane.id
 scene.current_entity_id += 1

 scene.add plane.shape

proc prepare_vertices*( scene: var Scene
                                    , vk_device: VkDevice
                                    , memory_properties: VkPhysicalDeviceMemoryProperties
                                    , command_pool: VkCommandPool
                                    , queue: VkQueue
                                    , master_vertex_buffer: var Buffer
                                    , master_index_buffer: var Buffer
                                    ) = 
 var 
  copyCmd: VkCommandBuffer = vk_device.getCommandBuffers(command_pool, true)
  copyRegion: VkBufferCopy
  data: pointer
 
#[  copyMem( data, addr scene.the_vertices[0]
        , Natural scene.the_vertices.sizeof
        )
 
 vkUnmapMemory( vk_device
              , scene.staging_vertex_buffer.device_memory
              )
 
 discard vkBindBufferMemory( vk_device
                           , scene.staging_vertex_buffer.vkbuffer
                           , scene.staging_vertex_buffer.device_memory
                           , VkDeviceSize 0
                           )

 discard vkBindBufferMemory( vk_device
                           , scene.vertex_buffer.vkbuffer
                           , scene.vertex_buffer.device_memory
                           , VkDeviceSize 0
                           )  
  
 scene.staging_index_buffer = a_vulkan_buffer( vk_device 
                                      , memory_properties
                                      ,  VkBufferUsageFlags VK_BUFFER_USAGE_TRANSFER_SRC_BIT
                                       , VkMemoryPropertyFlags bitor( VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT.ord
                                                                    , VK_MEMORY_PROPERTY_HOST_COHERENT_BIT.ord
                                                                    )
                                       , VkDeviceSize scene.the_indices.sizeof
                                       ) 
   
   
 scene.index_buffer = a_vulkan_buffer( vk_device 
                                     , 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
                                     , VkDeviceSize scene.the_indices.sizeof
                                     ) 
 
 vk_device.map_memory( scene.staging_index_buffer.device_memory
                     , data
                     )
 
 copyMem(data, addr scene.the_indices[0], Natural scene.the_indices.sizeof)
 
 vkUnmapMemory(vk_device, scene.staging_index_buffer.device_memory)
 discard vkBindBufferMemory(vk_device, scene.staging_index_buffer.vkbuffer, scene.staging_index_buffer.device_memory, VkDeviceSize 0)
 discard vkBindBufferMemory(vk_device, scene.index_buffer.vkbuffer, scene.index_buffer.device_memory, VkDeviceSize 0)
  
 copyRegion.size = VkDeviceSize scene.the_vertices.sizeof
 vkCmdCopyBuffer(copyCmd, scene.staging_vertex_buffer.vkbuffer, scene.vertex_buffer.vkbuffer, 1.uint32, addr copyRegion) 

 copyRegion.size = VkDeviceSize scene.the_indices.sizeof
 vkCmdCopyBuffer(copyCmd, scene.staging_index_buffer.vkbuffer, scene.index_buffer.vkbuffer, 1.uint32, addr copyRegion)  
 
 #  # Flushing the command buffer will also submit it to the queue and 
 #  # uses a fence to ensure that all commands have been executed before returning
 vk_device.flushCommandBuffer queue, command_pool, copyCmd
 
 # Destroy staging buffers
 # Note: Staging buffer must not be deleted before the copies have been submitted and executed
 vkDestroyBuffer( vk_device
                , scene.staging_vertex_buffer.vkbuffer
                , nil
                )

 vkFreeMemory( vk_device
             , scene.staging_vertex_buffer.device_memory
             , nil
             )
 
 vkDestroyBuffer( vk_device
                , scene.staging_index_buffer.vkbuffer
                , nil
                )

 vkFreeMemory( vk_device
             , scene.staging_index_buffer.device_memory
             , nil
             )   ]#

proc clean_up*( vk_record: var Vulkan_Record
             , scene: var Scene
             ) = 

 discard vkDeviceWaitIdle vk_record.vk_device
 
 # recycle the Pools!
 # vkDestroyDescriptorPool(demo->device, demo->desc_pool, NULL)
 # vkDestroyCommandPool(demo->device, demo->command_pool, NULL);
 
 #should we have an initial "setup command buffer"?
 
 #[ for shape in scene.shapes:
  vk_record.vk_device.vkDestroyPipeline(shape.graphicsPipeline.pipeline, nil)
  vk_record.vk_device.vkDestroyPipelineLayout(shape.graphicsPipeline.pipelineLayout, nil)
  vk_record.vk_device.vkDestroyDescriptorSetLayout(shape.descrSetLayout, nil)
 
 for text in scene.texts:
  vk_record.vk_device.vkDestroyPipeline(text.graphicsPipeline.pipeline, nil)
  vk_record.vk_device.vkDestroyPipelineLayout(text.graphicsPipeline.pipelineLayout, nil)
  vk_record.vk_device.vkDestroyDescriptorSetLayout(text.descrSetLayout, nil) ]#
 
#[  for i,fb in vk_record.draw_command_buffers:
  vkFreeCommandBuffers( vk_record.vk_device
                      , vk_record.command_pool
                      , 1
                      , addr vk_record.draw_command_buffers[i]
                      )
 vk_record.vk_device.vkDestroyRenderPass(scene.render_pass, nil) ]#

#[ 
proc rebuild_render_pass_info*( vk_record: var Vulkan_Record
                                                  , scene: var Scene 
                                                  ) = 

 var
  scissor = VkRect2D( extent: VkExtent2D( width: uint32 vk_record.swapchain.current_extent.width
                                        , height: uint32 vk_record.swapchain.current_extent.height
                                        )
                    
                    , offset: VkOffset2D( x: 0
                                        , y: 0
                                        )  

                    )
  renderPassBeginInfo = VkRenderPassBeginInfo(sType: VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO)
  clearValues: array[2,VkClearValue]

 clearValues[0].color = VkClearColorValue(float32: [0f, 0f, 0f, 1f])
 clearValues[1].depth_stencil = VkClearDepthStencilValue( depth: 1.0f
                                                        , stencil: 0 
                                                        ) 

 renderPassBeginInfo.pNext = nil
 renderPassBeginInfo.renderArea.offset.x = 0
 renderPassBeginInfo.renderArea.offset.y = 0
 renderPassBeginInfo.renderArea.extent.width = uint32 vk_record.swapchain.current_extent.width
 renderPassBeginInfo.renderArea.extent.height = uint32 vk_record.swapchain.current_extent.height
 renderPassBeginInfo.clearValueCount = 2
 renderPassBeginInfo.pClearValues = addr clearValues[0]
 renderPassBeginInfo.render_pass = scene.render_pass
 
 renderPassBeginInfo.framebuffer = vk_record.frame_buffers[vk_record.currentFrameBuffer]
 
 scene[].render_pass_begin_info = renderPassBeginInfo
 scene[].scissor = scissor  ]#