package instance

import (
	"github.com/pkg/errors"
	"github.com/vkngwrapper/core/v2/core1_0"
	"github.com/vkngwrapper/extensions/v2/khr_portability_subset"
	"github.com/vkngwrapper/extensions/v2/khr_surface"
	"github.com/vkngwrapper/extensions/v2/khr_swapchain"
	vkng_sdl2 "github.com/vkngwrapper/integrations/sdl2/v2"
	"os"
)

func (e *FInstance) createSurface() error {
	surfaceLoader := khr_surface.CreateExtensionFromInstance(e.ptr)

	surface, err := vkng_sdl2.CreateSurface(e.ptr, surfaceLoader, e.win.GetInstance())
	if err != nil {
		return err
	}

	e.surface = surface
	return nil
}
func (e *FInstance) pickPhysicalDevice() error {
	physicalDevices, _, err := e.ptr.EnumeratePhysicalDevices()
	if err != nil {
		return err
	}

	for _, device := range physicalDevices {
		if help.isDeviceSuitable(device) {
			e.physicalDevice = device
			break
		}
	}

	if e.physicalDevice == nil {
		return errors.New("failed to find a suitable GPU!")
	}

	return nil
}
func (e *FInstance) createLogicalDevice() error {
	indices, err := help.findQueueFamilies(e.physicalDevice)
	if err != nil {
		return err
	}

	uniqueQueueFamilies := []int{*indices.GraphicsFamily}
	if uniqueQueueFamilies[0] != *indices.PresentFamily {
		uniqueQueueFamilies = append(uniqueQueueFamilies, *indices.PresentFamily)
	}

	var queueFamilyOptions []core1_0.DeviceQueueCreateInfo
	queuePriority := float32(1.0)
	for _, queueFamily := range uniqueQueueFamilies {
		queueFamilyOptions = append(queueFamilyOptions, core1_0.DeviceQueueCreateInfo{
			QueueFamilyIndex: queueFamily,
			QueuePriorities:  []float32{queuePriority},
		})
	}

	var extensionNames []string
	extensionNames = append(extensionNames, deviceExtensions...)

	// Makes this example compatible with vulkan portability, necessary to run on mobile & mac
	extensions, _, err := e.physicalDevice.EnumerateDeviceExtensionProperties()
	if err != nil {
		return err
	}

	_, supported := extensions[khr_portability_subset.ExtensionName]
	if supported {
		extensionNames = append(extensionNames, khr_portability_subset.ExtensionName)
	}

	e.device, _, err = e.physicalDevice.CreateDevice(nil, core1_0.DeviceCreateInfo{
		QueueCreateInfos:      queueFamilyOptions,
		EnabledFeatures:       &core1_0.PhysicalDeviceFeatures{},
		EnabledExtensionNames: extensionNames,
	})
	if err != nil {
		return err
	}

	e.graphicsQueue = e.device.GetQueue(*indices.GraphicsFamily, 0)
	e.presentQueue = e.device.GetQueue(*indices.PresentFamily, 0)
	return nil
}

