package main

import (
	"encoding/xml"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"
)

const (
	vulkanRegistryURL = "https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/registry/vk.xml"
	outputDir        = "pkg/vulkan"
)

// VulkanRegistry represents the root of the Vulkan XML registry
type VulkanRegistry struct {
	XMLName   xml.Name  `xml:"registry"`
	Platforms []Platform `xml:"platforms>platform"`
	Types     []Type     `xml:"types>type"`
	Enums     []Enum     `xml:"enums"`
	Commands  []Command  `xml:"commands>command"`
	Extensions []Extension `xml:"extensions>extension"`
	Features   []Feature   `xml:"feature"`
}

type Platform struct {
	Name    string `xml:"name,attr"`
	Protect string `xml:"protect,attr"`
	Comment string `xml:"comment,attr"`
}

type Type struct {
	Category string `xml:"category,attr"`
	Name     string `xml:"name,attr"`
	Content  string `xml:",innerxml"`
}

type Enum struct {
	Name   string     `xml:"name,attr"`
	Type   string     `xml:"type,attr"`
	Values []EnumValue `xml:"enum"`
}

type EnumValue struct {
	Name  string `xml:"name,attr"`
	Value string `xml:"value,attr"`
	Bitpos string `xml:"bitpos,attr"`
}

type Command struct {
	Proto  CommandProto `xml:"proto"`
	Params []CommandParam `xml:"param"`
}

type CommandProto struct {
	Content string `xml:",innerxml"`
	Name    string `xml:"name"`
}

type CommandParam struct {
	Content string `xml:",innerxml"`
	Name    string `xml:"name"`
}

type Extension struct {
	Name     string `xml:"name,attr"`
	Number   string `xml:"number,attr"`
	Type     string `xml:"type,attr"`
	Requires []ExtensionRequire `xml:"require"`
}

type ExtensionRequire struct {
	Commands []ExtensionCommand `xml:"command"`
	Types    []ExtensionType    `xml:"type"`
	Enums    []ExtensionEnum    `xml:"enum"`
}

type ExtensionCommand struct {
	Name string `xml:"name,attr"`
}

type ExtensionType struct {
	Name string `xml:"name,attr"`
}

type ExtensionEnum struct {
	Name   string `xml:"name,attr"`
	Value  string `xml:"value,attr"`
	Bitpos string `xml:"bitpos,attr"`
}

type Feature struct {
	API     string `xml:"api,attr"`
	Name    string `xml:"name,attr"`
	Number  string `xml:"number,attr"`
	Require []FeatureRequire `xml:"require"`
}

type FeatureRequire struct {
	Commands []FeatureCommand `xml:"command"`
	Types    []FeatureType    `xml:"type"`
	Enums    []FeatureEnum    `xml:"enum"`
}

type FeatureCommand struct {
	Name string `xml:"name,attr"`
}

type FeatureType struct {
	Name string `xml:"name,attr"`
}

type FeatureEnum struct {
	Name   string `xml:"name,attr"`
	Value  string `xml:"value,attr"`
	Bitpos string `xml:"bitpos,attr"`
}

func main() {
	fmt.Println("Downloading Vulkan Registry...")
	
	resp, err := http.Get(vulkanRegistryURL)
	if err != nil {
		fmt.Printf("Failed to download registry: %v\n", err)
		os.Exit(1)
	}
	defer resp.Body.Close()
	
	if resp.StatusCode != http.StatusOK {
		fmt.Printf("HTTP error: %s\n", resp.Status)
		os.Exit(1)
	}
	
	data, err := io.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("Failed to read response: %v\n", err)
		os.Exit(1)
	}
	
	fmt.Println("Parsing Vulkan Registry...")
	
	var registry VulkanRegistry
	err = xml.Unmarshal(data, &registry)
	if err != nil {
		fmt.Printf("Failed to parse XML: %v\n", err)
		os.Exit(1)
	}
	
	fmt.Printf("Found %d types, %d enums, %d commands, %d extensions\n",
		len(registry.Types), len(registry.Enums), len(registry.Commands), len(registry.Extensions))
	
	// Create output directory
	err = os.MkdirAll(outputDir, 0755)
	if err != nil {
		fmt.Printf("Failed to create output directory: %v\n", err)
		os.Exit(1)
	}
	
	// Generate files
	fmt.Println("Generating Go bindings...")
	
	generateTypes(&registry)
	generateEnums(&registry)
	generateCommands(&registry)
	generateExtensions(&registry)
	generateLoader(&registry)
	
	fmt.Println("Generation complete!")
}

