from direct.showbase.PythonUtil import *
from types import *
import string
import FFIConstants
import FFISpecs
import FFITypes

"""
Things that are not supported:
 - Overloading a function based on an enum being differentiated from an int
 - Type names from C++ cannot have __enum__ in their name
 - Overloading static and non-static methods with the same name
"""

AT_not_atomic = 0
AT_int = 1
AT_float = 2
AT_double = 3
AT_bool = 4
AT_char = 5
AT_void = 6
AT_string = 7
AT_longlong = 8

def cullOverloadedMethods(fullMethodDict):
    """
    Find all the entries that have multiple indexes for the same method name
    Get rid of all others.
    """
    tmpDict = {}
    # For each class
    for methodName in fullMethodDict.keys():
        methodList = fullMethodDict[methodName]
        # See if this method has more than one function index (overloaded)
        if (len(methodList) > 1):
            tmpDict[methodName] = methodList
            # Mark all the method specifications as overloaded
            for methodSpec in methodList:
                methodSpec.overloaded = 1

    return tmpDict


def getTypeName(classTypeDesc, typeDesc):
    """
    Map the interrogate primitive type names to python type names.
    We assume that the module using this has imported the types module.
    It is valid to pass in None for classTypeDesc if we are not in a class
    """

    typeName = typeDesc.getFullNestedName()

    # Atomic C++ types are type checked against the builtin
    # Python types. This code sorts out the mapping
    if typeDesc.isAtomic():

        # Ints, bools, and chars are treated as ints.
        # Enums are special and are not atomic, see below
        if ((typeDesc.atomicType == AT_int) or
            (typeDesc.atomicType == AT_bool) or
            (typeDesc.atomicType == AT_char)):
            return 'IntType'

        # Floats and doubles are both floats in Python
        elif ((typeDesc.atomicType == AT_float) or
            (typeDesc.atomicType == AT_double)):
            return 'FloatType'

        elif ((typeDesc.atomicType == AT_longlong)):
            return 'LongType'

        # Strings are treated as Python strings
        elif ((typeDesc.atomicType == AT_string)):
            return 'StringType'

        elif (typeDesc.atomicType == AT_void):
            # Convert the void type to None type... I guess...
            # So far we do not have any code that uses this
            return 'NoneType'

        else:
            FFIConstants.notify.error("Unknown atomicType: %s" % (typeDesc.atomicType))

    # If the type is an enum, we really want to treat it like an int
    # To handle this, the type will have __enum__ in the name
    # Usually it will start the typeName, but some typeNames have the
    # surrounding class as part of their name
    # like BoundedObject.__enum__BoundingVolumeType
    elif (typeName.find('__enum__') >= 0):
        return 'IntType'

    # If it was not atomic or enum, it must be a class which is a
    # bit trickier because we output different things depending on the
    # scoping of the type.
    else:

        #   classTypeDesc  typeDesc fullNestedName Resulting TypeName
        # 1   Outer         Other     Other          Other.Other
        # 2   Outer         Outer     Outer          Outer
        # 3   Outer         Inner     Outer.Inner    Outer.Inner
        # 4   Inner         Other     Other          Other.Other
        # 5   Inner         Outer     Outer          Outer
        # 6   Inner         Inner     Outer.Inner    Outer.Inner
        # 7   None          Other     Other          Other.Other

        # CASES 1, 4, and 7 are the only ones that are different from the full
        # nested name, returning Other.Other

        returnNestedTypeNames = string.split(typeName, '.')
        returnModuleName = returnNestedTypeNames[0]

        if classTypeDesc:
            classTypeName = classTypeDesc.getFullNestedName()
            classNestedTypeNames = string.split(classTypeName, '.')
            # If there is no nesting, return typeName.typeName
            if ((not (classTypeDesc.foreignTypeName in returnNestedTypeNames)) and
                (not (typeDesc.foreignTypeName in classNestedTypeNames))):
                return (returnModuleName + '.' + typeName)
            # All other cases, we just need typeName
            else:
                return typeName
        else:
            # If you had no class, you need to specify module plus typename
            return (returnModuleName + '.' + typeName)


