"""Dependency injector injections module."""

import six

from dependency_injector.providers.base import (
    _parse_positional_injections,
    _parse_keyword_injections,
)
from dependency_injector import utils
from dependency_injector import errors


def inject(*args, **kwargs):
    """Dependency injection decorator.

    :py:func:`inject` decorator can be used for making inline dependency
    injections. It patches decorated callable in such way that dependency
    injection will be done during every call of decorated callable.

    :py:func:`inject` decorator supports different syntaxes of passing
    injections:

    .. code-block:: python

        # Positional arguments injections:
        @inject(1, 2)
        def some_function(arg1, arg2):
            pass

        # Keyword arguments injections:
        @inject(arg1=1)
        @inject(arg2=2)
        def some_function(arg1, arg2):
            pass

        # Keyword arguments injections into class init:
        @inject(arg1=1)
        @inject(arg2=2)
        class SomeClass(object):

            def __init__(self, arg1, arg2):
                pass

    :param args: Tuple of context positional arguments.
    :type args: tuple[object]

    :param kwargs: Dictionary of context keyword arguments.
    :type kwargs: dict[str, object]

    :return: Class / callable decorator
    :rtype: (callable) -> (type | callable)
    """
    arg_injections = _parse_positional_injections(args)
    kwarg_injections = _parse_keyword_injections(kwargs)

    def decorator(callback_or_cls):
        """Dependency injection decorator."""
        if isinstance(callback_or_cls, six.class_types):
            cls = callback_or_cls
            cls_init = utils.fetch_cls_init(cls)
            if not cls_init:
                raise errors.Error(
                    'Class {0}.{1} has no __init__() '.format(cls.__module__,
                                                              cls.__name__) +
                    'method and could not be decorated with @inject decorator')
            cls.__init__ = decorator(cls_init)
            return cls

        callback = callback_or_cls

        if hasattr(callback, '__INJECT_DECORATED__'):
            callback.args += arg_injections
            callback.kwargs.update(kwarg_injections)
            return callback

        @six.wraps(callback)
        def decorated(*args, **kwargs):
            """Decorated with dependency injection callback."""
            if decorated.args:
                args = tuple(arg.provide_injection()
                             for arg in decorated.args) + args

            for name, arg in six.iteritems(decorated.kwargs):
                if name not in kwargs:
                    kwargs[name] = arg.provide_injection()

            return callback(*args, **kwargs)

        decorated.__INJECT_DECORATED__ = True
        decorated.origin = callback
        decorated.args = arg_injections
        decorated.kwargs = kwarg_injections

        return decorated
    return decorator
