from copy import copy
import inspect
import os
from os.path import join, abspath
import threading
import pythoncom
import sys
from win32com.client import Dispatch

sys.path.append(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()) + "\\..\\..")))

from IFikatia import IFikatia
from IFikomObject import IFikomObject


class AsyncCall(object):
    def __init__(self, fnc, callback=None):
        self.Callable = fnc
        self.Callback = callback

    def __call__(self, *args, **kwargs):
        self.Thread = threading.Thread(target=self.run, name=self.Callable.__name__, args=args, kwargs=kwargs)
        self.Thread.setDaemon(True)
        self.Thread.start()
        return self

    def wait(self, timeout=None):
        self.Thread.join(timeout)
        if self.Thread.isAlive():
            raise TimeoutError()
        else:
            return self.Result

    def run(self, *args, **kwargs):
        pythoncom.CoInitialize()
        argler = []
        arglerAppend=argler.append
        for x in range(len(args)):
            if str(type(args[x])) == "<class 'PyIStream'>":
                arglerAppend(Dispatch(pythoncom.CoGetInterfaceAndReleaseStream(args[x], pythoncom.IID_IDispatch)))
            else:
                arglerAppend(args[x])

        for x in range(len(argler)):
            if type(argler[x]) == list:
                for y in range(len(argler[x])):
                    if str(type(argler[x][y])) == "<class 'PyIStream'>":
                        argler[x][y]=Dispatch(pythoncom.CoGetInterfaceAndReleaseStream(args[x][y], pythoncom.IID_IDispatch))

        self.Result = self.Callable(*argler, **kwargs)
        if self.Callback:
            self.Callback(self.Result)


class AsyncMethod(object):
    def __init__(self, fnc, callback=None):
        self.Callable = fnc
        self.Callback = callback

    def __call__(self, *args, **kwargs):
        argler = []
        for x in range(len(args)):
            if str(type(args[x])) == "<class 'win32com.client.CDispatch'>":
                argler.append(pythoncom.CoMarshalInterThreadInterfaceInStream(pythoncom.IID_IDispatch, args[x]))
            elif isinstance(args[x], IFikomObject):
                argler.append(
                    pythoncom.CoMarshalInterThreadInterfaceInStream(pythoncom.IID_IDispatch, args[x].catNesne))
            else:
                argler.append(args[x])

        for x in range(len(argler)):
            if type(argler[x]) == list:
                for y in range(len(argler[x])):
                    if str(type(args[x][y])) == "<class 'win32com.client.CDispatch'>":
                        args[x][y]=pythoncom.CoMarshalInterThreadInterfaceInStream(pythoncom.IID_IDispatch, args[x][y])
                    elif isinstance(args[x][y], IFikomObject):
                        args[x][y]=pythoncom.CoMarshalInterThreadInterfaceInStream(pythoncom.IID_IDispatch, args[x][y].catNesne)

        return AsyncCall(self.Callable, self.Callback)(*argler, **kwargs)


def Async(fnc=None, callback=None):
    if fnc == None:
        def AddAsyncCallback(fnc):
            return AsyncMethod(fnc, callback)

        return AddAsyncCallback
    else:
        return AsyncMethod(fnc, callback)