func (e *FInstance) createSwapchain() error {
	e.swapchainExtension = khr_swapchain.CreateExtensionFromDevice(e.device)

	swapchainSupport, err := help.querySwapChainSupport(e.physicalDevice)
	if err != nil {
		return err
	}

	surfaceFormat := help.chooseSwapSurfaceFormat(swapchainSupport.Formats)
	presentMode := help.chooseSwapPresentMode(swapchainSupport.PresentModes)
	extent := help.chooseSwapExtent(swapchainSupport.Capabilities)

	imageCount := swapchainSupport.Capabilities.MinImageCount + 1
	if swapchainSupport.Capabilities.MaxImageCount > 0 && swapchainSupport.Capabilities.MaxImageCount < imageCount {
		imageCount = swapchainSupport.Capabilities.MaxImageCount
	}

	sharingMode := core1_0.SharingModeExclusive
	var queueFamilyIndices []int

	indices, err := help.findQueueFamilies(e.physicalDevice)
	if err != nil {
		return err
	}

	if *indices.GraphicsFamily != *indices.PresentFamily {
		sharingMode = core1_0.SharingModeConcurrent
		queueFamilyIndices = append(queueFamilyIndices, *indices.GraphicsFamily, *indices.PresentFamily)
	}

	swapchain, _, err := e.swapchainExtension.CreateSwapchain(e.device, nil, khr_swapchain.SwapchainCreateInfo{
		Surface: e.surface,

		MinImageCount:    imageCount,
		ImageFormat:      surfaceFormat.Format,
		ImageColorSpace:  surfaceFormat.ColorSpace,
		ImageExtent:      extent,
		ImageArrayLayers: 1,
		ImageUsage:       core1_0.ImageUsageColorAttachment,

		ImageSharingMode:   sharingMode,
		QueueFamilyIndices: queueFamilyIndices,

		PreTransform:   swapchainSupport.Capabilities.CurrentTransform,
		CompositeAlpha: khr_surface.CompositeAlphaOpaque,
		PresentMode:    presentMode,
		Clipped:        true,
	})
	if err != nil {
		return err
	}
	e.swapchainExtent = extent
	e.swapchain = swapchain
	e.swapchainImageFormat = surfaceFormat.Format

	images, _, err := swapchain.SwapchainImages()
	if err != nil {
		return err
	}
	e.swapchainImages = images

	var imageViews []core1_0.ImageView
	for _, image := range images {
		view, _, err := e.device.CreateImageView(nil, core1_0.ImageViewCreateInfo{
			ViewType: core1_0.ImageViewType2D,
			Image:    image,
			Format:   surfaceFormat.Format,
			Components: core1_0.ComponentMapping{
				R: core1_0.ComponentSwizzleIdentity,
				G: core1_0.ComponentSwizzleIdentity,
				B: core1_0.ComponentSwizzleIdentity,
				A: core1_0.ComponentSwizzleIdentity,
			},
			SubresourceRange: core1_0.ImageSubresourceRange{
				AspectMask:     core1_0.ImageAspectColor,
				BaseMipLevel:   0,
				LevelCount:     1,
				BaseArrayLayer: 0,
				LayerCount:     1,
			},
		})
		if err != nil {
			return err
		}

		imageViews = append(imageViews, view)
	}
	e.swapchainImageViews = imageViews

	return nil
}
func (e *FInstance) createRenderPass() error {
	renderPass, _, err := e.device.CreateRenderPass(nil, core1_0.RenderPassCreateInfo{
		Attachments: []core1_0.AttachmentDescription{
			{
				Format:         e.swapchainImageFormat,
				Samples:        core1_0.Samples1,
				LoadOp:         core1_0.AttachmentLoadOpClear,
				StoreOp:        core1_0.AttachmentStoreOpStore,
				StencilLoadOp:  core1_0.AttachmentLoadOpDontCare,
				StencilStoreOp: core1_0.AttachmentStoreOpDontCare,
				InitialLayout:  core1_0.ImageLayoutUndefined,
				FinalLayout:    khr_swapchain.ImageLayoutPresentSrc,
			},
		},
		Subpasses: []core1_0.SubpassDescription{
			{
				PipelineBindPoint: core1_0.PipelineBindPointGraphics,
				ColorAttachments: []core1_0.AttachmentReference{
					{
						Attachment: 0,
						Layout:     core1_0.ImageLayoutColorAttachmentOptimal,
					},
				},
			},
		},
		SubpassDependencies: []core1_0.SubpassDependency{
			{
				SrcSubpass: core1_0.SubpassExternal,
				DstSubpass: 0,

				SrcStageMask:  core1_0.PipelineStageColorAttachmentOutput,
				SrcAccessMask: 0,

				DstStageMask:  core1_0.PipelineStageColorAttachmentOutput,
				DstAccessMask: core1_0.AccessColorAttachmentWrite,
			},
		},
	})
	if err != nil {
		return err
	}

	e.renderPass = renderPass

	return nil
}

