"""Utilities for makegw - Parse a header file to build an interface

 This module contains the core code for parsing a header file describing a
 COM interface, and building it into an "Interface" structure.

 Each Interface has methods, and each method has arguments.

 Each argument knows how to use Py_BuildValue or Py_ParseTuple to
 exchange itself with Python.

 See the @win32com.makegw@ module for information in building a COM
 interface
"""
import re
import traceback


class error_not_found(Exception):

    def __init__(self, msg="The requested item could not be found"):
        super(error_not_found, self).__init__(msg)


class error_not_supported(Exception):

    def __init__(self, msg="The required functionality is not supported"):
        super(error_not_supported, self).__init__(msg)

VERBOSE = 0
DEBUG = 0

# NOTE : For interfaces as params to work correctly, you must
# make sure any PythonCOM extensions which expose the interface are loaded
# before generating.


class ArgFormatter:
    """An instance for a specific type of argument.	 Knows how to convert itself"""

    def __init__(self, arg, builtinIndirection, declaredIndirection=0):
        # print 'init:', arg.name, builtinIndirection, declaredIndirection,
        # arg.indirectionLevel
        self.arg = arg
        self.builtinIndirection = builtinIndirection
        self.declaredIndirection = declaredIndirection
        self.gatewayMode = 0

    def _IndirectPrefix(self, indirectionFrom, indirectionTo):
        """Given the indirection level I was declared at (0=Normal, 1=*, 2=**)
        return a string prefix so I can pass to a function with the
        required indirection (where the default is the indirection of the method's param.

        eg, assuming my arg has indirection level of 2, if this function was passed 1
        it would return "&", so that a variable declared with indirection of 1
        can be prefixed with this to turn it into the indirection level required of 2
        """
        dif = indirectionFrom - indirectionTo
        if dif == 0:
            return ""
        elif dif == -1:
            return "&"
        elif dif == 1:
            return "*"
        else:
            return "?? (%d)" % (dif,)
            raise error_not_supported(
                "Can't indirect this far - please fix me :-)")

    def GetIndirectedArgName(self, indirectFrom, indirectionTo):
        # print 'get:',self.arg.name,
        # indirectFrom,self._GetDeclaredIndirection() +
        # self.builtinIndirection, indirectionTo, self.arg.indirectionLevel

        if indirectFrom is None:
            # ACK! this does not account for [in][out] variables.
            # when this method is called, we need to know which
            indirectFrom = self._GetDeclaredIndirection() + self.builtinIndirection

        return self._IndirectPrefix(
            indirectFrom, indirectionTo) + self.arg.name

    def GetBuildValueArg(self):
        "Get the argument to be passes to Py_BuildValue"
        return self.arg.name

    def GetParseTupleArg(self):
        "Get the argument to be passed to PyArg_ParseTuple"
        if self.gatewayMode:
            # use whatever they were declared with
            return self.GetIndirectedArgName(None, 1)
        # local declarations have just their builtin indirection
        return self.GetIndirectedArgName(self.builtinIndirection, 1)

    def GetInterfaceCppObjectInfo(self):
        """Provide information about the C++ object used.

        Simple variables (such as integers) can declare their type (eg an integer)
        and use it as the target of both PyArg_ParseTuple and the COM function itself.

        More complex types require a PyObject * declared as the target of PyArg_ParseTuple,
        then some conversion routine to the C++ object which is actually passed to COM.

        This method provides the name, and optionally the type of that C++ variable.
        If the type if provided, the caller will likely generate a variable declaration.
        The name must always be returned.

        Result is a tuple of (variableName, [DeclareType|None|""])
        """

        # the first return element is the variable to be passed as
        #	 an argument to an interface method. the variable was
        #	 declared with only its builtin indirection level. when
        #	 we pass it, we'll need to pass in whatever amount of
        #	 indirection was applied (plus the builtin amount)
        # the second return element is the variable declaration; it
        #	 should simply be builtin indirection
        return self.GetIndirectedArgName(self.builtinIndirection, self.arg.indirectionLevel +
                                         self.builtinIndirection), "%s %s" % (self.GetUnconstType(), self.arg.name)

    def GetInterfaceArgCleanup(self):
        "Return cleanup code for C++ args passed to the interface method."
        if DEBUG:
            return "/* GetInterfaceArgCleanup output goes here: %s */\n" % self.arg.name
        else:
            return ""

    def GetInterfaceArgCleanupGIL(self):
        """Return cleanup code for C++ args passed to the interface
        method that must be executed with the GIL held"""
        if DEBUG:
            return "/* GetInterfaceArgCleanup (GIL held) output goes here: %s */\n" % self.arg.name
        else:
            return ""

    def GetUnconstType(self):
        return self.arg.unc_type

    def SetGatewayMode(self):
        self.gatewayMode = 1

    def _GetDeclaredIndirection(self):
        return self.arg.indirectionLevel
        print('declared:', self.arg.name, self.gatewayMode)
        if self.gatewayMode:
            return self.arg.indirectionLevel
        else:
            return self.declaredIndirection

    def DeclareParseArgTupleInputConverter(self):
        "Declare the variable used as the PyArg_ParseTuple param for a gateway"
        # Only declare it??
        # if self.arg.indirectionLevel==0:
        #	return "\t%s %s;\n" % (self.arg.type, self.arg.name)
        # else:
        if DEBUG:
            return "/* Declare ParseArgTupleInputConverter goes here: %s */\n" % self.arg.name
        else:
            return ""

    def GetParsePostCode(self):
        "Get a string of C++ code to be executed after (ie, to finalise) the PyArg_ParseTuple conversion"
        if DEBUG:
            return "/* GetParsePostCode code goes here: %s */\n" % self.arg.name
        else:
            return ""

    def GetBuildForInterfacePreCode(self):
        "Get a string of C++ code to be executed before (ie, to initialise) the Py_BuildValue conversion for Interfaces"
        if DEBUG:
            return "/* GetBuildForInterfacePreCode goes here: %s */\n" % self.arg.name
        else:
            return ""

    def GetBuildForGatewayPreCode(self):
        "Get a string of C++ code to be executed before (ie, to initialise) the Py_BuildValue conversion for Gateways"
        s = self.GetBuildForInterfacePreCode()  # Usually the same
        if DEBUG:
            if s[:4] == "/* G":
                s = "/* GetBuildForGatewayPreCode goes here: %s */\n" % self.arg.name
        return s

    def GetBuildForInterfacePostCode(self):
        "Get a string of C++ code to be executed after (ie, to finalise) the Py_BuildValue conversion for Interfaces"
        if DEBUG:
            return "/* GetBuildForInterfacePostCode goes here: %s */\n" % self.arg.name
        return ""

    def GetBuildForGatewayPostCode(self):
        "Get a string of C++ code to be executed after (ie, to finalise) the Py_BuildValue conversion for Gateways"
        s = self.GetBuildForInterfacePostCode()  # Usually the same
        if DEBUG:
            if s[:4] == "/* G":
                s = "/* GetBuildForGatewayPostCode goes here: %s */\n" % self.arg.name
        return s

    def GetAutoduckString(self):
        return '// @pyparm %s|%s||Description for %s' % (
            self._GetPythonTypeDesc(), self.arg.name, self.arg.name)

    def _GetPythonTypeDesc(self):
        "Returns a string with the description of the type.	 Used for doco purposes"
        return None

    def NeedUSES_CONVERSION(self):
        "Determines if this arg forces a USES_CONVERSION macro"
        return 0

