#!/usr/bin/env python3

import sys
import os
import clang.cindex
from clang.cindex import CursorKind, TypeKind, Type


def map_type(clang_type: Type):
    canonical = clang_type.get_canonical()
    kind = canonical.kind
    spelling = (
        canonical.spelling.replace("const ", "")
        .replace("volatile ", "")
        .replace("restrict ", "")
        .replace("struct ", "")
        .replace("union ", "")
    )

    if kind == TypeKind.POINTER:
        pointee = canonical.get_pointee()
        if pointee.kind == TypeKind.RECORD:
            decl = pointee.get_declaration()
            if not decl.is_definition():
                return "^void"

        if pointee.kind == TypeKind.CHAR_S or pointee.kind == TypeKind.CHAR_U:
            return "cstring"

        if pointee.kind == TypeKind.FUNCTIONPROTO:
            arg_types = []

            for arg in pointee.get_canonical().argument_types():
                arg_types.append(map_type(arg))

            mapped_return = map_type(pointee.get_canonical().get_result())
            args_str = ", ".join(arg_types)

            return f"func({args_str}): {mapped_return}"

        return f"^{map_type(pointee)}"

    if kind == TypeKind.CONSTANTARRAY:
        element_type = canonical.get_array_element_type()
        size = canonical.get_array_size()
        return f"[{size}]{map_type(element_type)}"

    if kind == TypeKind.INCOMPLETEARRAY:
        element_type = canonical.get_array_element_type()
        return f"[?]{map_type(element_type)}"

    if kind == TypeKind.FUNCTIONPROTO or kind == TypeKind.FUNCTIONNOPROTO:
        arg_types = []

        for arg in canonical.argument_types():
            arg_types.append(map_type(arg))

        mapped_return = map_type(canonical.get_result())
        args_str = ", ".join(arg_types)

        return f"func({args_str}): {mapped_return}"

    if kind == TypeKind.VOID:
        return "void"

    if kind == TypeKind.BOOL:
        return "bool"

    if kind in [TypeKind.CHAR_S, TypeKind.SCHAR]:
        return "i8"
    if kind == TypeKind.CHAR_U or kind == TypeKind.UCHAR:
        return "u8"
    if kind == TypeKind.SHORT:
        return "i16"
    if kind == TypeKind.USHORT:
        return "u16"
    if kind == TypeKind.INT:
        return "i32"
    if kind == TypeKind.UINT:
        return "u32"
    if kind in [TypeKind.LONG, TypeKind.LONGLONG]:
        return "i64"
    if kind in [TypeKind.ULONG, TypeKind.ULONGLONG]:
        return "u64"

    if kind == TypeKind.FLOAT:
        return "f32"
    if kind == TypeKind.DOUBLE or kind == TypeKind.LONGDOUBLE:
        return "f64"

    if kind == TypeKind.RECORD:
        return spelling

    raise Exception(f"Unresolved type: {spelling}")


if len(sys.argv) != 2:
    print("Usage: python3 generate.py [path to header]", file=sys.stderr)
    sys.exit(1)

filename = sys.argv[1]

index = clang.cindex.Index.create()

tu = index.parse(filename, ["-x", "c", "-std=c23", "-I/usr/include"])

if tu.diagnostics:
    for diag in tu.diagnostics:
        if diag.severity >= clang.cindex.Diagnostic.Error:
            print(f"Error: {diag.spelling}", file=sys.stderr)

print(f'module "{os.path.basename(filename).split(".")[0]}"')
print()

seen_structs = []

for cursor in tu.cursor.walk_preorder():
    if cursor.location.file and cursor.location.file.name != filename:
        continue

    if cursor.kind == CursorKind.FUNCTION_DECL:
        name = cursor.spelling
        return_type = map_type(cursor.result_type)

        params = []
        for arg in cursor.get_arguments():
            param_name = arg.spelling
            param_type = map_type(arg.type)
            params.append(f"{param_name}: {param_type}")

        params_str = ", ".join(params)

        print(f'export extern "{name}" func {name}({params_str}): {return_type}')

    elif cursor.kind == CursorKind.STRUCT_DECL:
        if cursor.get_usr() in seen_structs:
            continue

        seen_structs.append(cursor.get_usr())

        if cursor.is_definition():
            name = cursor.spelling
            print(f"export struct {name}")
            print("{")
            for field in cursor.get_children():
                if field.kind == CursorKind.FIELD_DECL:
                    field_name = field.spelling
                    field_type = map_type(field.type)
                    print(f"    {field_name}: {field_type}")
                else:
                    raise Exception(
                        f"Unsupported child of struct: {field.spelling}: {field.kind}"
                    )
            print("}")

    elif cursor.kind == CursorKind.ENUM_DECL:
        name = cursor.spelling
        print(f"export enum {name} : u32")
        print("{")
        for field in cursor.get_children():
            if field.kind == CursorKind.ENUM_CONSTANT_DECL:
                field_name = field.spelling
                field_value = field.enum_value
                print(f"    {field_name} = {field_value}")
            else:
                raise Exception(
                    f"Unsupported child of enum: {field.spelling}: {field.kind}"
                )
        print("}")
