# pylint: skip-file

from __future__ import absolute_import

# Originally taken from https://pypi.python.org/pypi/ProxyTypes
# inlined due to python3 issues with setup.py, we should gut this out and simplify it


class AbstractProxy(object):
    """Delegates all operations (except ``.__subject__``) to another object"""
    __slots__ = ()

    def __call__(self, *args, **kw):
        return self.__subject__(*args, **kw)

    def __getattribute__(self, attr, oga=object.__getattribute__):
        subject = oga(self, '__subject__')
        if attr == '__subject__':
            return subject
        return getattr(subject, attr)

    def __setattr__(self, attr, val, osa=object.__setattr__):
        if attr == '__subject__':
            osa(self, attr, val)
        else:
            setattr(self.__subject__, attr, val)

    def __delattr__(self, attr, oda=object.__delattr__):
        if attr == '__subject__':
            oda(self, attr)
        else:
            delattr(self.__subject__, attr)

    def __nonzero__(self):
        return bool(self.__subject__)

    def __getitem__(self, arg):
        return self.__subject__[arg]

    def __setitem__(self, arg, val):
        self.__subject__[arg] = val

    def __delitem__(self, arg):
        del self.__subject__[arg]

    def __getslice__(self, i, j):
        return self.__subject__[i:j]

    def __setslice__(self, i, j, val):
        self.__subject__[i:j] = val

    def __delslice__(self, i, j):
        del self.__subject__[i:j]

    def __contains__(self, ob):
        return ob in self.__subject__

    for name in 'repr str hash len abs complex int long float iter oct hex'.split():
        exec('def __%s__(self): return %s(self.__subject__)' % (name, name))

    for name in 'cmp', 'coerce', 'divmod':
        exec('def __%s__(self,ob): return %s(self.__subject__,ob)' % (name, name))

    for name, op in [
        ('lt', '<'), ('gt', '>'), ('le', '<='), ('ge', '>='),
        ('eq', '=='), ('ne', '!=')
    ]:
        exec('def __%s__(self,ob): return self.__subject__ %s ob' % (name, op))

    for name, op in [('neg', '-'), ('pos', '+'), ('invert', '~')]:
        exec('def __%s__(self): return %s self.__subject__' % (name, op))

    for name, op in [
        ('or', '|'),  ('and', '&'), ('xor', '^'), ('lshift', '<<'),
        ('rshift', '>>'), ('add', '+'), ('sub', '-'), ('mul', '*'),
        ('div', '/'), ('mod', '%'), ('truediv', '/'), ('floordiv', '//')
    ]:
        exec((
            'def __%(name)s__(self,ob):\n'
            '    return self.__subject__ %(op)s ob\n'
            '\n'
            'def __r%(name)s__(self,ob):\n'
            '    return ob %(op)s self.__subject__\n'
            '\n'
            'def __i%(name)s__(self,ob):\n'
            '    self.__subject__ %(op)s=ob\n'
            '    return self\n'
        ) % locals())

    del name, op

    # Oddball signatures

    def __rdivmod__(self, ob):
        return divmod(ob, self.__subject__)

    def __pow__(self, *args):
        return pow(self.__subject__, *args)

    def __ipow__(self, ob):
        self.__subject__ **= ob
        return self

    def __rpow__(self, ob):
        return pow(ob, self.__subject__)


class ObjectProxy(AbstractProxy):
    """Proxy for a specific object"""

    __slots__ = '__subject__'

    def __init__(self, subject):
        self.__subject__ = subject


class CallbackProxy(AbstractProxy):
    """Proxy for a dynamically-chosen object"""

    __slots__ = '__callback__'

    def __init__(self, func):
        set_callback(self, func)


set_callback = CallbackProxy.__callback__.__set__
get_callback = CallbackProxy.__callback__.__get__
CallbackProxy.__subject__ = property(lambda self, gc=get_callback: gc(self)())


class LazyProxy(CallbackProxy):
    """Proxy for a lazily-obtained object, that is cached on first use"""
    __slots__ = '__cache__'


get_cache = LazyProxy.__cache__.__get__
set_cache = LazyProxy.__cache__.__set__


def __subject__(self, get_cache=get_cache, set_cache=set_cache):
    try:
        return get_cache(self)
    except AttributeError:
        set_cache(self, get_callback(self)())
        return get_cache(self)


LazyProxy.__subject__ = property(__subject__, set_cache)
del __subject__


class AbstractWrapper(AbstractProxy):
    """Mixin to allow extra behaviors and attributes on proxy instance"""
    __slots__ = ()

    def __getattribute__(self, attr, oga=object.__getattribute__):
        if attr.startswith('__'):
            subject = oga(self, '__subject__')
            if attr == '__subject__':
                return subject
            return getattr(subject, attr)
        return oga(self, attr)

    def __getattr__(self, attr, oga=object.__getattribute__):
        return getattr(oga(self, '__subject__'), attr)

    def __setattr__(self, attr, val, osa=object.__setattr__):
        if (
            attr == '__subject__' or
            hasattr(type(self), attr) and not attr.startswith('__')
        ):
            osa(self, attr, val)
        else:
            setattr(self.__subject__, attr, val)

    def __delattr__(self, attr, oda=object.__delattr__):
        if (
            attr == '__subject__' or
            hasattr(type(self), attr) and not attr.startswith('__')
        ):
            oda(self, attr)
        else:
            delattr(self.__subject__, attr)


class ObjectWrapper(ObjectProxy, AbstractWrapper):
    __slots__ = ()


class CallbackWrapper(CallbackProxy, AbstractWrapper):
    __slots__ = ()


class LazyWrapper(LazyProxy, AbstractWrapper):
    __slots__ = ()


#######################
#  MONKEYS DOWN HERE  #
#######################


# pylint: disable=E1001,E1002
class CallableProxy(ObjectWrapper):
    __slots__ = ('_eop_wrapper_')

    def __init__(self, wrapped, wrapper):
        super(CallableProxy, self).__init__(wrapped)
        self._eop_wrapper_ = wrapper

    def __call__(self, *args, **kwargs):
        return self._eop_wrapper_(self.__subject__, *args, **kwargs)


class BoundMethodProxy(ObjectWrapper):
    __slots__ = ('_eop_wrapper_', '_eop_instance_')

    def __init__(self, wrapped, instance, wrapper):
        super(BoundMethodProxy, self).__init__(wrapped)
        self._eop_instance_ = instance
        self._eop_wrapper_ = wrapper

    def __call__(self, *args, **kwargs):
        return self._eop_wrapper_(self.__subject__, self._eop_instance_, *args, **kwargs)


class UnboundMethodProxy(CallableProxy):
    __slots__ = ('_eop_wrapper_')

    def __get__(self, instance, owner):
        return BoundMethodProxy(self.__subject__.__get__(instance, owner), instance or owner, self._eop_wrapper_)

    def __getattribute__(self, attr, oga=object.__getattribute__):
        """
        We need to return our own version of __get__ or we may end up
        never being called if the member we are wrapping is wrapped
        again by someone else
        """
        if attr == '__get__':
            return oga(self, attr)
        return super(UnboundMethodProxy, self).__getattribute__(attr)


def monkeypatch_method(cls, method_name=None):
    def decorator(func):
        method_to_patch = method_name or func.__name__
        original = cls.__dict__[method_to_patch]
        replacement = UnboundMethodProxy(original, func)
        type.__setattr__(cls, method_to_patch, replacement)  # Avoid overrides
        return func
    return decorator
