import struct
import ctypes
import functools
from ctypes.wintypes import HRESULT

# Simple Abstraction to call COM interface in Python (Python -> COM)
IID_PACK = "<I", "<H", "<H", "<B", "<B", "<B", "<B", "<B", "<B", "<B", "<B"


def get_IID_from_raw(raw):
    return "".join([struct.pack(i, j) for i, j in zip(IID_PACK, raw)])


class COMInterface(ctypes.c_void_p):
    _functions_ = {
        "QueryInterface": ctypes.WINFUNCTYPE(HRESULT, ctypes.c_void_p, ctypes.c_void_p)(0, "QueryInterface"),
        "AddRef": ctypes.WINFUNCTYPE(HRESULT)(1, "AddRef"),
        "Release": ctypes.WINFUNCTYPE(HRESULT)(2, "Release")
    }

    def __getattr__(self, name):
        if name in self._functions_:
            return functools.partial(self._functions_[name], self)
        return super(COMInterface, self).__getattribute__(name)


# Simple Implem to create COM Interface in Python (COM -> Python)
def BasicQueryInterface(self, *args):
    return 1


def BasicAddRef(self, *args):
    return 1


def BasicRelease(self, *args):
    return 0


def create_c_callable(func, types, keepalive=[]):
    func_type = ctypes.WINFUNCTYPE(*types)
    c_callable = func_type(func)
    # Dirty, but other methods require native code execution
    c_callback_addr = ctypes.c_ulong.from_address(id(c_callable._objects['0']) + 3 * ctypes.sizeof(ctypes.c_void_p)).value
    keepalive.append(c_callable)
    return c_callback_addr


class ComVtable(object):
    # Name, types, DefaultImplem
    _funcs_ = [("QueryInterface", [ctypes.HRESULT, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p], BasicQueryInterface),
               ("AddRef", [ctypes.HRESULT, ctypes.c_void_p], BasicAddRef),
               ("Release", [ctypes.HRESULT, ctypes.c_void_p], BasicRelease)
               ]

    def __init__(self):
        raise NotImplementedError("Nop: use create_vtable")

    @classmethod
    def create_vtable(cls, **implem_overwrite):
        vtables_names = [x[0] for x in cls._funcs_]
        non_expected_args = [func_name for func_name in implem_overwrite if func_name not in vtables_names]
        if non_expected_args:
            raise ValueError("Non expected function : {0}".format(non_expected_args))

        implems = []
        for name, types, func_implem in cls._funcs_:
            func_implem = implem_overwrite.get(name, func_implem)
            if func_implem is None:
                raise ValueError("Missing implementation for function <{0}>".format(name))
            implems.append(create_c_callable(func_implem, types))

        class Vtable(ctypes.Structure):
            _fields_ = [(name, ctypes.c_void_p) for name in vtables_names]

        return Vtable(*implems)


class IDebugOutputCallbacksVtable(ComVtable):
    _funcs_ = ComVtable._funcs_ + [("Output", [ctypes.HRESULT, ctypes.c_void_p, ctypes.c_ulong, ctypes.c_char_p], None)]