import functools


def with_wraps(decorator=None, arguments=False):
    """Wrap another decorator and set _wraps on decorated functions.

    This is used to programatically find the root of a chain of decorators.
    Doctor needs to know this so it can introspect the *real* function and
    figure out its arguments. :func:`~with_wraps` is used to modify existing
    decorators to ensure they set the _wraps attribute that Doctor looks for
    on decorated functions.

    :param callable decorator: Decorator to wrap.
    :param bool arguments: If True, will expect to wrap a decorator which in
        turn takes arguments and returns another decorator.
    """
    if decorator is None and arguments:
        # Called as with_wraps(arguments=True), which means we need to return
        # a version of with_wraps which will call itself with arguments=True
        # to decorate another function (which in turn accepts arguments and
        # returns a decorator).
        @functools.wraps(with_wraps)
        def _with_wraps_decorator(decorator):
            return with_wraps(decorator, arguments=True)
        return _with_wraps_decorator
    elif decorator is None:
        # Someone called this as with_wraps(), probably on accident.
        raise TypeError('decorator is None, must be a callable. Please use '
                        '@with_wraps or @with_wraps(arguments=True).')

    @functools.wraps(decorator)
    def _decorator_wrapper(*args, **kwargs):
        if arguments:
            generated_decorator = decorator(*args, **kwargs)
            return with_wraps(generated_decorator)
        else:
            if not args:
                raise TypeError(
                    'Decorator for {!r} called without arguments. This '
                    'usually means you wrote @with_wraps but should have '
                    'written @with_wraps(arguments=True).'.format(decorator))
            func = args[0]
            if not callable(func):
                raise TypeError('first argument to decorator must be callable')
            wrapped_func = decorator(func)
            wrapped_func._wraps = func
            return wrapped_func
    return _decorator_wrapper