func (e *FInstance) createGraphicsPipeline() error {
	// Load vertex shader
	vertShaderBytes, err := os.ReadFile("asset/shaders/vert.spv")
	if err != nil {
		return err
	}

	vertShader, _, err := e.device.CreateShaderModule(nil, core1_0.ShaderModuleCreateInfo{
		Code: bytesToBytecode(vertShaderBytes),
	})
	if err != nil {
		return err
	}
	defer vertShader.Destroy(nil)

	// Load fragment shader
	fragShaderBytes, err := os.ReadFile("asset/shaders/frag.spv")
	if err != nil {
		return err
	}

	fragShader, _, err := e.device.CreateShaderModule(nil, core1_0.ShaderModuleCreateInfo{
		Code: bytesToBytecode(fragShaderBytes),
	})
	if err != nil {
		return err
	}
	defer fragShader.Destroy(nil)

	vertexInput := &core1_0.PipelineVertexInputStateCreateInfo{}

	inputAssembly := &core1_0.PipelineInputAssemblyStateCreateInfo{
		Topology:               core1_0.PrimitiveTopologyTriangleList,
		PrimitiveRestartEnable: false,
	}

	vertStage := core1_0.PipelineShaderStageCreateInfo{
		Stage:  core1_0.StageVertex,
		Module: vertShader,
		Name:   "main",
	}

	fragStage := core1_0.PipelineShaderStageCreateInfo{
		Stage:  core1_0.StageFragment,
		Module: fragShader,
		Name:   "main",
	}

	viewport := &core1_0.PipelineViewportStateCreateInfo{
		Viewports: []core1_0.Viewport{
			{
				X:        0,
				Y:        0,
				Width:    float32(e.swapchainExtent.Width),
				Height:   float32(e.swapchainExtent.Height),
				MinDepth: 0,
				MaxDepth: 1,
			},
		},
		Scissors: []core1_0.Rect2D{
			{
				Offset: core1_0.Offset2D{X: 0, Y: 0},
				Extent: e.swapchainExtent,
			},
		},
	}

	rasterization := &core1_0.PipelineRasterizationStateCreateInfo{
		DepthClampEnable:        false,
		RasterizerDiscardEnable: false,

		PolygonMode: core1_0.PolygonModeFill,
		CullMode:    core1_0.CullModeBack,
		FrontFace:   core1_0.FrontFaceClockwise,

		DepthBiasEnable: false,

		LineWidth: 1.0,
	}

	multisample := &core1_0.PipelineMultisampleStateCreateInfo{
		SampleShadingEnable:  false,
		RasterizationSamples: core1_0.Samples1,
		MinSampleShading:     1.0,
	}

	colorBlend := &core1_0.PipelineColorBlendStateCreateInfo{
		LogicOpEnabled: false,
		LogicOp:        core1_0.LogicOpCopy,

		BlendConstants: [4]float32{0, 0, 0, 0},
		Attachments: []core1_0.PipelineColorBlendAttachmentState{
			{
				BlendEnabled:   false,
				ColorWriteMask: core1_0.ColorComponentRed | core1_0.ColorComponentGreen | core1_0.ColorComponentBlue | core1_0.ColorComponentAlpha,
			},
		},
	}

	e.pipelineLayout, _, err = e.device.CreatePipelineLayout(nil, core1_0.PipelineLayoutCreateInfo{})
	if err != nil {
		return err
	}

	pipelines, _, err := e.device.CreateGraphicsPipelines(nil, nil, []core1_0.GraphicsPipelineCreateInfo{
		{
			Stages: []core1_0.PipelineShaderStageCreateInfo{
				vertStage,
				fragStage,
			},
			VertexInputState:   vertexInput,
			InputAssemblyState: inputAssembly,
			ViewportState:      viewport,
			RasterizationState: rasterization,
			MultisampleState:   multisample,
			ColorBlendState:    colorBlend,
			Layout:             e.pipelineLayout,
			RenderPass:         e.renderPass,
			Subpass:            0,
			BasePipelineIndex:  -1,
		},
	})
	if err != nil {
		return err
	}
	e.graphicsPipeline = pipelines[0]

	return nil
}