# Special formatter for floats since they're smaller than Python floats.


class ArgFormatterFloat(ArgFormatter):

    def GetFormatChar(self):
        return "f"

    def DeclareParseArgTupleInputConverter(self):
        # Declare a double variable
        return "\tdouble dbl%s;\n" % self.arg.name

    def GetParseTupleArg(self):
        return "&dbl" + self.arg.name

    def _GetPythonTypeDesc(self):
        return "float"

    def GetBuildValueArg(self):
        return "&dbl" + self.arg.name

    def GetBuildForInterfacePreCode(self):
        return "\tdbl" + self.arg.name + " = " + self.arg.name + ";\n"

    def GetBuildForGatewayPreCode(self):
        return "\tdbl%s = " % self.arg.name + self._IndirectPrefix(
            self._GetDeclaredIndirection(),
            0) + self.arg.name + ";\n"

    def GetParsePostCode(self):
        s = "\t"
        if self.gatewayMode:
            s = s + self._IndirectPrefix(
                self._GetDeclaredIndirection(),
                0)
        s = s + self.arg.name
        s = s + " = (float)dbl%s;\n" % self.arg.name
        return s

# Special formatter for Shorts because they're
# a different size than Python ints!


class ArgFormatterShort(ArgFormatter):

    def GetFormatChar(self):
        return "i"

    def DeclareParseArgTupleInputConverter(self):
        # Declare a double variable
        return "\tINT i%s;\n" % self.arg.name

    def GetParseTupleArg(self):
        return "&i" + self.arg.name

    def _GetPythonTypeDesc(self):
        return "int"

    def GetBuildValueArg(self):
        return "&i" + self.arg.name

    def GetBuildForInterfacePreCode(self):
        return "\ti" + self.arg.name + " = " + self.arg.name + ";\n"

    def GetBuildForGatewayPreCode(self):
        return "\ti%s = " % self.arg.name + self._IndirectPrefix(
            self._GetDeclaredIndirection(),
            0) + self.arg.name + ";\n"

    def GetParsePostCode(self):
        s = "\t"
        if self.gatewayMode:
            s = s + self._IndirectPrefix(
                self._GetDeclaredIndirection(),
                0)
        s = s + self.arg.name
        s = s + " = i%s;\n" % self.arg.name
        return s