func generateTypes(registry *VulkanRegistry) {
	file, err := os.Create(filepath.Join(outputDir, "types.go"))
	if err != nil {
		panic(err)
	}
	defer file.Close()
	
	tmpl := `// Code generated by generate.go. DO NOT EDIT.

package vulkan

/*
#cgo CFLAGS: -I.
#cgo windows LDFLAGS: -lvulkan-1
#cgo linux LDFLAGS: -lvulkan
#cgo darwin LDFLAGS: -L/usr/local/lib -lMoltenVK

#include <stdint.h>
#include <stdlib.h>

// Forward declarations for Vulkan types
typedef struct VkInstance_T* VkInstance;
typedef struct VkPhysicalDevice_T* VkPhysicalDevice;
typedef struct VkDevice_T* VkDevice;
typedef struct VkQueue_T* VkQueue;
typedef struct VkCommandBuffer_T* VkCommandBuffer;

// Basic types
typedef uint32_t VkFlags;
typedef uint64_t VkDeviceSize;
typedef uint32_t VkSampleMask;
typedef uint32_t VkBool32;
typedef float VkFloat;
typedef double VkDouble;

// Result type
typedef enum VkResult {
    VK_SUCCESS = 0,
    VK_NOT_READY = 1,
    VK_TIMEOUT = 2,
    VK_EVENT_SET = 3,
    VK_EVENT_RESET = 4,
    VK_INCOMPLETE = 5,
    VK_ERROR_OUT_OF_HOST_MEMORY = -1,
    VK_ERROR_OUT_OF_DEVICE_MEMORY = -2,
    VK_ERROR_INITIALIZATION_FAILED = -3,
    VK_ERROR_DEVICE_LOST = -4,
    VK_ERROR_MEMORY_MAP_FAILED = -5,
    VK_ERROR_LAYER_NOT_PRESENT = -6,
    VK_ERROR_EXTENSION_NOT_PRESENT = -7,
    VK_ERROR_FEATURE_NOT_PRESENT = -8,
    VK_ERROR_INCOMPATIBLE_DRIVER = -9,
    VK_ERROR_TOO_MANY_OBJECTS = -10,
    VK_ERROR_FORMAT_NOT_SUPPORTED = -11,
    VK_ERROR_FRAGMENTED_POOL = -12,
    VK_ERROR_UNKNOWN = -13,
} VkResult;

*/
import "C"
import "unsafe"

// Basic Go types for Vulkan
type (
	Instance       C.VkInstance
	PhysicalDevice C.VkPhysicalDevice
	Device         C.VkDevice
	Queue          C.VkQueue
	CommandBuffer  C.VkCommandBuffer
	
	Flags       C.VkFlags
	DeviceSize  C.VkDeviceSize
	SampleMask  C.VkSampleMask
	Bool32      C.VkBool32
	Float       C.VkFloat
	Double      C.VkDouble
	
	Result      C.VkResult
)

// Result constants
const (
	SUCCESS                        Result = C.VK_SUCCESS
	NOT_READY                      Result = C.VK_NOT_READY
	TIMEOUT                        Result = C.VK_TIMEOUT
	EVENT_SET                      Result = C.VK_EVENT_SET
	EVENT_RESET                    Result = C.VK_EVENT_RESET
	INCOMPLETE                     Result = C.VK_INCOMPLETE
	ERROR_OUT_OF_HOST_MEMORY       Result = C.VK_ERROR_OUT_OF_HOST_MEMORY
	ERROR_OUT_OF_DEVICE_MEMORY     Result = C.VK_ERROR_OUT_OF_DEVICE_MEMORY
	ERROR_INITIALIZATION_FAILED    Result = C.VK_ERROR_INITIALIZATION_FAILED
	ERROR_DEVICE_LOST              Result = C.VK_ERROR_DEVICE_LOST
	ERROR_MEMORY_MAP_FAILED        Result = C.VK_ERROR_MEMORY_MAP_FAILED
	ERROR_LAYER_NOT_PRESENT        Result = C.VK_ERROR_LAYER_NOT_PRESENT
	ERROR_EXTENSION_NOT_PRESENT    Result = C.VK_ERROR_EXTENSION_NOT_PRESENT
	ERROR_FEATURE_NOT_PRESENT      Result = C.VK_ERROR_FEATURE_NOT_PRESENT
	ERROR_INCOMPATIBLE_DRIVER      Result = C.VK_ERROR_INCOMPATIBLE_DRIVER
	ERROR_TOO_MANY_OBJECTS         Result = C.VK_ERROR_TOO_MANY_OBJECTS
	ERROR_FORMAT_NOT_SUPPORTED     Result = C.VK_ERROR_FORMAT_NOT_SUPPORTED
	ERROR_FRAGMENTED_POOL          Result = C.VK_ERROR_FRAGMENTED_POOL
	ERROR_UNKNOWN                  Result = C.VK_ERROR_UNKNOWN
)

// Error implements the error interface for Result
func (r Result) Error() string {
	switch r {
	case SUCCESS:
		return "VK_SUCCESS"
	case NOT_READY:
		return "VK_NOT_READY"
	case TIMEOUT:
		return "VK_TIMEOUT"
	case EVENT_SET:
		return "VK_EVENT_SET"
	case EVENT_RESET:
		return "VK_EVENT_RESET"
	case INCOMPLETE:
		return "VK_INCOMPLETE"
	case ERROR_OUT_OF_HOST_MEMORY:
		return "VK_ERROR_OUT_OF_HOST_MEMORY"
	case ERROR_OUT_OF_DEVICE_MEMORY:
		return "VK_ERROR_OUT_OF_DEVICE_MEMORY"
	case ERROR_INITIALIZATION_FAILED:
		return "VK_ERROR_INITIALIZATION_FAILED"
	case ERROR_DEVICE_LOST:
		return "VK_ERROR_DEVICE_LOST"
	case ERROR_MEMORY_MAP_FAILED:
		return "VK_ERROR_MEMORY_MAP_FAILED"
	case ERROR_LAYER_NOT_PRESENT:
		return "VK_ERROR_LAYER_NOT_PRESENT"
	case ERROR_EXTENSION_NOT_PRESENT:
		return "VK_ERROR_EXTENSION_NOT_PRESENT"
	case ERROR_FEATURE_NOT_PRESENT:
		return "VK_ERROR_FEATURE_NOT_PRESENT"
	case ERROR_INCOMPATIBLE_DRIVER:
		return "VK_ERROR_INCOMPATIBLE_DRIVER"
	case ERROR_TOO_MANY_OBJECTS:
		return "VK_ERROR_TOO_MANY_OBJECTS"
	case ERROR_FORMAT_NOT_SUPPORTED:
		return "VK_ERROR_FORMAT_NOT_SUPPORTED"
	case ERROR_FRAGMENTED_POOL:
		return "VK_ERROR_FRAGMENTED_POOL"
	case ERROR_UNKNOWN:
		return "VK_ERROR_UNKNOWN"
	default:
		return fmt.Sprintf("VkResult(%d)", int(r))
	}
}

// IsError returns true if the result indicates an error
func (r Result) IsError() bool {
	return r < 0
}

// Must panics if the result indicates an error
func (r Result) Must() {
	if r.IsError() {
		panic(r)
	}
}

// String conversion utilities
func GoString(cstr *C.char) string {
	if cstr == nil {
		return ""
	}
	return C.GoString(cstr)
}

func CString(s string) *C.char {
	return C.CString(s)
}

func FreeCString(cstr *C.char) {
	C.free(unsafe.Pointer(cstr))
}

// Slice conversion utilities
func GoStringSlice(cstrs **C.char, count int) []string {
	if cstrs == nil || count == 0 {
		return nil
	}
	
	slice := make([]string, count)
	ptr := uintptr(unsafe.Pointer(cstrs))
	size := unsafe.Sizeof(*cstrs)
	
	for i := 0; i < count; i++ {
		cstr := *(**C.char)(unsafe.Pointer(ptr))
		slice[i] = GoString(cstr)
		ptr += size
	}
	
	return slice
}

func CStringSlice(strs []string) []*C.char {
	if len(strs) == 0 {
		return nil
	}
	
	cstrs := make([]*C.char, len(strs))
	for i, str := range strs {
		cstrs[i] = CString(str)
	}
	
	return cstrs
}

func FreeCStringSlice(cstrs []*C.char) {
	for _, cstr := range cstrs {
		FreeCString(cstr)
	}
}
`
	
	file.WriteString(tmpl)
}

