import os

import sys
from pprint import pprint
import math

import subprocess

# yeah I ain't gonna sugar coat it,
# parsing C files is really awful. 
# goal with these files is to use em to generate usable APIs only for the stuff that's going to be widely exposed througout 
# the engine. 

# which.. in this case... the only there is- is the gpu API.

def convertCase(x):
    s = ''
    underbar = False
    for y in x:
        if y == '_':
            underbar = True
            continue
        else:
            s += y.lower() if not underbar else y

        if underbar:
            underbar = False
    return s


origDir = os.path.abspath(os.path.dirname(__file__))
os.chdir(origDir)

infile = "../../SDL/include/SDL3/SDL_gpu.h"
ofile = os.path.abspath(os.path.join(origDir, '../gpu.zig'))

SDL_files = [
    'SDL_gpu.h',
]


typeMap = {
    'Uint32': 'u32',
    'float': 'f32',
    'Sint32': 'i32',
    'bool': 'bool',
    'Uint8': 'u8',
    'SDL_PropertiesID': 'PropertiesID',
    'char': 'u8',
    'void': 'void',
    'int': 'c_int',
    'size_t': 'usize',
    'SDL_Window': 'Window'
}

typeMap_r = {}

def generateAll(infile, ofile):

    mode_default = 0
    mode_collecting = 1

    typedefs_pod = []
    typedefs_opaque = []
    typedefs_enum = []
    typedefs_struct = []
    typedefs_unknown = []

    functions = []

    defines = []

    lastList = None

    with open(infile) as rf:
        mode = mode_default
        braceCount = 0
        f = rf.readlines()

        x = [ y + '\n' for y in """
typedef struct SDL_FColor {

    float r;
    float g;
    float b;
    float a;
} SDL_FColor;

typedef struct SDL_Rect
{
    int x;
    int y;
    int w;
    int h;
} SDL_Rect;

typedef enum SDL_FlipMode {

    SDL_FLIP_NONE,          /**< Do not flip */
    SDL_FLIP_HORIZONTAL,    /**< flip horizontally */
    SDL_FLIP_VERTICAL       /**< flip vertically */
} SDL_FlipMode;
        """.split('\n')]

        f += x

        for l in f:
            dropped = True
            line = l.strip()
            if mode == mode_default:
                if line.startswith('typedef'):
                    if ";" in line and 'struct' in line:
                        typedefs_opaque.append(line)
                        dropped = False
                    elif ';' in line:
                        typedefs_pod.append(line)
                        dropped = False
                    elif 'enum' in line:
                        lastList = typedefs_enum
                        typedefs_enum.append(line)
                        dropped = False
                    elif 'struct' in line:
                        lastList = typedefs_struct
                        typedefs_struct.append(line)
                        dropped = False
                    else:
                        lastList = typedefs_unknown
                        dropped = False
                        typedefs_unknown.append(line)

                elif line.startswith('extern SDL_DECLSPEC'):
                    lastList = functions
                    dropped = False
                    functions.append(line)

                elif line.startswith('#define '):
                    lastList = defines
                    defines.append(line)

                if ';' not in line and not dropped:
                    mode = mode_collecting
                    if "{" in l:
                        braceCount = 1

            elif mode == mode_collecting:
                lastList[-1] += l.replace('\n', '\n')

                if "{" in line:
                    braceCount += 1

                if "}" in line:
                    braceCount -= 1

                if ';' in line and braceCount == 0:
                    mode = mode_default

    print_types = False
    print_functions = False
    print_defines= False

    def plos(l):
        for s in l:
            print('> ', s, " < ")

    print('struct')
# plos(typedefs_struct)
    print('pod')
    plos(typedefs_pod)
    if print_types:
        print('opaque')
        plos(typedefs_opaque)
        print('pod')
        plos(typedefs_pod)
        print('enum')
        plos(typedefs_enum)
        print('unknown')
        plos(typedefs_unknown)

    if print_functions:
        print('functions')
        plos(functions)

    if print_defines:
        print('defines')
        plos(defines)

    print(f"\nfunctions: {len(functions)}, structs:{len(typedefs_struct)}, enums:{len(typedefs_enum)}\n")

    ostring = 'pub const c = @import("c.zig").c;\n\npub const PropertiesID = u32;\n pub const Window = c.SDL_Window;'
