import types,string

""" You could add polymorphism for your class.
    You just have to inherit polymorph.Polymorph class
    and prefix your methods that have the same name
    but differ from the number of args with the
    following prefix : 
    
        __<x>p__ where x is the number of args of your method
        __<x>pe__ where x is the number of args of your method if you use elliptic argument at the end (*args)

    for example :   
        def __1p__spam(self, first)
        def __3pe__egg(self,first,second,*args)
    
    Priority : check if __<x>p__... exists before __<x>pe__... for the same <x>.
    
    author: rashkatsa 
    send any comments to rashkatsa@wanadoo.fr
"""

# metaclass
class MetaPolymorph:
    def __init__(self, name, bases, namespace):
        """Create a new class."""
        self.__name__ = name
        self.__bases__ = bases
        self.__namespace__ = namespace
    def __call__(self):
        """Create a new instance."""
        return PolymorphInstance(self)

# instance
class PolymorphInstance:
    def __init__(self, metaclass):
        self.__metaclass__ = metaclass
        # simple polymorphic method
        self.polymorphicMethods={}
        for pmn in [x for x in self.__metaclass__.__namespace__.keys() if string.find(x,'p__') != -1 and string.rfind(x[:string.find(x,'p__')],'__')!=-1 and type(self.__metaclass__.__namespace__[x]) is types.FunctionType]:
            pm = pmn[string.find(pmn,'p__')+3:]
            nbarg=str(pmn[string.rfind(pmn[:string.find(pmn,'p__')],'__')+2:string.find(pmn,'p__')])
            if pm in self.polymorphicMethods:
                self.polymorphicMethods[pm][nbarg]=self.__metaclass__.__namespace__[pmn]
            else:
                self.polymorphicMethods[pm]={nbarg:self.__metaclass__.__namespace__[pmn]}
        # elliptic polymorphic method
        self.ePolymorphicMethods={}
        for pmn in [x for x in self.__metaclass__.__namespace__.keys() if string.find(x,'pe__') != -1 and string.rfind(x[:string.find(x,'pe__')],'__')!=-1 and type(self.__metaclass__.__namespace__[x]) is types.FunctionType]:
            pm = pmn[string.find(pmn,'pe__')+4:]
            nbarg=str(pmn[string.rfind(pmn[:string.find(pmn,'pe__')],'__')+2:string.find(pmn,'pe__')])
            if pm in self.ePolymorphicMethods:
                self.ePolymorphicMethods[pm][nbarg]=self.__metaclass__.__namespace__[pmn]
            else:
                self.ePolymorphicMethods[pm]={nbarg:self.__metaclass__.__namespace__[pmn]}
    def __getattr__(self, name):
        polymorphMethodName=None
        try:
            value = self.__metaclass__.__namespace__[name]
        except KeyError:
            if name in self.polymorphicMethods.keys()+self.ePolymorphicMethods.keys():
                polymorphMethodName=name
            else:
                raise AttributeError, name
        if not polymorphMethodName:
            if type(value) is not types.FunctionType:
                return value
            return BoundMethod(self,value)
        else:
            if name in self.polymorphicMethods.keys():
                pMethods=self.polymorphicMethods[name]
            else:
                pMethods=None
            if name in self.ePolymorphicMethods.keys():
                ellipticPMethods=self.ePolymorphicMethods[name]
            else:
                ellipticPMethods=None
            return BoundPolymorphMethods(self,polymorphMethodName,pMethods,ellipticPMethods)

# bound function with instance == BoundMethod
class BoundMethod:
    def __init__(self, instance, function):
        self.instance = instance
        self.function = function
    def __call__(self, *args):
        return apply(self.function, (self.instance,) + args)

# bound polymorph functions with instance == BoundPolymorphMethods
class BoundPolymorphMethods:
    def __init__(self, instance, functionName, functions, ellipticFunctions):
        self.instance = instance
        self.functionName=functionName
        self.functions = functions
        self.ellipticFunctions=ellipticFunctions
    def __call__(self, *args):
        self.func=None
        argl=len(args)
        try:
            self.func = self.functions[str(argl)]
        except KeyError:
            # try to find an elliptic function
            if self.ellipticFunctions:
                # sort the list to find the min-max elliptic function that could be called
                sortedKeys=[int(x) for x in self.ellipticFunctions.keys()]
                sortedKeys.sort()
                sortedKeys.reverse()
                keyFound=None
                # (x-1) because *args could be empty : ()
                for x in sortedKeys:
                    if argl >= (x-1):
                        keyFound=str(x)
                        break
                if keyFound:
                    self.func=self.ellipticFunctions[keyFound]
            if not self.func:
                if argl < 1:
                    errorMsg='Error: Unknown polymorphic method '+self.functionName+' with no argument'
                elif argl == 1:
                    errorMsg='Error: Unknown polymorphic method '+self.functionName+' with 1 argument'
                else:
                    errorMsg='Error: Unknown polymorphic method '+self.functionName+' with '+str(len(args))+' arguments'
                raise errorMsg
        return apply(self.func, (self.instance,) + args)

# create Polymorph class with the metaclass defined above
Polymorph = MetaPolymorph('Polymorph', (), {})
