import functools
import sys
import types
import time
import threading

_context = threading.local()

class InternalTrace(object):

    def __init__(self, name, metrics=None):
        self.name = name
        self.metrics = metrics
        self.start = 0.0

    def __enter__(self):
        if self.metrics is None:
            self.metrics = getattr(_context, 'current', None)
        self.start = time.time()
        return self

    def __exit__(self, exc, value, tb):
        duration = max(self.start, time.time()) - self.start
        if self.metrics is not None:
            self.metrics.record_custom_metric(self.name, duration)

class xInternalTraceWrapper(object):

    def __init__(self, wrapped, name=None):
        self.wrapped = wrapped
        self.name = name

    def execute(self, wrapped, *args, **kwargs):
        metrics = getattr(_context, 'current', None)

        if metrics is None:
            return wrapped(*args, **kwargs)

        with InternalTrace(self.name, metrics):
            return wrapped(*args, **kwargs)

    def __get__(self, instance, klass):
        if instance is None:
            return self

        def wrapper(*args, **kwargs):
            descriptor = self.wrapped.__get__(instance, klass)
            return self.execute(descriptor, *args, **kwargs)

        return wrapper

    def __call__(self, *args, **kwargs):
        return self.execute(self.wrapped, *args, **kwargs)

class InternalTraceWrapper(object):

    def __init__(self, wrapped, name):
        if type(wrapped) == type(()):
            (instance, wrapped) = wrapped
        else:
            instance = None
        self.__instance = instance
        self.__wrapped = wrapped
        self.__name = name

    def __getattr__(self, name):
        return getattr(self.__wrapped, name)

    def __get__(self, instance, klass):
        if instance is None:
            return self
        descriptor = self.__wrapped.__get__(instance, klass)
        return self.__class__((instance, descriptor), self.__name)

    def __call__(self, *args, **kwargs):
        metrics = getattr(_context, 'current', None)

        if metrics is None:
            return self.__wrapped(*args, **kwargs)

        with InternalTrace(self.__name, metrics):
            return self.__wrapped(*args, **kwargs)

class InternalTraceContext(object):

    def __init__(self, metrics):
        self.previous = None
        self.metrics = metrics

    def __enter__(self):
        self.previous = getattr(_context, 'current', None)
        _context.current = self.metrics
        return self

    def __exit__(self, exc, value, tb):
        if self.previous is not None:
            _context.current = self.previous

def internal_trace(name=None):
    def decorator(wrapped):
        return InternalTraceWrapper(wrapped, name)
    return decorator

def wrap_internal_trace(module, object_path, name=None):
    newrelic.api.object_wrapper.wrap_object(module, object_path,
            InternalTraceWrapper, (name,))

def internal_metric(name, value):
    metrics = getattr(_context, 'current', None)
    if metrics is not None:
        metrics.record_custom_metric(name, value)
