"""
NetRef - transparent network references implementation.

SURGEON GENERAL'S WARNING: Black magaic is known to causes Lung Cancer,
Heart Disease, Emphysema, and May Complicate Pregnancy. Close your eyes!
"""
import sys
import inspect
import types
import pickle
from rpyc.core import consts


_local_netref_attrs = frozenset([
    '____conn__', '____oid__', '__class__', '__cmp__', '__del__', '__delattr__',
    '__dir__', '__doc__', '__getattr__', '__getattribute__', '__hash__',
    '__init__', '__metaclass__', '__module__', '__new__', '__reduce__',
    '__reduce_ex__', '__repr__', '__setattr__', '__slots__', '__str__',
    '__weakref__', '__dict__', '__members__', '__methods__',
])

_builtin_types = [
    type, object, bool, complex, dict,
    float, int, list, slice, str, bytes, tuple,
    str, set, frozenset, Exception, type(None),
    types.BuiltinFunctionType, types.GeneratorType, types.MethodType,
    types.CodeType, types.FrameType, types.TracebackType, range,
    types.ModuleType, types.FunctionType,

    type(int.__add__), # wrapper_descriptor
    type((1).__add__), # method-wrapper
    type(iter([])), # listiterator
    type(iter(())), # tupleiterator
    type(iter(range(10))), # rangeiterator
    type(iter(set())), # setiterator
]

_normalized_builtin_types = dict(((t.__name__, t.__module__), t)
    for t in _builtin_types)

def syncreq(proxy, handler, *args):
    """performs a synchronous request on the given proxy object"""
    conn = object.__getattribute__(proxy, "____conn__")
    oid = object.__getattribute__(proxy, "____oid__")
    return conn().sync_request(handler, oid, *args)

def asyncreq(proxy, handler, *args):
    """performs an asynchronous request on the given proxy object,
    retuning an AsyncResult"""
    conn = object.__getattribute__(proxy, "____conn__")
    oid = object.__getattribute__(proxy, "____oid__")
    return conn().async_request(handler, oid, *args)

class NetrefMetaclass(type):
    """a metaclass just to customize the __repr__ of netref classes"""
    __slots__ = ()
    def __repr__(self):
        if self.__module__:
            return "<netref class '%s.%s'>" % (self.__module__, self.__name__)
        else:
            return "<netref class '%s'>" % (self.__name__,)

class BaseNetref(object, metaclass=NetrefMetaclass):
    """the base netref object, from which all netref classes derive"""
    __slots__ = ["____conn__", "____oid__", "__weakref__"]
    def __init__(self, conn, oid):
        self.____conn__ = conn
        self.____oid__ =  oid
    def __del__(self):
        try:
            asyncreq(self, consts.HANDLE_DEL)
        except:
            pass

    def __getattribute__(self, name):
        if name in _local_netref_attrs:
            if name == "__class__":
                cls = object.__getattribute__(self, "__class__")
                if cls is None:
                    cls = self.__getattr__("__class__")
                return cls
            elif name == "__doc__":
                return self.__getattr__("__doc__")
            elif name == "__members__": # sys.version_info < (2, 6)
                return self.__dir__()
            else:
                return object.__getattribute__(self, name)
        else:
            return syncreq(self, consts.HANDLE_GETATTR, name)
    def __getattr__(self, name):
        return syncreq(self, consts.HANDLE_GETATTR, name)
    def __delattr__(self, name):
        if name in _local_netref_attrs:
            object.__delattr__(self, name)
        else:
            syncreq(self, consts.HANDLE_DELATTR, name)
    def __setattr__(self, name, value):
        if name in _local_netref_attrs:
            object.__setattr__(self, name, value)
        else:
            syncreq(self, consts.HANDLE_SETATTR, name, value)
    def __dir__(self):
        return list(syncreq(self, consts.HANDLE_DIR))

    # support for metaclasses
    def __hash__(self):
        return syncreq(self, consts.HANDLE_HASH)
    def __cmp__(self, other):
        return syncreq(self, consts.HANDLE_CMP, other)
    def __repr__(self):
        return syncreq(self, consts.HANDLE_REPR)
    def __str__(self):
        return syncreq(self, consts.HANDLE_STR)
    # support for pickle
    def __reduce_ex__(self, proto):
        return pickle.loads, (syncreq(self, consts.HANDLE_PICKLE, proto),)

def _make_method(name, doc):
    if name == "__call__":
        def __call__(_self, *args, **kwargs):
            kwargs = tuple(kwargs.items())
            return syncreq(_self, consts.HANDLE_CALL, args, kwargs)
        __call__.__doc__ = doc
        return __call__
    else:
        def method(_self, *args, **kwargs):
            kwargs = tuple(kwargs.items())
            return syncreq(_self, consts.HANDLE_CALLATTR, name, args, kwargs)
        method.__name__ = name
        method.__doc__ = doc
        return method

def inspect_methods(obj):
    """returns a list of (method name, docstring) tuples of all the methods of
    the given object"""
    methods = {}
    attrs = {}
    if isinstance(obj, type):
        # don't forget the darn metaclass
        mros = list(reversed(type(obj).__mro__)) + list(reversed(obj.__mro__))
    else:
        mros = reversed(type(obj).__mro__)
    for basecls in mros:
        attrs.update(basecls.__dict__)
    for name, attr in attrs.items():
        if name not in _local_netref_attrs and hasattr(attr, "__call__"):
            methods[name] = inspect.getdoc(attr)
    return list(methods.items())

def class_factory(clsname, modname, methods):
    ns = {"__slots__" : ()}
    for name, doc in methods:
        if name not in _local_netref_attrs:
            ns[name] = _make_method(name, doc)
    ns["__module__"] = modname
    if modname in sys.modules and hasattr(sys.modules[modname], clsname):
        ns["__class__"] = getattr(sys.modules[modname], clsname)
    elif (clsname, modname) in _normalized_builtin_types:
        ns["__class__"] = _normalized_builtin_types[clsname, modname]
    else:
        ns["__class__"] = None # to be resolved by the instance
    return type(clsname, (BaseNetref,), ns)

builtin_classes_cache = {}
for cls in _builtin_types:
    builtin_classes_cache[cls.__name__, cls.__module__] = class_factory(
        cls.__name__, cls.__module__, inspect_methods(cls))