func generateEnums(registry *VulkanRegistry) {
	file, err := os.Create(filepath.Join(outputDir, "enums.go"))
	if err != nil {
		panic(err)
	}
	defer file.Close()
	
	file.WriteString("// Code generated by generate.go. DO NOT EDIT.\n\npackage vulkan\n\n")
	file.WriteString("// Vulkan enumerations\n")
	
	for _, enum := range registry.Enums {
		if len(enum.Values) == 0 {
			continue
		}
		
		// Convert enum name to Go type name
		typeName := strings.TrimPrefix(enum.Name, "Vk")
		
		file.WriteString(fmt.Sprintf("type %s int32\n\n", typeName))
		file.WriteString(fmt.Sprintf("const (\n"))
		
		for i, value := range enum.Values {
			constName := strings.TrimPrefix(value.Name, "VK_")
			constName = strings.Replace(constName, enum.Name[2:], "", 1)
			constName = strings.TrimPrefix(constName, "_")
			
			if i == 0 {
				if value.Value != "" {
					file.WriteString(fmt.Sprintf("\t%s %s = %s\n", constName, typeName, value.Value))
				} else if value.Bitpos != "" {
					file.WriteString(fmt.Sprintf("\t%s %s = 1 << %s\n", constName, typeName, value.Bitpos))
				} else {
					file.WriteString(fmt.Sprintf("\t%s %s = iota\n", constName, typeName))
				}
			} else {
				if value.Value != "" {
					file.WriteString(fmt.Sprintf("\t%s = %s\n", constName, value.Value))
				} else if value.Bitpos != "" {
					file.WriteString(fmt.Sprintf("\t%s = 1 << %s\n", constName, value.Bitpos))
				} else {
					file.WriteString(fmt.Sprintf("\t%s\n", constName))
				}
			}
		}
		
		file.WriteString(")\n\n")
	}
}