def inheritsFrom(type1, type2):
    """
    Return true if type1 inherits from type2
    This works by recursively checking parentTypes for type1
    """
    if type1.parentTypes:
        if type2 in type1.parentTypes:
            return 1
        else:
            result = 0
            for type in type1.parentTypes:
                result = (result or inheritsFrom(type, type2))
            return result
    else:
        return 0

def getInheritanceLevel(type, checkNested = 1):
    if type.__class__ == FFITypes.PyObjectTypeDescriptor:
        # A special case: PyObject * is always the most general
        # object.  Everything is a PyObject.
        return -1

    # If this is a nested type, return the inheritance level of the outer type.
    if type.isNested:
        # Check the level of your outer class
        # pass the checkNested flag as 0 to prevent an infinite loop
        # between the parent and child
        level = getInheritanceLevel(type.outerType, 0)
    else:
        level = 0

    for parentType in type.parentTypes:
        # Add 1 because you are one level higher than your parent
        level = max(level, 1+getInheritanceLevel(parentType))

    if checkNested:
        for nestedType in type.nestedTypes:
            # Do not add 1 to your nested types
            level = max(level, getInheritanceLevel(nestedType))

    return level

def inheritanceLevelSort(type1, type2):
    level1 = getInheritanceLevel(type1)
    level2 = getInheritanceLevel(type2)
    if (level1 == level2):
        # If they are equal in the inheritance,
        # sort them alphabetically by their type name
        return cmp(type1.foreignTypeName, type2.foreignTypeName)
    elif (level1 < level2):
        return -1
    elif (level1 > level2):
        return 1


def subclass(type1, type2):
    """
    Helper funcion used in sorting classes by inheritance
    """
    # If the types are the same, return 0
    if type1 == type2:
        return 0
    # If you have no args, sort you first
    elif (type1 == 0):
        return 1
    elif (type2 == 0):
        return -1
    # If class1 inherits from class2 return -1
    elif inheritsFrom(type1, type2):
        return -1
    # If class2 inherits from class1 return 1
    elif inheritsFrom(type2, type1):
        return 1
    else:
        # This is the don't care case. We must specify a sorting
        # rule just so it is not arbitrary
        if (type1.foreignTypeName > type2.foreignTypeName):
            return -1
        else:
            return 1


class FFIMethodArgumentTreeCollection:
    def __init__(self, classTypeDesc, methodSpecList):
        self.classTypeDesc = classTypeDesc
        self.methodSpecList = methodSpecList
        self.methodDict = {}
        self.treeDict = {}

    def outputOverloadedMethodHeader(self, file, nesting):
        # If one is static, we assume they all are.
        # The current system does not support overloading static and non-static
        # methods with the same name
        # Constructors are not treated as static. They are special because
        # they are not really constructors, they are instance methods that fill
        # in the this pointer.
        # Global functions do not need static versions
        if (self.methodSpecList[0].isStatic() and
            (not self.methodSpecList[0].isConstructor())):
            indent(file, nesting, 'def ' +
                   self.methodSpecList[0].name + '(*_args):\n')
        else:
            indent(file, nesting, 'def ' +
                   self.methodSpecList[0].name + '(self, *_args):\n')
        self.methodSpecList[0].outputCFunctionComment(file, nesting+2)
        indent(file, nesting+2, 'numArgs = len(_args)\n')

    def outputOverloadedMethodFooter(self, file, nesting):
        # If this is a static method, we need to output a static version
        # If one is static, we assume they all are.
        # The current system does not support overloading static and non-static
        # methods with the same name
        # Constructors are not treated as static. They are special because
        # they are not really constructors, they are instance methods that fill
        # in the this pointer.
        methodName = self.methodSpecList[0].name

        if (self.methodSpecList[0].isStatic() and
            (not self.methodSpecList[0].isConstructor()) and
            (not isinstance(self.methodSpecList[0], FFISpecs.GlobalFunctionSpecification))):
                self.outputOverloadedStaticFooter(file, nesting)
        else:
            if self.classTypeDesc:
                indent(file, nesting,   "FFIExternalObject.funcToMethod("+methodName+','+ self.classTypeDesc.foreignTypeName+ ",'"+methodName+"')\n")
                indent(file, nesting,   'del '+methodName+'\n')
                indent(file, nesting, ' \n')

        indent(file, nesting+1, '\n')

    def outputOverloadedStaticFooter(self, file, nesting):
        # foo = staticmethod(foo)
        methodName = self.methodSpecList[0].name
        indent(file, nesting, self.classTypeDesc.foreignTypeName + '.' + methodName + ' = staticmethod(' + methodName + ')\n')
        indent(file, nesting,'del ' +methodName+' \n\n')

    def setup(self):
        for method in self.methodSpecList:
            numArgs = len(method.typeDescriptor.thislessArgTypes())
            numArgsList = self.methodDict.setdefault(numArgs, [])
            numArgsList.append(method)
        for numArgs in self.methodDict.keys():
            methodList = self.methodDict[numArgs]
            tree = FFIMethodArgumentTree(self.classTypeDesc, methodList)
            treeList = self.treeDict.setdefault(numArgs, [])
            treeList.append(tree)

    def generateCode(self, file, nesting):
        self.setup()
        self.outputOverloadedMethodHeader(file, nesting)
        numArgsKeys = self.treeDict.keys()
        numArgsKeys.sort()
        for i in range(len(numArgsKeys)):
            numArgs = numArgsKeys[i]
            trees = self.treeDict[numArgs]
            for tree in trees:
                # If this is the first case, output an if clause
                if (i == 0):
                    indent(file, nesting+2, 'if (numArgs == ' + repr(numArgs) + '):\n')
                # If this is a subsequent first case, output an elif clause
                else:
                    indent(file, nesting+2, 'elif (numArgs == ' + repr(numArgs) + '):\n')
                tree.setup()
                tree.traverse(file, nesting+1, 0)

        # If the overloaded function got all the way through the if statements
        # it must have had the wrong number or type of arguments
        indent(file, nesting+2, "else:\n")
        indent(file, nesting+3, "raise TypeError, 'Invalid number of arguments: ' + repr(numArgs) + ', expected one of: ")
        for numArgs in numArgsKeys:
            indent(file, 0, (repr(numArgs) + ' '))
        indent(file, 0, "'\n")

        self.outputOverloadedMethodFooter(file, nesting)



