import bpy
import os
import sys
import platform
from ctypes import *

CCharPtr8 = c_char_p * 8


class UpdateCureve(Structure):
    _fields_ = [
        ("curve_mapping", c_void_p),
        ("pybuf", c_void_p),
        ("curve_cur", c_int),
        ("curve_mt", c_int),
        ("r_current_curve", c_int),
        ("r_timestamp", c_int),
        ("r_updated", c_bool)
    ]


class ShaderAttr(Structure):
    _fields_ = [
        ("binded", c_bool),
        ("loc", c_int),
        ("name", c_char_p),
    ]


class VAOBindingAttr(Structure):
    _fields_ = [
        ("vao_id", c_int),
        ("vbo_id", c_int),
        ("attr_len", c_int),
        ("ibo_id", c_int),
        ("ibo_len", c_int),
        ("vbuf", c_void_p),
        ("ibuf", c_void_p),
        ("ibo_binded", c_bool),
    ]


def init_module():
    lib_name = "gl_utils"
    version = bpy.app.version
    if version[:2] == (3, 2):
        lib_name += "_320_py310"
    elif version[:2] == (3, 1):
        lib_name += "_310_py310"
    elif version[:2] == (3, 0):
        lib_name += "_300_py39"
    elif version[:2] == (2, 93):
        if bpy.app.version[2] < 4 or (bpy.app.version[2] == 4 and bpy.app.version_cycle != 'release'):
            lib_name += "_293_py39"
        else:
            lib_name += "_2934_py39"

    dirname = os.path.dirname(__file__)

    if sys.platform.startswith("linux"):
        ext = ".so"
    else:
        assert sys.platform.startswith("darwin")
        ext = ".dylib"
        if platform.machine() == 'arm64':
            dirname += "/darwin_arm64"

    dylib = cdll.LoadLibrary(f'{dirname}/{lib_name}{ext}')
    if not dylib.init_gl_utils():
        return
    dylib.set_mesh_surface_gpu_batch.argtypes = (c_void_p, c_void_p)
    dylib.set_mesh_surface_gpu_batch.restype = c_bool

    dylib.set_mesh_surface_material_gpu_batch.argtypes = (c_void_p, c_void_p, c_int)
    dylib.set_mesh_surface_material_gpu_batch.restype = c_bool

    dylib.clearup_mesh_surface_gpu_batch.argtypes = (c_void_p,)

    dylib.set_edit_hair_gpu_batch.argtypes = (c_void_p, c_void_p)
    dylib.set_edit_hair_gpu_batch.restype = c_void_p

    dylib.get_hair_cache_info.argtypes = (c_void_p, c_bool, c_void_p, c_void_p)
    dylib.get_hair_cache_info.restype = c_int

    dylib.fill_hair_position_buffer.argtypes = (c_void_p, c_void_p)

    dylib.fill_hair_strand_data.argtypes = (c_void_p, c_void_p, c_void_p)

    dylib.fill_hair_segments_indices.argtypes = (c_void_p, c_void_p, c_int, c_int)
    dylib.fill_hair_segments_indices.restype = c_uint32

    dylib.fill_hair_strand_color_buf.argtypes = (c_void_p, c_void_p, c_void_p)
    dylib.fill_hair_strand_color_buf.restype = c_int

    dylib.fill_hair_frand_buffer.argtypes = (c_void_p,)

    dylib.hair_cache_is_dirty.argtypes = (c_void_p,)
    dylib.hair_cache_is_dirty.restype = c_bool

    dylib.update_curve_mapping_table.argtypes = (c_void_p,)

    dylib.transform_feedback_names_set.argtypes = (c_int, c_void_p, c_int)

    dylib.begin_transform_feedback.argtypes = (c_void_p,)

    dylib.get_gpu_batch.argtypes = (c_void_p,)
    dylib.get_gpu_batch.restype = c_void_p

    dylib.restore_gpu_batch.argtypes = (c_void_p, c_void_p)

    dylib.bind_vert_buffer.argtypes = (c_void_p, c_int)
    dylib.bind_vert_buffer.restype = c_bool

    dylib.set_vertex_array_binding.argtypes = (c_void_p, c_void_p)
    dylib.set_vertex_array_binding.restype = c_bool

    dylib.bind_texbuf_to_vertbuf.argtypes = (c_int, c_int, c_void_p)
    dylib.bind_texbuf_to_vertbuf.restype = c_bool

    dylib.bind_uniform_buffer.argtypes = (c_void_p, c_int)

    dylib.get_texture_id.argtypes = (c_void_p,)
    dylib.get_texture_id.restype = c_int

    dylib.batch_draw_instanced.argtypes = (c_int, c_int, c_int, c_int)

    dylib.pack_material_info_to_buffer.argtypes = (c_void_p, c_int, c_float,
                                                   c_float, c_float, c_float,
                                                   c_float, c_float)

    return dylib


py_clib = init_module()


def set_mesh_surface_gpu_batch(gpu_batch, mesh):
    return py_clib.set_mesh_surface_gpu_batch(id(gpu_batch), mesh)