# for types which are 64bits on AMD64 - eg, HWND


class ArgFormatterLONG_PTR(ArgFormatter):

    def GetFormatChar(self):
        return "O"

    def DeclareParseArgTupleInputConverter(self):
        # Declare a PyObject variable
        return "\tPyObject *ob%s;\n" % self.arg.name

    def GetParseTupleArg(self):
        return "&ob" + self.arg.name

    def _GetPythonTypeDesc(self):
        return "int/long"

    def GetBuildValueArg(self):
        return "ob" + self.arg.name

    def GetBuildForInterfacePostCode(self):
        return "\tPy_XDECREF(ob%s);\n" % self.arg.name

    def DeclareParseArgTupleInputConverter(self):
        # Declare a PyObject variable
        return "\tPyObject *ob%s;\n" % self.arg.name

    def GetParsePostCode(self):
        return "\tif (bPythonIsHappy && !PyWinLong_AsULONG_PTR(ob%s, (ULONG_PTR *)%s)) bPythonIsHappy = FALSE;\n" % (
            self.arg.name, self.GetIndirectedArgName(None, 2))

    def GetBuildForInterfacePreCode(self):
        notdirected = self.GetIndirectedArgName(None, 1)
        return "\tob%s = PyWinObject_FromULONG_PTR(%s);\n" % \
            (self.arg.name, notdirected)

    def GetBuildForGatewayPostCode(self):
        return "\tPy_XDECREF(ob%s);\n" % self.arg.name


class ArgFormatterPythonCOM(ArgFormatter):
    """An arg formatter for types exposed in the PythonCOM module"""

    def GetFormatChar(self):
        return "O"
    # def GetInterfaceCppObjectInfo(self):
    #	return ArgFormatter.GetInterfaceCppObjectInfo(self)[0], \
    #		"%s %s%s" % (self.arg.unc_type, "*" * self._GetDeclaredIndirection(), self.arg.name)

    def DeclareParseArgTupleInputConverter(self):
        # Declare a PyObject variable
        return "\tPyObject *ob%s;\n" % self.arg.name

    def GetParseTupleArg(self):
        return "&ob" + self.arg.name

    def _GetPythonTypeDesc(self):
        return "<o Py%s>" % self.arg.type

    def GetBuildValueArg(self):
        return "ob" + self.arg.name

    def GetBuildForInterfacePostCode(self):
        return "\tPy_XDECREF(ob%s);\n" % self.arg.name

    def DeclareParseArgTupleInputConverter(self):
        # Declare a PyObject variable
        return "\tPyObject *ob%s;\n" % self.arg.name


class ArgFormatterBSTR(ArgFormatterPythonCOM):

    def _GetPythonTypeDesc(self):
        return "<o unicode>"

    def GetParsePostCode(self):
        return "\tif (bPythonIsHappy && !PyWinObject_AsBstr(ob%s, %s)) bPythonIsHappy = FALSE;\n" % (
            self.arg.name, self.GetIndirectedArgName(None, 2))

    def GetBuildForInterfacePreCode(self):
        notdirected = self.GetIndirectedArgName(None, 1)
        return "\tob%s = MakeBstrToObj(%s);\n" % \
            (self.arg.name, notdirected)

    def GetBuildForInterfacePostCode(self):
        return "\tSysFreeString(%s);\n" % (self.arg.name,) + \
               ArgFormatterPythonCOM.GetBuildForInterfacePostCode(self)

    def GetBuildForGatewayPostCode(self):
        return "\tPy_XDECREF(ob%s);\n" % self.arg.name