class FFIMethodArgumentTree:
    """
    Tree is made from nested dictionaries.
    The keys are methodNamed.
    The values are [tree, methodSpec]
    methodSpec may be None at any level
    If tree is None, it is a leaf node and methodSpec will be defined
    """
    def __init__(self, classTypeDesc, methodSpecList):
        self.argSpec = None
        self.classTypeDesc = classTypeDesc
        self.methodSpecList = methodSpecList
        # The actual tree is implemented as nested dictionaries
        self.tree = {}

    def setup(self):
        for methodSpec in self.methodSpecList:
            argTypes = methodSpec.typeDescriptor.thislessArgTypes()
            self.fillInArgTypes(argTypes, methodSpec)

    def fillInArgTypes(self, argTypes, methodSpec):
        # If the method takes no arguments, we will assign a type index of 0
        if (len(argTypes) == 0):
            self.tree[0] = [
                FFIMethodArgumentTree(self.classTypeDesc,
                                      self.methodSpecList),
                methodSpec]

        else:
            self.argSpec = argTypes[0]
            typeDesc = self.argSpec.typeDescriptor.recursiveTypeDescriptor()

            if (len(argTypes) == 1):
                # If this is the last parameter, we are a leaf node, so store the
                # methodSpec in this dictionary
                self.tree[typeDesc] = [None, methodSpec]
            else:
                if typeDesc in self.tree:
                    # If there already is a tree here, jump into and pass the
                    # cdr of the arg list
                    subTree = self.tree[typeDesc][0]
                    subTree.fillInArgTypes(argTypes[1:], methodSpec)
                else:
                    # Add a subtree for the rest of the arg list
                    subTree = FFIMethodArgumentTree(self.classTypeDesc,
                                                    self.methodSpecList)
                    subTree.fillInArgTypes(argTypes[1:], methodSpec)
                    # This subtree has no method spec
                    self.tree[typeDesc] = [subTree, None]

    def traverse(self, file, nesting, level):
        oneTreeHasArgs = 0
        typeNameList = []

        # First see if this tree branches at all. If it does not there are
        # drastic optimizations we can take because we can simply call the
        # bottom-most function. We are not checking the types of all the
        # arguments for the sake of type checking, we are simply trying to
        # figure out which overloaded function to call. If there is only
        # one overloaded function with this number of arguements at this
        # level, it must be the one. No need to continue checking all the
        # arguments.
        branches = 0
        subTree = self
        prevTree = subTree
        levelCopy = level

        while subTree:
            if (len(subTree.tree.keys()) == 0):
                # Dead end branch
                break
            if (len(subTree.tree.keys()) > 1):
                # Ok, we branch, it was worth a try though
                branches = 1
                break

            prevTree = subTree
            # Must only have one subtree, traverse it
            subTree = subTree.tree.values()[0][0]
            levelCopy += 1

        # If there were no branches, this is easy
        # Just output the function and return
        # Note this operates on prevTree because subTree went one too far
        if not branches:
            methodSpec = prevTree.tree.values()[0][1]
            indent(file, nesting+2, 'return ')
            methodSpec.outputOverloadedCall(file, prevTree.classTypeDesc, levelCopy)
            return

        # Ok, We must have a branch down here somewhere
        # Make a copy of the keys so we can sort them in place
        sortedKeys = self.tree.keys()
        # Sort the keys based on inheritance hierarchy, most specific classes first
        sortedKeys.sort(subclass)

        for i in range(len(sortedKeys)):
            typeDesc = sortedKeys[i]
            # See if this takes no arguments
            if (typeDesc == 0):
                # Output the function
                methodSpec = self.tree[0][1]
                indent(file, nesting+2, 'return ')
                methodSpec.outputOverloadedCall(file, self.classTypeDesc, 0)
            else:
                # This is handled at the top of the file now (?)
                # Import a file if we need to for this typeDesc
                # if ((typeDesc != 0) and
                #     (not typeDesc.isNested) and
                #     # Do not put our own module in the import list
                #     (self.classTypeDesc != typeDesc) and
                #     # If this is a class (not a primitive), put it on the list
                #     (typeDesc.__class__ == FFITypes.ClassTypeDescriptor)):
                #     indent(file, nesting+2, 'import ' + typeDesc.foreignTypeName + '\n')

                # Specify that at least one of these trees had arguments
                # so we know to output an else clause
                oneTreeHasArgs = 1
                typeName = getTypeName(self.classTypeDesc, typeDesc)
                typeNameList.append(typeName)
                if typeDesc.__class__ == FFITypes.PyObjectTypeDescriptor:
                    # A special case: if one of the parameters is
                    # PyObject *, that means anything is accepted.
                    condition = '1'

                else:
                    # Otherwise, we'll check the particular type of
                    # the object.
                    condition = '(isinstance(_args[' + repr(level) + '], ' + typeName + '))'
                    # Legal types for a float parameter include int and long.
                    if (typeName == 'FloatType'):
                        condition += (' or (isinstance(_args[' + repr(level) + '], IntType))')
                        condition += (' or (isinstance(_args[' + repr(level) + '], LongType))')
                    # Legal types for a long parameter include int.
                    elif (typeName == 'LongType'):
                        condition += (' or (isinstance(_args[' + repr(level) + '], IntType))')
                    # Legal types for an int parameter include long.
                    elif (typeName == 'IntType'):
                        condition += (' or (isinstance(_args[' + repr(level) + '], LongType))')

                indent(file, nesting+2, 'if ' + condition + ':\n')

                if (self.tree[typeDesc][0] is not None):
                    self.tree[typeDesc][0].traverse(file, nesting+1, level+1)
                else:
                    methodSpec = self.tree[typeDesc][1]
                    indent(file, nesting+3, 'return ')
                    numArgs = level+1
                    methodSpec.outputOverloadedCall(file, self.classTypeDesc, numArgs)

        # Output an else clause if one of the trees had arguments
        if oneTreeHasArgs:
            indent(file, nesting+2, "raise TypeError, 'Invalid argument " + repr(level) + ", expected one of: ")
            for name in typeNameList:
                indent(file, 0, ('<' + name + '> '))
            indent(file, 0, "'\n")

    def isSinglePath(self):
        if (len(self.tree.keys()) > 1):
            # More than one child, return false
            return 0
        else:
            # Only have one child, see if he only has one child
            key = self.tree.keys()[0]
            tree = self.tree[key][0]
            if tree:
                return tree.isSinglePath()
            else:
                return self.tree[key][1]