func generateCommands(registry *VulkanRegistry) {
	file, err := os.Create(filepath.Join(outputDir, "commands.go"))
	if err != nil {
		panic(err)
	}
	defer file.Close()
	
	file.WriteString("// Code generated by generate.go. DO NOT EDIT.\n\npackage vulkan\n\n")
	file.WriteString("/*\n#include <vulkan/vulkan.h>\n*/\nimport \"C\"\nimport \"unsafe\"\n\n")
	
	// Generate function pointers
	file.WriteString("// Function pointers loaded at runtime\nvar (\n")
	for _, cmd := range registry.Commands {
		funcName := strings.TrimPrefix(cmd.Proto.Name, "vk")
		file.WriteString(fmt.Sprintf("\tpfn%s uintptr\n", funcName))
	}
	file.WriteString(")\n\n")
	
	// Generate function wrappers
	for _, cmd := range registry.Commands {
		generateCommandWrapper(file, cmd)
	}
}

func generateCommandWrapper(file *os.File, cmd Command) {
	funcName := strings.TrimPrefix(cmd.Proto.Name, "vk")
	
	// Parse return type and parameters
	protoContent := strings.TrimSpace(cmd.Proto.Content)
	returnType := "Result"
	if strings.Contains(protoContent, "void") {
		returnType = ""
	}
	
	// Generate Go function signature
	file.WriteString(fmt.Sprintf("func %s(", funcName))
	
	for i, param := range cmd.Params {
		if i > 0 {
			file.WriteString(", ")
		}
		
		paramName := cleanParamName(param.Name)
		paramType := mapCTypeToGo(param.Content)
		
		file.WriteString(fmt.Sprintf("%s %s", paramName, paramType))
	}
	
	file.WriteString(")")
	
	if returnType != "" {
		file.WriteString(fmt.Sprintf(" %s", returnType))
	}
	
	file.WriteString(" {\n")
	
	// Generate function body
	file.WriteString("\t// TODO: Implement function call through loaded pointer\n")
	if returnType != "" {
		file.WriteString("\treturn SUCCESS\n")
	}
	file.WriteString("}\n\n")
}