class ArgFormatterOLECHAR(ArgFormatterPythonCOM):

    def _GetPythonTypeDesc(self):
        return "<o unicode>"

    def GetUnconstType(self):
        if self.arg.type[:3] == "LPC":
            return self.arg.type[:2] + self.arg.type[3:]
        else:
            return self.arg.unc_type

    def GetParsePostCode(self):
        return "\tif (bPythonIsHappy && !PyWinObject_AsBstr(ob%s, %s)) bPythonIsHappy = FALSE;\n" % (
            self.arg.name, self.GetIndirectedArgName(None, 2))

    def GetInterfaceArgCleanup(self):
        return "\tSysFreeString(%s);\n" % self.GetIndirectedArgName(None, 1)

    def GetBuildForInterfacePreCode(self):
        # the variable was declared with just its builtin indirection
        notdirected = self.GetIndirectedArgName(self.builtinIndirection, 1)
        return "\tob%s = MakeOLECHARToObj(%s);\n" % \
            (self.arg.name, notdirected)

    def GetBuildForInterfacePostCode(self):
        # memory returned into an OLECHAR should be freed
        return "\tCoTaskMemFree(%s);\n" % (self.arg.name,) + \
               ArgFormatterPythonCOM.GetBuildForInterfacePostCode(self)

    def GetBuildForGatewayPostCode(self):
        return "\tPy_XDECREF(ob%s);\n" % self.arg.name


class ArgFormatterTCHAR(ArgFormatterPythonCOM):

    def _GetPythonTypeDesc(self):
        return "string/<o unicode>"

    def GetUnconstType(self):
        if self.arg.type[:3] == "LPC":
            return self.arg.type[:2] + self.arg.type[3:]
        else:
            return self.arg.unc_type

    def GetParsePostCode(self):
        return "\tif (bPythonIsHappy && !PyWinObject_AsTCHAR(ob%s, %s)) bPythonIsHappy = FALSE;\n" % (
            self.arg.name, self.GetIndirectedArgName(None, 2))

    def GetInterfaceArgCleanup(self):
        return "\tPyWinObject_FreeTCHAR(%s);\n" % self.GetIndirectedArgName(
            None, 1)

    def GetBuildForInterfacePreCode(self):
        # the variable was declared with just its builtin indirection
        notdirected = self.GetIndirectedArgName(self.builtinIndirection, 1)
        return "\tob%s = PyWinObject_FromTCHAR(%s);\n" % \
            (self.arg.name, notdirected)

    def GetBuildForInterfacePostCode(self):
        return "// ??? - TCHAR post code\n"

    def GetBuildForGatewayPostCode(self):
        return "\tPy_XDECREF(ob%s);\n" % self.arg.name


class ArgFormatterIID(ArgFormatterPythonCOM):

    def _GetPythonTypeDesc(self):
        return "<o PyIID>"

    def GetParsePostCode(self):
        return "\tif (!PyWinObject_AsIID(ob%s, &%s)) bPythonIsHappy = FALSE;\n" % (
            self.arg.name, self.arg.name)

    def GetBuildForInterfacePreCode(self):
        #		notdirected = self.GetIndirectedArgName(self.arg.indirectionLevel, 0)
        notdirected = self.GetIndirectedArgName(None, 0)
        return "\tob%s = PyWinObject_FromIID(%s);\n" % (
            self.arg.name, notdirected)

    def GetInterfaceCppObjectInfo(self):
        return self.arg.name, "IID %s" % (self.arg.name)


class ArgFormatterTime(ArgFormatterPythonCOM):

    def __init__(self, arg, builtinIndirection, declaredIndirection=0):
        # we don't want to declare LPSYSTEMTIME / LPFILETIME objects
        if arg.indirectionLevel == 0 and arg.unc_type[:2] == "LP":
            arg.unc_type = arg.unc_type[2:]
            # reduce the builtin and increment the declaration
            arg.indirectionLevel = arg.indirectionLevel + 1
            builtinIndirection = 0
        ArgFormatterPythonCOM.__init__(
            self, arg, builtinIndirection, declaredIndirection)

    def _GetPythonTypeDesc(self):
        return "<o PyTime>"

    def GetParsePostCode(self):
        # variable was declared with only the builtinIndirection
        # NOTE: this is an [in] ... so use only builtin
        return '\tif (!PyTime_Check(ob%s)) {\n\t\tPyErr_SetString(PyExc_TypeError, "The argument must be a PyTime object");\n\t\tbPythonIsHappy = FALSE;\n\t}\n\tif (!((PyTime *)ob%s)->GetTime(%s)) bPythonIsHappy = FALSE;\n' % (
            self.arg.name, self.arg.name, self.GetIndirectedArgName(self.builtinIndirection, 1))

    def GetBuildForInterfacePreCode(self):
        # use just the builtinIndirection again...
        notdirected = self.GetIndirectedArgName(self.builtinIndirection, 0)
        return "\tob%s = new PyTime(%s);\n" % (self.arg.name, notdirected)

    def GetBuildForInterfacePostCode(self):
        # hack to determine if we need to free stuff
        ret = ''
        if self.builtinIndirection + self.arg.indirectionLevel > 1:
            # memory returned into an OLECHAR should be freed
            ret = "\tCoTaskMemFree(%s);\n" % self.arg.name
        return ret + ArgFormatterPythonCOM.GetBuildForInterfacePostCode(self)


