from inspect import isfunction
import thread

class ThreadStack(object):
    "An object that keeps a separate stack for each frame"
    def __init__(self):
        self.stacks = {}
    def push(self, val):
        key = thread.get_ident()
        try:
            stack = self.stacks[key]
        except KeyError:
            stack = self.stacks[key] = [None]
        stack.append(val)
    def pop(self):
        return self.stacks[thread.get_ident()].pop()
    def top(self):
        return self.stacks[thread.get_ident()][-1]
        
# This keeps track of what class a function is called from
__callerstack__ = ThreadStack()

class function(object):
    "A function. It knows what class it is defined in"
    def __init__(self, f, callerclass=None):
        self.data = f, callerclass
    def __call__(self, *args, **kwargs):
        f, callerclass = self.data
        try:
            __callerstack__.push(callerclass)
            return f(*args, **kwargs)
        finally:
            __callerstack__.pop()

class method(object):
    "A normal class method, visible from outside the class"
    def __init__(self, f):
        self.f = f
    def __get__(self, obj, objtype=None):
        return function(self.f.__get__(obj, objtype), self.defclass)

class LocalMethodError(AttributeError):
    pass
    
class localmethod(method):
    "A method that cannot be seen from outside its definition class"
    def __get__(self, obj, objtype=None):
        # callobj = obj or objtype
        defclass = self.defclass
        callerclass = __callerstack__.top()
        if callerclass is defclass:
            return self.f.__get__(obj, objtype)
        else:
            # The caller method is from a different class, so resume mro
            mro = iter( (obj and type(obj) or objtype).mro() )
            for c in mro: # Skip all classes up to the localmethod's class
                if c == defclass: break
            name = self.name
            for base in mro:
                try:
                    attr = base.__dict__[name]
                    if base is callerclass or not isinstance(attr, localmethod):
                        return attr.__get__(obj, objtype)
                except KeyError:
                    continue
        raise LocalMethodError, "method '%s' is local to '%s'" % (self.name, self.defclass.__name__)
        

class Type(type):
    "Replaces type"
    def __new__(self, name, bases, attrs):
        # decorate all function attributes with 'method'
        for attr, val in attrs.items():
            if isfunction(val):
                attrs[attr] = method(val)
        return type.__new__(self, name, bases, attrs)
    def __init__(self, name, bases, attrs):
        for attr, val in attrs.iteritems():
            # Inform methods of what class they are created in
            if isinstance(val, method):
                val.defclass = self
            # Inform localmethod of their name (in case they have to
            # be bypassed)
            if isinstance(val, localmethod):
                val.name = attr


class Object(object):
    "Replaces object"
    __metaclass__ = Type