def set_mesh_surface_material_gpu_batch(gpu_batch, mesh, idx):
    return py_clib.set_mesh_surface_material_gpu_batch(id(gpu_batch), mesh, idx)


def clearup_mesh_surface_gpu_batch(mesh):
    return py_clib.clearup_mesh_surface_gpu_batch(mesh)


def set_edit_hair_gpu_batch(psys, gpu_batch):
    return py_clib.set_edit_hair_gpu_batch(psys, id(gpu_batch))


def get_hair_cache_info(psys, ask_edit):
    r_point_len = c_int(0)
    r_elems_len = c_int(0)

    strands_len = py_clib.get_hair_cache_info(psys, ask_edit, byref(r_point_len), byref(r_elems_len))
    return r_point_len.value, r_elems_len.value, strands_len


def fill_hair_position_buffer(pypsys, pyvbuf):
    return py_clib.fill_hair_position_buffer(pypsys, id(pyvbuf))


def fill_hair_strand_data(psys, strands_buf, strand_seg_buf):
    return py_clib.fill_hair_strand_data(psys, id(strands_buf), id(strand_seg_buf))


def fill_hair_segments_indices(pypsys, pyibuf, verts_per_hair, elem_count):
    return py_clib.fill_hair_segments_indices(pypsys, id(pyibuf), verts_per_hair, elem_count)


def fill_hair_strand_color_buf(psys, strands_color_buf, color_groups_buffer):
    return py_clib.fill_hair_strand_color_buf(psys, id(strands_color_buf), id(color_groups_buffer))


def fill_hair_frand_buffer(pyvbuf):
    return py_clib.fill_hair_frand_buffer(id(pyvbuf))


def hair_cache_is_dirty(args):
    return py_clib.hair_cache_is_dirty(args)


def update_curve_mapping_table(curve_mapping, pybuf, curve_cur, curve_mt):
    args = UpdateCureve()
    args.curve_mapping = curve_mapping
    args.pybuf = id(pybuf)
    args.curve_cur = curve_cur
    args.curve_mt = curve_mt

    py_clib.update_curve_mapping_table(byref(args))
    return args.r_updated, args.r_current_curve, args.r_timestamp


def transform_feedback_names_set(program, names):
    count = len(names)
    if count > 8:
        return
    char_pp = CCharPtr8()
    for i in range(count):
        char_pp[i] = c_char_p(bytes(names[i], encoding="utf8"))

    return py_clib.transform_feedback_names_set(program, byref(char_pp), count)


def begin_transform_feedback(arg):
    return py_clib.begin_transform_feedback(arg)


def end_transform_feedback():
    return py_clib.end_transform_feedback()


def get_gpu_batch(arg):
    return py_clib.get_gpu_batch(id(arg))


def restore_gpu_batch(pygpu_batch, gpu_batch_ptr):
    return py_clib.restore_gpu_batch(id(pygpu_batch), gpu_batch_ptr)


def bind_vert_buffer(pyvbuf, vbo_id):
    return py_clib.bind_vert_buffer(id(pyvbuf), vbo_id)


def set_vertex_array_binding(vao_id, pyvbuf, pyibuf, attr_dict):
    vbo_id = pyvbuf.get_vbo_id()
    if vao_id == 0 or vbo_id == 0:
        return

    vbuf = pyvbuf.get_vert_buffer()
    if not vbuf:
        return

    args = VAOBindingAttr()
    args.vao_id = vao_id
    args.vbo_id = vbo_id
    args.vbuf = id(vbuf)

    if pyibuf:
        ibo_id = pyibuf.get_ibo_id()
        if ibo_id != 0:
            args.ibo_id = ibo_id
            args.ibo_len = pyibuf.len()
            ibuf = pyibuf.get_index_buffer()
            if ibuf:
                args.ibuf = id(ibuf)
                args.ibo_binded = pyibuf.get_binded()

    length = len(attr_dict)
    attrs = (ShaderAttr * length)()
    i = 0
    for k, v in attr_dict.items():
        attrs[i].name = c_char_p(bytes(k, encoding="utf8"))
        attrs[i].loc = v
        i += 1

    args.attr_len = length
    bind_ibo = py_clib.set_vertex_array_binding(byref(args), byref(attrs))

    if bind_ibo:
        pyibuf.set_binded()


def bind_texbuf_to_vertbuf(tex_id, vbo_id, pyvbuf):
    return py_clib.bind_texbuf_to_vertbuf(tex_id, vbo_id, id(pyvbuf))


def bind_uniform_buffer(pyubo, slot):
    return py_clib.bind_uniform_buffer(id(pyubo), slot)


def get_texture_id(arg):
    return py_clib.get_texture_id(id(arg))


def set_primitive_restart_index():
    return py_clib.set_primitive_restart_index()


def batch_draw_instanced(prim_type, v_count, i_count, has_elem):
    return py_clib.batch_draw_instanced(prim_type, v_count, i_count, has_elem)


def pack_material_info_to_buffer(buf, offset, r, g, b, a, roughness, metallic):
    return py_clib.pack_material_info_to_buffer(id(buf), offset, r, g, b, a, roughness, metallic)