class ArgFormatterSTATSTG(ArgFormatterPythonCOM):

    def _GetPythonTypeDesc(self):
        return "<o STATSTG>"

    def GetParsePostCode(self):
        return '\tif (!PyCom_PyObjectAsSTATSTG(ob%s, %s, 0/*flags*/)) bPythonIsHappy = FALSE;\n' % (
            self.arg.name, self.GetIndirectedArgName(None, 1))

    def GetBuildForInterfacePreCode(self):
        notdirected = self.GetIndirectedArgName(None, 1)
        return "\tob%s = PyCom_PyObjectFromSTATSTG(%s);\n\t// STATSTG doco says our responsibility to free\n\tif ((%s).pwcsName) CoTaskMemFree((%s).pwcsName);\n" % (
            self.arg.name, self.GetIndirectedArgName(None, 1), notdirected, notdirected)


class ArgFormatterGeneric(ArgFormatterPythonCOM):

    def _GetPythonTypeDesc(self):
        return "<o %s>" % self.arg.type

    def GetParsePostCode(self):
        return '\tif (!PyObject_As%s(ob%s, &%s) bPythonIsHappy = FALSE;\n' % (
            self.arg.type, self.arg.name, self.GetIndirectedArgName(None, 1))

    def GetInterfaceArgCleanup(self):
        return '\tPyObject_Free%s(%s);\n' % (self.arg.type, self.arg.name)

    def GetBuildForInterfacePreCode(self):
        notdirected = self.GetIndirectedArgName(None, 1)
        return "\tob%s = PyObject_From%s(%s);\n" % (
            self.arg.name, self.arg.type, self.GetIndirectedArgName(None, 1))


class ArgFormatterIDLIST(ArgFormatterPythonCOM):

    def _GetPythonTypeDesc(self):
        return "<o PyIDL>"

    def GetParsePostCode(self):
        return '\tif (bPythonIsHappy && !PyObject_AsPIDL(ob%s, &%s)) bPythonIsHappy = FALSE;\n' % (
            self.arg.name, self.GetIndirectedArgName(None, 1))

    def GetInterfaceArgCleanup(self):
        return '\tPyObject_FreePIDL(%s);\n' % (self.arg.name,)

    def GetBuildForInterfacePreCode(self):
        notdirected = self.GetIndirectedArgName(None, 1)
        return "\tob%s = PyObject_FromPIDL(%s);\n" % (
            self.arg.name, self.GetIndirectedArgName(None, 1))


class ArgFormatterHANDLE(ArgFormatterPythonCOM):

    def _GetPythonTypeDesc(self):
        return "<o PyHANDLE>"

    def GetParsePostCode(self):
        return '\tif (!PyWinObject_AsHANDLE(ob%s, &%s, FALSE) bPythonIsHappy = FALSE;\n' % (
            self.arg.name, self.GetIndirectedArgName(None, 1))

    def GetBuildForInterfacePreCode(self):
        notdirected = self.GetIndirectedArgName(None, 1)
        return "\tob%s = PyWinObject_FromHANDLE(%s);\n" % (
            self.arg.name, self.GetIndirectedArgName(None, 0))


class ArgFormatterLARGE_INTEGER(ArgFormatterPythonCOM):

    def GetKeyName(self):
        return "LARGE_INTEGER"

    def _GetPythonTypeDesc(self):
        return "<o %s>" % self.GetKeyName()

    def GetParsePostCode(self):
        return '\tif (!PyWinObject_As%s(ob%s, %s)) bPythonIsHappy = FALSE;\n' % (
            self.GetKeyName(), self.arg.name, self.GetIndirectedArgName(None, 1))

    def GetBuildForInterfacePreCode(self):
        notdirected = self.GetIndirectedArgName(None, 0)
        return "\tob%s = PyWinObject_From%s(%s);\n" % (
            self.arg.name, self.GetKeyName(), notdirected)


class ArgFormatterULARGE_INTEGER(ArgFormatterLARGE_INTEGER):

    def GetKeyName(self):
        return "ULARGE_INTEGER"