func (e *FInstance) createFramebuffers() error {
	for _, imageView := range e.swapchainImageViews {
		framebuffer, _, err := e.device.CreateFramebuffer(nil, core1_0.FramebufferCreateInfo{
			RenderPass: e.renderPass,
			Layers:     1,
			Attachments: []core1_0.ImageView{
				imageView,
			},
			Width:  e.swapchainExtent.Width,
			Height: e.swapchainExtent.Height,
		})
		if err != nil {
			return err
		}

		e.swapchainFramebuffers = append(e.swapchainFramebuffers, framebuffer)
	}

	return nil
}

func (e *FInstance) createCommandPool() error {
	indices, err := help.findQueueFamilies(e.physicalDevice)
	if err != nil {
		return err
	}

	pool, _, err := e.device.CreateCommandPool(nil, core1_0.CommandPoolCreateInfo{
		QueueFamilyIndex: *indices.GraphicsFamily,
	})

	if err != nil {
		return err
	}
	e.commandPool = pool

	return nil
}

func (e *FInstance) createCommandBuffers() error {
	buffers, _, err := e.device.AllocateCommandBuffers(core1_0.CommandBufferAllocateInfo{
		CommandPool:        e.commandPool,
		Level:              core1_0.CommandBufferLevelPrimary,
		CommandBufferCount: len(e.swapchainImages),
	})
	if err != nil {
		return err
	}
	e.commandBuffers = buffers

	for bufferIdx, buffer := range buffers {
		_, err = buffer.Begin(core1_0.CommandBufferBeginInfo{})
		if err != nil {
			return err
		}

		err = buffer.CmdBeginRenderPass(core1_0.SubpassContentsInline,
			core1_0.RenderPassBeginInfo{
				RenderPass:  e.renderPass,
				Framebuffer: e.swapchainFramebuffers[bufferIdx],
				RenderArea: core1_0.Rect2D{
					Offset: core1_0.Offset2D{X: 0, Y: 0},
					Extent: e.swapchainExtent,
				},
				ClearValues: []core1_0.ClearValue{
					core1_0.ClearValueFloat{0, 0, 0, 1},
				},
			})
		if err != nil {
			return err
		}

		buffer.CmdBindPipeline(core1_0.PipelineBindPointGraphics, e.graphicsPipeline)
		buffer.CmdDraw(3, 1, 0, 0)
		buffer.CmdEndRenderPass()

		_, err = buffer.End()
		if err != nil {
			return err
		}
	}

	return nil
}
func (e *FInstance) createSyncObjects() error {
	for i := 0; i < MaxFramesInFlight; i++ {
		semaphore, _, err := e.device.CreateSemaphore(nil, core1_0.SemaphoreCreateInfo{})
		if err != nil {
			return err
		}

		e.imageAvailableSemaphore = append(e.imageAvailableSemaphore, semaphore)

		semaphore, _, err = e.device.CreateSemaphore(nil, core1_0.SemaphoreCreateInfo{})
		if err != nil {
			return err
		}

		e.renderFinishedSemaphore = append(e.renderFinishedSemaphore, semaphore)

		fence, _, err := e.device.CreateFence(nil, core1_0.FenceCreateInfo{
			Flags: core1_0.FenceCreateSignaled,
		})
		if err != nil {
			return err
		}

		e.inFlightFence = append(e.inFlightFence, fence)
	}

	for i := 0; i < len(e.swapchainImages); i++ {
		e.imagesInFlight = append(e.imagesInFlight, nil)
	}

	return nil
}
func (e *FInstance) Init() error {
	help.vulkan = e
	err := e.createSurface()
	if err != nil {
		return err
	}
	err = e.pickPhysicalDevice()
	if err != nil {
		return err
	}
	err = e.createLogicalDevice()
	if err != nil {
		return err
	}
	err = e.createSwapchain()
	if err != nil {
		return err
	}
	err = e.createRenderPass()
	if err != nil {
		return err
	}
	err = e.createGraphicsPipeline()
	if err != nil {
		return err
	}
	err = e.createFramebuffers()
	if err != nil {
		return err
	}
	err = e.createCommandPool()
	if err != nil {
		return err
	}
	err = e.createCommandBuffers()
	if err != nil {
		return err
	}
	return e.createSyncObjects()
}
