# -*- coding: utf-8 -*-
from __future__ import print_function
import re
import time
import traceback
from itertools import chain
from functools import partial

from .cross import imap, basestring
from .decorators import decorator, wraps, Call


__all__ = [
    'tap',
    'log_calls', 'print_calls',
    'log_enters', 'print_enters',
    'log_exits', 'print_exits',
    'log_errors', 'print_errors',
    'log_durations', 'print_durations',
    'log_iter_durations', 'print_iter_durations',
]


def tap(x, label=None):
    if label:
        print('%s: %s' % (label, x))
    else:
        print(x)
    return x


@decorator
def log_calls(call, print_func, errors=True, stack=True):
    signature = signature_repr(call)
    try:
        print_func('Call %s' % signature)
        result = call()
        print_func('-> %s from %s' % (smart_repr(result, max_len=None), signature))
        return result
    except BaseException as e:
        if errors:
            print_func('-> ' + _format_error(signature, e, stack))
        raise

def print_calls(errors=True, stack=True):
    if callable(errors):
        return log_calls(print)(errors)
    else:
        return log_calls(print, errors, stack)


@decorator
def log_enters(call, print_func):
    print_func('Call %s' % signature_repr(call))
    return call()

print_enters = log_enters(print)


@decorator
def log_exits(call, print_func, errors=True, stack=True):
    signature = signature_repr(call)
    try:
        result = call()
        print_func('-> %s from %s' % (smart_repr(result, max_len=None), signature))
        return result
    except BaseException as e:
        if errors:
            print_func('-> ' + _format_error(signature, e, stack))
        raise

def print_exits(errors=True, stack=True):
    if callable(errors):
        return log_exits(print)(errors)
    else:
        return log_exits(print, errors, stack)


class LabeledContextDecorator(object):
    """
    A context manager which also works as decorator, passing call signature as its label.
    """
    def __init__(self, print_func, label=None):
        self.print_func = print_func
        self.label = label

    def __call__(self, label=None, **kwargs):
        if callable(label):
            return self.decorator(label)
        else:
            return self.__class__(self.print_func, label, **kwargs)

    def decorator(self, func):
        @wraps(func)
        def inner(*args, **kwargs):
            # Recreate self with a new label so that nested and recursive calls will work
            cm = self.__class__.__new__(self.__class__)
            cm.__dict__.update(self.__dict__)
            cm.label = signature_repr(Call(func, args, kwargs))
            with cm:
                return func(*args, **kwargs)
        return inner


class log_errors(LabeledContextDecorator):
    def __init__(self, print_func, label=None, stack=True):
        LabeledContextDecorator.__init__(self, print_func, label=label)
        self.stack = stack

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, tb):
        if exc_type:
            if self.stack:
                exc_message = ''.join(traceback.format_exception(exc_type, exc_value, tb))
            else:
                exc_message = '%s: %s' % (exc_type.__name__, exc_value)
            self.print_func(_format_error(self.label, exc_message, self.stack))

print_errors = log_errors(print)


class log_durations(LabeledContextDecorator):
    def __enter__(self):
        self.start = time.time()
        return self

    def __exit__(self, *exc):
        duration = format_time(time.time() - self.start)
        self.print_func("%s in %s" % (duration, self.label) if self.label else duration)

print_durations = log_durations(print)


def log_iter_durations(seq, print_func, label=None):
    suffix = " of %s" % label if label else ""
    it = iter(seq)
    for i, item in enumerate(it):
        start = time.time()
        yield item
        duration = format_time(time.time() - start)
        print_func("%s in iteration %d%s" % (duration, i, suffix))

def print_iter_durations(seq, label=None):
    return log_iter_durations(seq, print, label)


### Formatting utils

def _format_error(label, e, stack=True):
    if isinstance(e, Exception):
        if stack:
            e_message = traceback.format_exc()
        else:
            e_message = '%s: %s' % (e.__class__.__name__, e)
    else:
        e_message = e

    if label:
        template = '%s    raised in %s' if stack else '%s raised in %s'
        return template % (e_message, label)
    else:
        return e_message

def format_time(sec):
    if sec < 1e-6:
        return '%6.2f ns' % (sec * 1e9)
    elif sec < 1e-3:
        return '%6.2f mks' % (sec * 1e6)
    elif sec < 1:
        return '%6.2f ms' % (sec * 1e3)
    else:
        return '%6.2f s' % sec


### Call signature stringification utils

MAX_REPR_LEN = 25

def signature_repr(call):
    if isinstance(call._func, partial):
        if hasattr(call._func.func, '__name__'):
            name = '<%s partial>' % call._func.func.__name__
        else:
            name = '<unknown partial>'
    else:
        name = getattr(call._func, '__name__', '<unknown>')
    args_repr = imap(smart_repr, call._args)
    kwargs_repr = ('%s=%s' % (key, smart_repr(value)) for key, value in call._kwargs.items())
    return '%s(%s)' % (name, ', '.join(chain(args_repr, kwargs_repr)))

def smart_repr(value, max_len=MAX_REPR_LEN):
    if isinstance(value, basestring):
        res = repr(value)
    else:
        res = str(value)

    res = re.sub(r'\s+', ' ', res)
    if max_len and len(res) > max_len:
        res = res[:max_len-3] + '...'
    return res
