from new import instance
import FFIConstants

WrapperClassMap = {}

DowncastMap = {}

# For testing, you can turn verbose and debug on
# FFIConstants.notify.setInfo(1)
# FFIConstants.notify.setDebug(1)

# Uncomment the notify statements if you need to debug,
# otherwise leave them commented out to prevent runtime
# overhead of calling them



# Register a python class in the type map if it is a typed object
# The type map is used for upcasting and downcasting through
# the panda inheritance chain
def registerInTypeMap(pythonClass):
    from pandac import TypedObject
    if issubclass(pythonClass, TypedObject.TypedObject):
        typeIndex = pythonClass.getClassType().getIndex()
        WrapperClassMap[typeIndex] = pythonClass


def funcToMethod(func, clas, method_name=None):
    """Adds func to class so it is an accessible method; use method_name to specify the name to be used for calling the method.
    The new method is accessible to any instance immediately."""
    func.im_class=clas
    func.im_func=func
    func.im_self=None
    if not method_name:
        clas.__dict__[method_name]=func
    else:
        clas.__dict__[func.__name__]=func


def FFIInstance(classdef, this = 0, userManagesMemory = 0):
    answer = instance(classdef)
    answer.this = this
    answer.userManagesMemory = userManagesMemory
    return answer

class FFIExternalObject:
    def __init__(self, *_args):
        # By default, we do not manage our own memory
        self.userManagesMemory = 0
        # Start with a null this pointer
        self.this = 0

    def destructor(self):
        # Base destructor in case you do not define one
        pass

    def getLineage(self, thisClass, targetBaseClass):
        # Recursively determine the path in the heirarchy tree from thisClass
        # to the targetBaseClass
        return self.getLineageInternal(thisClass, targetBaseClass, [thisClass])

    def getLineageInternal(self, thisClass, targetBaseClass, chain):
        # Recursively determine the path in the heirarchy tree from thisClass
        # to the targetBaseClass
        #FFIConstants.notify.debug('getLineageInternal: checking %s to %s'
        #                          % (thisClass.__name__, targetBaseClass.__name__))
        if (targetBaseClass in thisClass.__bases__):
            # Found a link
            return chain + [targetBaseClass]
        elif (len(thisClass.__bases__) == 0):
            # No possible links
            return 0
        else:
            # recurse
            for base in thisClass.__bases__:
                res = self.getLineageInternal(base, targetBaseClass, chain+[base])
                if res:
                    # FFIConstants.notify.debug('getLineageInternal: found path: ' + repr(res))
                    return res
            # Not found anywhere
            return 0

    def getDowncastFunctions(self, thisClass, baseClass):
        #FFIConstants.notify.debug(
        #    'getDowncastFunctions: Looking for downcast function from %s to %s'
        #    % (baseClass.__name__, thisClass.__name__))
        lineage = self.getLineage(thisClass, baseClass)
        # Start with an empty list of downcast functions
        downcastFunctionList = []

        # If it could not find the baseClass anywhere in the lineage,
        # return empty
        if not lineage:
            return []

        # Walk along the lineage looking for downcast functions from
        # class to class+1.  Start at the top and work downwards.
        top = len(lineage) - 1
        for i in range(top):
            toClass = lineage[top - i - 1]
            fromClass = lineage[top - i]
            downcastFuncName = ('downcastTo' + toClass.__name__
                                + 'From' + fromClass.__name__)
            # Look over this classes global modules dictionaries
            # for the downcast function name
            for globmod in toClass.__CModuleDowncasts__:
                func = globmod.__dict__.get(downcastFuncName)
                if func:
                    #FFIConstants.notify.debug(
                    #    'getDowncastFunctions: Found downcast function %s in %s'
                    #    % (downcastFuncName, globmod.__name__))
                    downcastFunctionList.append(func)
        return downcastFunctionList

    def lookUpNewType(self, typeHandle, rootType):
        # We tried to downcast to an unknown type.  Try to figure out
        # the lowest type we *do* know, so we can downcast to that
        # type instead.
        if typeHandle.getNumParentClasses() == 0:
            # This type has no parents!  That shouldn't happen.
            FFIConstants.notify.warning("Unknown class type: %s has no parents!" % (typeHandle.getName()))
            return None

        parentType = typeHandle.getParentTowards(rootType, self)
        parentIndex = parentType.getIndex()
        parentWrapperClass = WrapperClassMap.get(parentIndex)
        if parentWrapperClass == None:
            parentWrapperClass = self.lookUpNewType(parentType, rootType)

        if parentWrapperClass != None:
            # If the parent class is known, then record that this
            # class is a derivation of that parent class.
            WrapperClassMap[typeHandle.getIndex()] = parentWrapperClass

        return parentWrapperClass

    def setPointer(self):
        # See what type it really is and downcast to that type (if necessary)
        # Look up the TypeHandle in the dict. get() returns None if it is not there
        index = self.getTypeIndex()
        exactWrapperClass = WrapperClassMap.get(index)
        if exactWrapperClass == None:
            # This is an unknown class type.  Perhaps it derives from
            # a class type we know.
            exactWrapperClass = self.lookUpNewType(self.getType(), self.getClassType())

        # We do not need to downcast if we already have the same class
        if (exactWrapperClass and (exactWrapperClass != self.__class__)):
            # Create a new wrapper class instance
            #exactObject = exactWrapperClass(None)
            exactObject = FFIInstance(exactWrapperClass)
            # Get the downcast pointer that has had all the downcast
            # funcs called
            downcastObject = self.downcast(exactWrapperClass)
            exactObject.this = downcastObject.this
            exactObject.userManagesMemory = downcastObject.userManagesMemory
            # Make sure the original downcast object does not get
            # garbage collected so that the exactObject will not get
            # gc'd thereby transferring ownership of the object to
            # this new exactObject
            downcastObject.userManagesMemory = 0
            return exactObject
        else:
            return self

    def downcast(self, toClass):
        fromClass = self.__class__
        #FFIConstants.notify.debug('downcast: downcasting from %s to %s' % \
        #    (fromClass.__name__, toClass.__name__))

        # Check the cache to see if we have looked this up before
        downcastChain = DowncastMap.get((fromClass, toClass))
        if downcastChain == None:
            downcastChain = self.getDowncastFunctions(toClass, fromClass)
            #FFIConstants.notify.debug('downcast: computed downcast chain: ' + repr(downcastChain))
            # Store it for next time
            DowncastMap[(fromClass, toClass)] = downcastChain
        newObject = self
        for downcastFunc in downcastChain:
            #FFIConstants.notify.debug('downcast: downcasting %s using %s' % \
            #                         (newObject.__class__.__name__, downcastFunc))
            newObject = downcastFunc(newObject)
        return newObject

    def compareTo(self, other):
        # By default, we compare the C++ pointers
        # Some classes will override the compareTo operator with their own
        # logic in C++ (like vectors and matrices for instance)
        try:
            if self.this < other.this:
                return -1
            if self.this > other.this:
                return 1
            else:
                return 0
        except:
            return 1

    def __cmp__(self, other):
        # Only use the C++ compareTo if they are the same class
        if isinstance(other, self.__class__):
            return self.compareTo(other)
        # Otherwise, they must not be the same
        # Just do a basic python id compare
        else:
            return cmp(id(self), id(other))

    def __repr__(self):
        # Lots of Panda classes have an output function defined that takes an Ostream
        # We create a LineStream for the output function to write to, then we extract
        # the string out of it and return it as our str
        try:
            from pandac import LineStream
            lineStream = LineStream.LineStream()
            self.output(lineStream)
            baseRepr = lineStream.getLine()
        except AssertionError, e:
            raise AssertionError, e
        except:
            baseRepr = ('[' + self.__class__.__name__ + ' at: ' + repr(self.this) + ']')
        # In any case, return the baseRepr
        return baseRepr

    def __str__(self):
        # This is a more complete version of printing which shows the object type
        # and pointer, plus the output from write() or output() whichever is defined
        # Print this info for all objects
        baseRepr = ('[' + self.__class__.__name__ + ' at: ' + repr(self.this) + ']')
        # Lots of Panda classes have an write or output function defined that takes an Ostream
        # We create a LineStream for the write or output function to write to, then we extract
        # the string out of it and return it as our repr
        from pandac import LineStream
        lineStream = LineStream.LineStream()
        try:
            # First try the write function, that is the better one
            self.write(lineStream)
            while lineStream.isTextAvailable():
                baseRepr = baseRepr + '\n' + lineStream.getLine()
        except AssertionError, e:
            raise AssertionError, e
        except:
            try:
                # Sometimes write insists on a seconds parameter.
                self.write(lineStream, 0)
                while lineStream.isTextAvailable():
                    baseRepr = baseRepr + '\n' + lineStream.getLine()
            except AssertionError, e:
                raise AssertionError, e
            except:
                try:
                    # Ok, no write function, lets try output then
                    self.output(lineStream)
                    while lineStream.isTextAvailable():
                        baseRepr = baseRepr + '\n' + lineStream.getLine()
                except AssertionError, e:
                    raise AssertionError, e
                except:
                    pass
        # In any case, return the baseRepr
        return baseRepr

    def __hash__(self):
        return self.this