class ArgFormatterInterface(ArgFormatterPythonCOM):

    def GetInterfaceCppObjectInfo(self):
        return self.GetIndirectedArgName(1, self.arg.indirectionLevel), \
            "%s * %s" % (self.GetUnconstType(), self.arg.name)

    def GetParsePostCode(self):
        # This gets called for out params in gateway mode
        if self.gatewayMode:
            sArg = self.GetIndirectedArgName(None, 2)
        else:
            # vs. in params for interface mode.
            sArg = self.GetIndirectedArgName(1, 2)
        return "\tif (bPythonIsHappy && !PyCom_InterfaceFromPyInstanceOrObject(ob%s, IID_%s, (void **)%s, TRUE /* bNoneOK */))\n\t\t bPythonIsHappy = FALSE;\n" % (
            self.arg.name, self.arg.type, sArg)

    def GetBuildForInterfacePreCode(self):
        return "\tob%s = PyCom_PyObjectFromIUnknown(%s, IID_%s, FALSE);\n" % (
            self.arg.name, self.arg.name, self.arg.type)

    def GetBuildForGatewayPreCode(self):
        sPrefix = self._IndirectPrefix(self._GetDeclaredIndirection(), 1)
        return "\tob%s = PyCom_PyObjectFromIUnknown(%s%s, IID_%s, TRUE);\n" % (
            self.arg.name, sPrefix, self.arg.name, self.arg.type)

    def GetInterfaceArgCleanup(self):
        return "\tif (%s) %s->Release();\n" % (self.arg.name, self.arg.name)


class ArgFormatterVARIANT(ArgFormatterPythonCOM):

    def GetParsePostCode(self):
        return "\tif ( !PyCom_VariantFromPyObject(ob%s, %s) )\n\t\tbPythonIsHappy = FALSE;\n" % (
            self.arg.name, self.GetIndirectedArgName(None, 1))

    def GetBuildForGatewayPreCode(self):
        notdirected = self.GetIndirectedArgName(None, 1)
        return "\tob%s = PyCom_PyObjectFromVariant(%s);\n" % (
            self.arg.name, notdirected)

    def GetBuildForGatewayPostCode(self):
        return "\tPy_XDECREF(ob%s);\n" % self.arg.name

        # Key :		, Python Type Description, ParseTuple format char
ConvertSimpleTypes = {"BOOL": ("BOOL", "int", "i"),
                      "UINT": ("UINT", "int", "i"),
                      "BYTE": ("BYTE", "int", "i"),
                      "INT": ("INT", "int", "i"),
                      "DWORD": ("DWORD", "int", "l"),
                      "HRESULT": ("HRESULT", "int", "l"),
                      "ULONG": ("ULONG", "int", "l"),
                      "LONG": ("LONG", "int", "l"),
                      "int": ("int", "int", "i"),
                      "long": ("long", "int", "l"),
                      "DISPID": ("DISPID", "long", "l"),
                      "APPBREAKFLAGS": ("int", "int", "i"),
                      "BREAKRESUMEACTION": ("int", "int", "i"),
                      "ERRORRESUMEACTION": ("int", "int", "i"),
                      "BREAKREASON": ("int", "int", "i"),
                      "BREAKPOINT_STATE": ("int", "int", "i"),
                      "BREAKRESUME_ACTION": ("int", "int", "i"),
                      "SOURCE_TEXT_ATTR": ("int", "int", "i"),
                      "TEXT_DOC_ATTR": ("int", "int", "i"),
                      "QUERYOPTION": ("int", "int", "i"),
                      "PARSEACTION": ("int", "int", "i"),
                      }


class ArgFormatterSimple(ArgFormatter):
    """An arg formatter for simple integer etc types"""

    def GetFormatChar(self):
        return ConvertSimpleTypes[self.arg.type][2]

    def _GetPythonTypeDesc(self):
        return ConvertSimpleTypes[self.arg.type][1]