func generateExtensions(registry *VulkanRegistry) {
	file, err := os.Create(filepath.Join(outputDir, "extensions.go"))
	if err != nil {
		panic(err)
	}
	defer file.Close()
	
	file.WriteString("// Code generated by generate.go. DO NOT EDIT.\n\npackage vulkan\n\n")
	file.WriteString("// Vulkan extension constants\nconst (\n")
	
	for _, ext := range registry.Extensions {
		extName := strings.Replace(ext.Name, "VK_", "", 1)
		file.WriteString(fmt.Sprintf("\t%s = \"%s\"\n", extName, ext.Name))
	}
	
	file.WriteString(")\n\n")
	
	file.WriteString("// Extension support functions\n")
	file.WriteString("func IsExtensionSupported(extensionName string, availableExtensions []string) bool {\n")
	file.WriteString("\tfor _, ext := range availableExtensions {\n")
	file.WriteString("\t\tif ext == extensionName {\n")
	file.WriteString("\t\t\treturn true\n")
	file.WriteString("\t\t}\n")
	file.WriteString("\t}\n")
	file.WriteString("\treturn false\n")
	file.WriteString("}\n")
}

func generateLoader(registry *VulkanRegistry) {
	file, err := os.Create(filepath.Join(outputDir, "loader.go"))
	if err != nil {
		panic(err)
	}
	defer file.Close()
	
	tmpl := `// Code generated by generate.go. DO NOT EDIT.

package vulkan

import (
	"fmt"
	"runtime"
	"syscall"
	"unsafe"
)

var (
	vulkanLib uintptr
	isLoaded  bool
)

// Init initializes the Vulkan loader and loads all function pointers
func Init() error {
	if isLoaded {
		return nil
	}
	
	libName := getVulkanLibName()
	lib, err := syscall.LoadLibrary(libName)
	if err != nil {
		return fmt.Errorf("failed to load Vulkan library %s: %w", libName, err)
	}
	
	vulkanLib = uintptr(lib)
	
	// Load core functions first
	err = loadCoreFunctions()
	if err != nil {
		syscall.FreeLibrary(syscall.Handle(vulkanLib))
		return fmt.Errorf("failed to load core functions: %w", err)
	}
	
	isLoaded = true
	return nil
}

// Destroy cleans up the Vulkan loader
func Destroy() {
	if !isLoaded {
		return
	}
	
	syscall.FreeLibrary(syscall.Handle(vulkanLib))
	vulkanLib = 0
	isLoaded = false
}

func getVulkanLibName() string {
	switch runtime.GOOS {
	case "windows":
		return "vulkan-1.dll"
	case "darwin":
		return "libMoltenVK.dylib" // or libvulkan.1.dylib if using native Vulkan
	default:
		return "libvulkan.so.1"
	}
}

func loadCoreFunctions() error {
	// Load vkGetInstanceProcAddr first - this is the bootstrap function
	addr, err := syscall.GetProcAddress(syscall.Handle(vulkanLib), "vkGetInstanceProcAddr")
	if err != nil {
		return fmt.Errorf("failed to get vkGetInstanceProcAddr: %w", err)
	}
	
	getInstanceProcAddr := addr
	
	// Use vkGetInstanceProcAddr to load other functions
	// This is a simplified version - in reality, you'd load all function pointers here
	
	return nil
}

// GetInstanceProcAddr loads a Vulkan function pointer by name
func GetInstanceProcAddr(instance Instance, name string) uintptr {
	// TODO: Implement actual function loading
	return 0
}

// GetDeviceProcAddr loads a device-specific Vulkan function pointer by name  
func GetDeviceProcAddr(device Device, name string) uintptr {
	// TODO: Implement actual function loading
	return 0
}
`
	
	file.WriteString(tmpl)
}

// Helper functions
func cleanParamName(name string) string {
	// Remove common prefixes and make Go-friendly
	name = strings.TrimSpace(name)
	if name == "type" {
		return "typ" // Go keyword
	}
	if name == "func" {
		return "fn" // Go keyword
	}
	return name
}

func mapCTypeToGo(ctype string) string {
	ctype = strings.TrimSpace(ctype)
	
	// Simple type mapping - would need to be more sophisticated for real use
	switch {
	case strings.Contains(ctype, "VkInstance"):
		return "Instance"
	case strings.Contains(ctype, "VkDevice"):
		return "Device"
	case strings.Contains(ctype, "VkPhysicalDevice"):
		return "PhysicalDevice"
	case strings.Contains(ctype, "uint32_t"):
		return "uint32"
	case strings.Contains(ctype, "uint64_t"):
		return "uint64"
	case strings.Contains(ctype, "int32_t"):
		return "int32"
	case strings.Contains(ctype, "float"):
		return "float32"
	case strings.Contains(ctype, "double"):
		return "float64"
	case strings.Contains(ctype, "char*"):
		return "string"
	default:
		return "unsafe.Pointer"
	}
}