from __future__ import print_function

import logging
import os
import platform
import re
import sys
from collections import deque
from functools import wraps
from inspect import isclass

RegexType = type(re.compile(""))

PY3 = sys.version_info[0] == 3
PY37plus = PY3 and sys.version_info[1] >= 7
PY2 = sys.version_info[0] == 2
PY26 = PY2 and sys.version_info[1] == 6
PYPY = platform.python_implementation() == 'PyPy'

if PY3:
    basestring = str
else:
    basestring = str, unicode  # noqa

FIRST_CAP_RE = re.compile('(.)([A-Z][a-z]+)')
ALL_CAP_RE = re.compile('([a-z0-9])([A-Z])')

DEBUG = os.getenv('ASPECTLIB_DEBUG')


def logf(logger_func):
    @wraps(logger_func)
    def log_wrapper(*args):
        if DEBUG:
            logProcesses = logging.logProcesses
            logThreads = logging.logThreads
            logMultiprocessing = logging.logMultiprocessing
            logging.logThreads = logging.logProcesses = logMultiprocessing = False
            # disable logging pids and tids - we don't want extra calls around, especilly when we monkeypatch stuff
            try:
                return logger_func(*args)
            finally:
                logging.logProcesses = logProcesses
                logging.logThreads = logThreads
                logging.logMultiprocessing = logMultiprocessing
    return log_wrapper


def camelcase_to_underscores(name):
    s1 = FIRST_CAP_RE.sub(r'\1_\2', name)
    return ALL_CAP_RE.sub(r'\1_\2', s1).lower()


def qualname(obj):
    if hasattr(obj, '__module__') and obj.__module__ not in ('builtins', 'exceptions'):
        return '%s.%s' % (obj.__module__, obj.__name__)
    else:
        return obj.__name__


def force_bind(func):
    def bound(self, *args, **kwargs):  # pylint: disable=W0613
        return func(*args, **kwargs)
    bound.__name__ = func.__name__
    bound.__doc__ = func.__doc__
    return bound


def make_method_matcher(regex_or_regexstr_or_namelist):
    if isinstance(regex_or_regexstr_or_namelist, basestring):
        return re.compile(regex_or_regexstr_or_namelist).match
    elif isinstance(regex_or_regexstr_or_namelist, (list, tuple)):
        return regex_or_regexstr_or_namelist.__contains__
    elif isinstance(regex_or_regexstr_or_namelist, RegexType):
        return regex_or_regexstr_or_namelist.match
    else:
        raise TypeError("Unacceptable methods spec %r." % regex_or_regexstr_or_namelist)


class Sentinel(object):
    def __init__(self, name, doc=''):
        self.name = name
        self.__doc__ = doc

    def __repr__(self):
        if not self.__doc__:
            return "%s" % self.name
        else:
            return "%s: %s" % (self.name, self.__doc__)
    __str__ = __repr__


def container(name):
    def __init__(self, value):
        self.value = value

    return type(name, (object,), {
        '__slots__': 'value',
        '__init__': __init__,
        '__str__': lambda self: "%s(%s)" % (name, self.value),
        '__repr__': lambda self: "%s(%r)" % (name, self.value),
        '__eq__': lambda self, other: type(self) is type(other) and self.value == other.value,
    })


def mimic(wrapper, func, module=None):
    try:
        wrapper.__name__ = func.__name__
    except (TypeError, AttributeError):
        pass
    try:
        wrapper.__module__ = module or func.__module__
    except (TypeError, AttributeError):
        pass
    try:
        wrapper.__doc__ = func.__doc__
    except (TypeError, AttributeError):
        pass
    return wrapper


representers = {
    tuple: lambda obj, aliases: "(%s%s)" % (', '.join(repr_ex(i) for i in obj), ',' if len(obj) == 1 else ''),
    list: lambda obj, aliases: "[%s]" % ', '.join(repr_ex(i) for i in obj),
    set: lambda obj, aliases: "set([%s])" % ', '.join(repr_ex(i) for i in obj),
    frozenset: lambda obj, aliases: "set([%s])" % ', '.join(repr_ex(i) for i in obj),
    deque: lambda obj, aliases: "collections.deque([%s])" % ', '.join(repr_ex(i) for i in obj),
    dict: lambda obj, aliases: "{%s}" % ', '.join(
        "%s: %s" % (repr_ex(k), repr_ex(v)) for k, v in (obj.items() if PY3 else obj.iteritems())
    ),
}


def _make_fixups():
    for obj in ('os.stat_result', 'grp.struct_group', 'pwd.struct_passwd'):
        mod, attr = obj.split('.')
        try:
            yield getattr(__import__(mod), attr), lambda obj, aliases, prefix=obj: "%s(%r)" % (
                prefix,
                obj.__reduce__()[1][0]
            )
        except ImportError:
            continue


representers.update(_make_fixups())


def repr_ex(obj, aliases=()):
    kind, ident = type(obj), id(obj)
    if isinstance(kind, BaseException):
        return "%s(%s)" % (qualname(type(obj)), ', '.join(repr_ex(i, aliases) for i in obj.args))
    elif isclass(obj):
        return qualname(obj)
    elif kind in representers:
        return representers[kind](obj, aliases)
    elif ident in aliases:
        return aliases[ident][0]
    else:
        return repr(obj)