AllConverters = {"const OLECHAR": (ArgFormatterOLECHAR, 0, 1),
                 "WCHAR": (ArgFormatterOLECHAR, 0, 1),
                 "OLECHAR": (ArgFormatterOLECHAR, 0, 1),
                 "LPCOLESTR": (ArgFormatterOLECHAR, 1, 1),
                 "LPOLESTR": (ArgFormatterOLECHAR, 1, 1),
                 "LPCWSTR": (ArgFormatterOLECHAR, 1, 1),
                 "LPWSTR": (ArgFormatterOLECHAR, 1, 1),
                 "LPCSTR": (ArgFormatterOLECHAR, 1, 1),
                 "LPTSTR": (ArgFormatterTCHAR, 1, 1),
                 "LPCTSTR": (ArgFormatterTCHAR, 1, 1),
                 "HANDLE": (ArgFormatterHANDLE, 0),
                 "BSTR": (ArgFormatterBSTR, 1, 0),
                 "const IID": (ArgFormatterIID, 0),
                 "CLSID": (ArgFormatterIID, 0),
                 "IID": (ArgFormatterIID, 0),
                 "GUID": (ArgFormatterIID, 0),
                 "const GUID": (ArgFormatterIID, 0),
                 "const IID": (ArgFormatterIID, 0),
                 "REFCLSID": (ArgFormatterIID, 0),
                 "REFIID": (ArgFormatterIID, 0),
                 "REFGUID": (ArgFormatterIID, 0),
                 "const FILETIME": (ArgFormatterTime, 0),
                 "const SYSTEMTIME": (ArgFormatterTime, 0),
                 "const LPSYSTEMTIME": (ArgFormatterTime, 1, 1),
                 "LPSYSTEMTIME": (ArgFormatterTime, 1, 1),
                 "FILETIME": (ArgFormatterTime, 0),
                 "SYSTEMTIME": (ArgFormatterTime, 0),
                 "STATSTG": (ArgFormatterSTATSTG, 0),
                 "LARGE_INTEGER": (ArgFormatterLARGE_INTEGER, 0),
                 "ULARGE_INTEGER": (ArgFormatterULARGE_INTEGER, 0),
                 "VARIANT": (ArgFormatterVARIANT, 0),
                 "float": (ArgFormatterFloat, 0),
                 "single": (ArgFormatterFloat, 0),
                 "short": (ArgFormatterShort, 0),
                 "WORD": (ArgFormatterShort, 0),
                 "VARIANT_BOOL": (ArgFormatterShort, 0),
                 "HWND": (ArgFormatterLONG_PTR, 1),
                 "HMENU": (ArgFormatterLONG_PTR, 1),
                 "HOLEMENU": (ArgFormatterLONG_PTR, 1),
                 "HICON": (ArgFormatterLONG_PTR, 1),
                 "HDC": (ArgFormatterLONG_PTR, 1),
                 "LPARAM": (ArgFormatterLONG_PTR, 1),
                 "WPARAM": (ArgFormatterLONG_PTR, 1),
                 "LRESULT": (ArgFormatterLONG_PTR, 1),
                 "UINT": (ArgFormatterShort, 0),
                 "SVSIF": (ArgFormatterShort, 0),
                 "Control": (ArgFormatterInterface, 0, 1),
                 "DataObject": (ArgFormatterInterface, 0, 1),
                 "_PropertyBag": (ArgFormatterInterface, 0, 1),
                 "AsyncProp": (ArgFormatterInterface, 0, 1),
                 "DataSource": (ArgFormatterInterface, 0, 1),
                 "DataFormat": (ArgFormatterInterface, 0, 1),
                 "void **": (ArgFormatterInterface, 2, 2),
                 "ITEMIDLIST": (ArgFormatterIDLIST, 0, 0),
                 "LPITEMIDLIST": (ArgFormatterIDLIST, 0, 1),
                 "LPCITEMIDLIST": (ArgFormatterIDLIST, 0, 1),
                 "const ITEMIDLIST": (ArgFormatterIDLIST, 0, 1),
                 }

# Auto-add all the simple types
for key in ConvertSimpleTypes.keys():
    AllConverters[key] = ArgFormatterSimple, 0


def make_arg_converter(arg):
    try:
        clz = AllConverters[arg.type][0]
        bin = AllConverters[arg.type][1]
        decl = 0
        if len(AllConverters[arg.type]) > 2:
            decl = AllConverters[arg.type][2]
        return clz(arg, bin, decl)
    except KeyError:
        if arg.type[0] == "I":
            return ArgFormatterInterface(arg, 0, 1)

        raise error_not_supported(
            "The type '%s' (%s) is unknown." %
            (arg.type, arg.name))


#############################################################
#
# The instances that represent the args, methods and interface
class Argument:
    """A representation of an argument to a COM method

    This class contains information about a specific argument to a method.
    In addition, methods exist so that an argument knows how to convert itself
    to/from Python arguments.
    """