# time to generate types...
# first the opaques

    ostring += '\n'

    opaques = {'Window': ''}
    enums = {}

    opaqueOrder = []

    for t in typedefs_pod:
        c_typename = t.strip().strip(';').split(' ')[-1]
        typename = c_typename

        if typename.startswith("SDL_"):
            typename = typename[4:]

        typeMap[c_typename] = typename
        typeMap_r[typename] =  c_typename
        # todo generate packed flags for each one

    for t in typedefs_opaque:
        c_typename = t.strip().strip(';').split(' ')[3]
        typename = c_typename

        if typename.startswith("SDL_"):
            typename = typename[4:]

        typeMap[c_typename] = typename
        typeMap_r[typename] =  c_typename

        brackets = "{}"
        definition = f"pub const {typename} = opaque {{\n"
        # ostring += definition
        opaques[typename] = definition
        opaqueOrder.append(typename)


    ostring += '\n'

    packedFlags = [
        ('SDL_GPUTextureUsageFlags', 'Uint32', [
            ('SDL_GPU_TEXTUREUSAGE_SAMPLER',                                 1 << 0),
            ('SDL_GPU_TEXTUREUSAGE_COLOR_TARGET',                            1 << 1),
            ('SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET',                    1 << 2),
            ('SDL_GPU_TEXTUREUSAGE_GRAPHICS_STORAGE_READ',                   1 << 3),
            ('SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_READ',                    1 << 4),
            ('SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_WRITE',                   1 << 5),
            ('SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_SIMULTANEOUS_READ_WRITE', 1 << 6),
            ('rsvd', 1 << 31)
        ]),
        ('SDL_GPUBufferUsageFlags', 'Uint32', [
            ('SDL_GPU_BUFFERUSAGE_VERTEX', (1 << 0)),
            ('SDL_GPU_BUFFERUSAGE_INDEX', (1 << 1)),
            ('SDL_GPU_BUFFERUSAGE_INDIRECT', (1 << 2)),
            ('SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ', (1 << 3)),
            ('SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_READ', (1 << 4)),
            ('SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_WRITE', (1 << 5)),
            ('rsvd', 1 << 31)
        ]),
        ('SDL_GPUColorComponentFlags', 'Uint8', [
            ('SDL_GPU_COLORCOMPONENT_R', (1 << 0)),
            ('SDL_GPU_COLORCOMPONENT_G', (1 << 1)),
            ('SDL_GPU_COLORCOMPONENT_B', (1 << 2)),
            ('SDL_GPU_COLORCOMPONENT_A', (1 << 3)),
            ('rsvd', 1 << 7)
        ]),
        ('SDL_GPUShaderFormat', 'Uint32', [
            ('SDL_GPU_SHADERFORMAT_PRIVATE', (1 << 0)),
            ('SDL_GPU_SHADERFORMAT_SPIRV', (1 << 1)),
            ('SDL_GPU_SHADERFORMAT_DXBC', (1 << 2)),
            ('SDL_GPU_SHADERFORMAT_DXIL', (1 << 3)),
            ('SDL_GPU_SHADERFORMAT_MSL', (1 << 4)),
            ('SDL_GPU_SHADERFORMAT_METALLIB', (1 << 5)),
            ('rsvd', 1 << 31)
        ], [('Invalid', '.{}')]),
    ]

    def makePackedFlags(typename, backingtype, flagList, constants=None):
        global typeMap
        global typeMap_r

        c_typename = typename
        if typename.startswith("SDL_"):
            typename = typename[4:]

        typeMap[c_typename] = typename
        typeMap_r[typename] =  c_typename

        rv = 'pub const ' + typename + ' = packed struct(' + typeMap[backingtype] + ') {\n'

        offset = 0
        padcount = 0
        for flag in flagList:
            name = flag[0]
            targetOffset = math.log2(flag[1])

            if 'SDL_GPU_' in name:
                name = name[8:]
            name = convertCase(name)

            if targetOffset != offset:
                diff = int(targetOffset - offset)
                rv += '    ' + 'pad' + str(padcount) + ': u' + str(diff) + ' = 0,\n'
                padcount += 1
                offset = targetOffset

            rv += '    ' + name + ': bool = false,\n'
            offset += 1

        if constants is not None:
            for constant in constants:
                rv += f'    pub const {constant[0]}: @This() = {constant[1]};\n'

        rv += '};\n'

        return rv

    ostring += '\n'

    for p in packedFlags:
        ostring += makePackedFlags(*p)

    for t in typedefs_enum:
        typename = t.split("}")[1].strip('\n ;')
        #print("E", typename, t)
        c_typename = typename
        if typename.startswith("SDL_"):
            typename = typename[4:]

        typeMap[c_typename] = typename
        typeMap_r[typename] = c_typename
        enums[typename] = ""

        # print(t)

        inners = t.strip().split('\n')[1:-1]
        e = []

        for i in range(0, len(inners)):
            line = inners[i]
            if ',' in line or line == inners[-1]:
                e.append(line.strip())


        for i in range(0, len(e)):
            e[i] = e[i].replace('/*', '//')
            e[i] = e[i].replace('SDL_GPU_', '')

            if ',' not in e[i]:
                count = 0
                for x in e[i]:
                    if x == ' ':
                        break
                    count += 1

                e[i] = e[i][:count] + ',' + e[i][count:]

            r = e[i].split(',')

            r[0] = convertCase(r[0])

            e[i] = r[0] + ',' + r[1]
        common = os.path.commonprefix(e)
        #print(common)
        #plos(e)

        ostring += 'pub const ' + typename + ' = ' + 'enum(c_int) {\n'
        for i in e:
            ostring += '    ' + i + '\n'
        ostring += '};\n\n'


    for t in typedefs_struct:
        typename = t.split("}")[1].strip('\n ;')
        c_typename = typename
        if typename.startswith("SDL_"):
            typename = typename[4:]

        typeMap[c_typename] = typename
        typeMap_r[typename] = c_typename

    for t in typedefs_struct:

        typename = t.split("}")[1].strip('\n ;')
        c_typename = typename
        if typename.startswith("SDL_"):
            typename = typename[4:]

        typeMap[c_typename] = typename
        typeMap_r[typename] = c_typename

        inner = t.strip("\n ").split('\n')[1:-1]

        # print("----", typename)
        for i in range(0, len(inner)):
            inner[i] = inner[i].replace('/*', "//")
            inner[i] = inner[i].replace('*<', "")
            inner[i] = inner[i].replace('*/', "")

        fields = []

        for txt in inner:
            s = txt.split(';')
            decl = s[0].strip()
            comment = ""
            if len(s) > 1:
                comment = s[1].strip()

            fieldType = " ".join(decl.split(' ')[:-1])
            name = decl.split(' ')[-1]
            const = False
            pointer = False

            if fieldType.startswith('const'):
                fieldType = fieldType[5:].strip()
                const = True

            if name.startswith('*'):
                pointer = True;
                name = name.strip('*')


            if fieldType != '' and name != '':
                fields.append((fieldType, name, const, comment, pointer))

            # not implemented
            if '[' in name:
                assert False

            if '[' in fieldType:
                assert False

        tstr = "pub const " + typename + " = extern struct {\n"

        for field in fields:
            typestring = typeMap[field[0]]
            ptr = '[*c]'
            if typestring in opaques:
                ptr = '*'
            tstr += '    ' + field[1] + ': ' + (ptr if field[4] else '') + ('const ' if field[2] else '') + typestring + ', ' + field[3] + ' \n'

        tstr += '};\n'

        ostring += tstr + "\n"

    for t in functions:
        funcname = None

        s = t[len("extern SDL_DECLSPEC "):].split('(')
        label = s[0]
        args = s[1].replace('\n', '')


        def findTypeFromLabel(label):
            pointerCount = label.count('*')

            label = label.split(' ')

            typeStr = None
            isConst = False


            if label[0] == 'const':
                isConst = True

            if isConst:
                typeStr = 'const ' + typeMap[label[1]]
            else:
                typeStr = typeMap[label[0]]

            for i in range(0, pointerCount):
                if typeStr.split(' ')[-1] in opaques:
                    typeStr = '*' + typeStr
                else:
                    typeStr = '[*c]' + typeStr

            if typeStr == '*void':
                typeStr = '*anyopaque'

            if typeStr == '*const u8':
                return '[*c]const u8'

            return typeStr

        def getArgsListFromArgs(t, args):
            rv = []

            if args.strip(' );') == 'void':
                return rv

            l = args.replace(')', '')
            l = l.replace(';', '')
            l = l.split(',')

            l2 = []
            for x in l:
                l2.append(x.strip())

            print(l2)
            for i in l2:
                x = findTypeFromLabel(i)
                print("type: ", i, '>', x, '<')

                y = i.split(' ')[-1].replace('*', '')

                if y == 'type':
                    y = '_type'

                v = (x, y)

                rv.append(v)
                print(v)

            return rv

        def findFuncFromLabel(label):
            rv = ''

            rv = label.split(' ')[-1]

            if rv.startswith('SDL_'):
                rv = rv[4:]

            if rv.startswith('GPU'):
                rv = 'gpu' + rv[3:]

            if rv.startswith('GDK'):
                rv = 'gdk' + rv[3:]

            rv = rv[0].lower() + rv[1:]

            return rv

        argsList = getArgsListFromArgs(t, args)
        rtype = findTypeFromLabel(label)
        funcLabel = findFuncFromLabel(label)
        funcargs = ''
        first = True
        body = ''
        for arg in argsList:
            if first:
                first = False
            else:
                funcargs += ', '
            funcargs += arg[1] + ': ' + arg[0]
            # body += '    _ = ' + arg[1] + ';\n'

        cfunc = label.split(' ')[-1].strip()
        funcstring = '// ' + label.split(' ')[-1] + '\n'
        funcstring += 'pub inline fn ' + funcLabel + '('+ funcargs + ') ' + rtype + ' {\n'
        bitCastRtype = False
        ptrCastRtype = False
        enumCastRtype = False

        if rtype is not None and rtype != 'void':
            funcstring += '   return '
            if rtype == '[*c]const u8' or rtype.strip('*') in opaques:
                ptrCastRtype = True
            elif rtype in enums:
                enumCastRtype = True
            elif rtype != 'bool' and rtype != 'void':
                print('rtype === ', rtype)
                bitCastRtype = True
        else:
            funcstring += '   '

        if ptrCastRtype:
            funcstring += '@ptrCast('

        if bitCastRtype:
            funcstring += '@bitCast('

        if enumCastRtype:
            funcstring += '@enumFromInt('

        funcstring += 'c.' + cfunc + '('
        first = True

        selfArg = None
        for arg in argsList:
            argType = arg[0]
            argName = arg[1]
            if first:
                first = False
                if argType.strip('*') in opaques:
                    selfArg = argType.strip('*')
            else:
                funcstring += ', '

            if argType == 'bool' or argType == '[*c]const u8':
                funcstring += argName
            elif '*' in argType:
                funcstring += '@ptrCast(' + argName + ')'
            else:
                funcstring += '@bitCast(' + argName + ')'

        if bitCastRtype or ptrCastRtype or enumCastRtype:
            funcstring += ')'

        funcstring += ');\n'

        funcstring += body
        funcstring += '}\n\n'

        if selfArg is not None:
            opaques[selfArg] += funcstring
        else:
            ostring += funcstring

    for opaque in opaques:
        opaques[opaque] += '};\n\n'

    for opaque in opaqueOrder:
        ostring += opaques[opaque]

    with open(ofile, 'w') as f:
        f.write(ostring)

    subprocess.run(['zig', 'fmt', ofile])


generateAll(infile, ofile)