#									  in,out					  type			  name			 [	]
#								   --------------				--------	  ------------		------
    regex = re.compile(
        r'/\* \[([^\]]*.*?)] \*/[ \t](.*[* ]+)(\w+)(\[ *])?[\),]')

    def __init__(self, good_interface_names):
        self.good_interface_names = good_interface_names
        self.inout = self.name = self.type = None
        self.const = 0
        self.arrayDecl = 0

    def BuildFromFile(self, file):
        """Parse and build my data from a file

        Reads the next line in the file, and matches it as an argument
        description.  If not a valid argument line, an error_not_found exception
        is raised.
        """
        line = file.readline()
        mo = self.regex.search(line)
        if not mo:
            raise error_not_found
        self.name = mo.group(3)
        self.inout = mo.group(1).split('][')
        typ = mo.group(2).strip()
        self.raw_type = typ
        self.indirectionLevel = 0
        if mo.group(4):  # Has "[ ]" decl
            self.arrayDecl = 1
            try:
                pos = typ.rindex("__RPC_FAR")
                self.indirectionLevel = self.indirectionLevel + 1
                typ = typ[:pos].strip()
            except ValueError:
                pass

        typ = typ.replace("__RPC_FAR", "")
        while True:
            try:
                pos = typ.rindex("*")
                self.indirectionLevel = self.indirectionLevel + 1
                typ = typ[:pos].strip()
            except ValueError:
                break
        self.type = typ
        if self.type[:6] == "const ":
            self.unc_type = self.type[6:]
        else:
            self.unc_type = self.type

        if VERBOSE:
            print(
                "	   Arg %s of type %s%s (%s)" %
                (self.name,
                 self.type,
                 "*" *
                 self.indirectionLevel,
                 self.inout))

    def HasAttribute(self, typ):
        """Determines if the argument has the specific attribute.

        Argument attributes are specified in the header file, such as
        "[in][out][retval]" etc.  You can pass a specific string (eg "out")
        to find if this attribute was specified for the argument
        """
        return typ in self.inout

    def GetRawDeclaration(self):
        ret = "%s %s" % (self.raw_type, self.name)
        if self.arrayDecl:
            ret = ret + "[]"
        return ret


class Method:
    """A representation of a C++ method on a COM interface

    This class contains information about a specific method, as well as
    a list of all @Argument@s
    """
#										 options	 ret type callconv	 name
#								   ----------------- -------- -------- --------
    regex = re.compile(r'virtual (/\*.*?\*/ )?(.*?) (.*?) (.*?)\(\w?')

    def __init__(self, good_interface_names):
        self.good_interface_names = good_interface_names
        self.name = self.result = self.callconv = None
        self.args = []

    def BuildFromFile(self, file):
        """Parse and build my data from a file

        Reads the next line in the file, and matches it as a method
        description.  If not a valid method line, an error_not_found exception
        is raised.
        """
        line = file.readline()
        mo = self.regex.search(line)
        if not mo:
            raise error_not_found
        self.name = mo.group(4)
        self.result = mo.group(2)
        if self.result != "HRESULT":
            if self.result == "DWORD":  # DWORD is for old old stuff?
                print(
                    "Warning: Old style interface detected - compilation errors likely!")
            else:
                print(
                    "Method %s - Only HRESULT return types are supported." %
                    self.name)
#				raise error_not_supported,		if VERBOSE:
            print("	 Method %s %s(" % (self.result, self.name))
        while True:
            arg = Argument(self.good_interface_names)
            try:
                arg.BuildFromFile(file)
                self.args.append(arg)
            except error_not_found:
                break


class Interface:
    """A representation of a C++ COM Interface

    This class contains information about a specific interface, as well as
    a list of all @Method@s
    """
#									  name				 base
#									 --------		   --------
    regex = re.compile("(interface|) ([^ ]*) : public (.*)$")

    def __init__(self, mo):
        self.methods = []
        self.name = mo.group(2)
        self.base = mo.group(3)
        if VERBOSE:
            print("Interface %s : public %s" % (self.name, self.base))

    def BuildMethods(self, file):
        """Build all sub-methods for this interface"""
        # skip the next 2 lines.
        file.readline()
        file.readline()
        while True:
            try:
                method = Method([self.name])
                method.BuildFromFile(file)
                self.methods.append(method)
            except error_not_found:
                break


def find_interface(interfaceName, file):
    """Find and return an interface in a file

    Given an interface name and file, search for the specified interface.

    Upon return, the interface itself has been built,
    but not the methods.
    """
    interface = None
    line = file.readline()
    while line:
        mo = Interface.regex.search(line)
        if mo:
            name = mo.group(2)
            print(name)
            AllConverters[name] = (ArgFormatterInterface, 0, 1)
            if name == interfaceName:
                interface = Interface(mo)
                interface.BuildMethods(file)
        line = file.readline()
    if interface:
        return interface
    raise error_not_found


def parse_interface_info(interfaceName, file):
    """Find, parse and return an interface in a file

    Given an interface name and file, search for the specified interface.

    Upon return, the interface itself is fully built,
    """
    try:
        return find_interface(interfaceName, file)
    except re.error:
        traceback.print_exc()
        print("The interface could not be built, as the regular expression failed!")


def test():
    f = open("d:\\msdev\\include\\objidl.h")
    try:
        parse_interface_info("IPersistStream", f)
    finally:
        f.close()


def test_regex(r, text):
    res = r.search(text, 0)
    if res == -1:
        print("** Not found")
    else:
        print(
            "%d\n%s\n%s\n%s\n%s" %
            (res,
             r.group(1),
             r.group(2),
             r.group(3),
             r.group(4)))
