# -*- coding: utf-8 -*-
"""
    sphinx.ext.autodoc.inspector
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    Inspect utilities for autodoc

    :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS.
    :license: BSD, see LICENSE for details.
"""

import typing
import warnings
from typing import TYPE_CHECKING

from six import StringIO, string_types

from sphinx.deprecation import RemovedInSphinx20Warning
from sphinx.util.inspect import object_description

if TYPE_CHECKING:
    from typing import Any, Callable, Dict, Tuple  # NOQA


def format_annotation(annotation):
    # type: (Any) -> str
    """Return formatted representation of a type annotation.

    Show qualified names for types and additional details for types from
    the ``typing`` module.

    Displaying complex types from ``typing`` relies on its private API.
    """
    warnings.warn('format_annotation() is now deprecated.  '
                  'Please use sphinx.util.inspect.Signature instead.',
                  RemovedInSphinx20Warning)
    if isinstance(annotation, typing.TypeVar):  # type: ignore
        return annotation.__name__
    if annotation == Ellipsis:
        return '...'
    if not isinstance(annotation, type):
        return repr(annotation)

    qualified_name = (annotation.__module__ + '.' + annotation.__qualname__  # type: ignore
                      if annotation else repr(annotation))

    if annotation.__module__ == 'builtins':
        return annotation.__qualname__  # type: ignore
    else:
        if hasattr(typing, 'GenericMeta') and \
                isinstance(annotation, typing.GenericMeta):
            # In Python 3.5.2+, all arguments are stored in __args__,
            # whereas __parameters__ only contains generic parameters.
            #
            # Prior to Python 3.5.2, __args__ is not available, and all
            # arguments are in __parameters__.
            params = None
            if hasattr(annotation, '__args__'):
                if annotation.__args__ is None or len(annotation.__args__) <= 2:  # type: ignore  # NOQA
                    params = annotation.__args__  # type: ignore
                else:  # typing.Callable
                    args = ', '.join(format_annotation(a) for a in annotation.__args__[:-1])  # type: ignore  # NOQA
                    result = format_annotation(annotation.__args__[-1])  # type: ignore
                    return '%s[[%s], %s]' % (qualified_name, args, result)
            elif hasattr(annotation, '__parameters__'):
                params = annotation.__parameters__  # type: ignore
            if params is not None:
                param_str = ', '.join(format_annotation(p) for p in params)
                return '%s[%s]' % (qualified_name, param_str)
        elif (hasattr(typing, 'UnionMeta') and
              isinstance(annotation, typing.UnionMeta) and  # type: ignore
              hasattr(annotation, '__union_params__')):
            params = annotation.__union_params__
            if params is not None:
                param_str = ', '.join(format_annotation(p) for p in params)
                return '%s[%s]' % (qualified_name, param_str)
        elif (hasattr(typing, 'CallableMeta') and
              isinstance(annotation, typing.CallableMeta) and  # type: ignore
              getattr(annotation, '__args__', None) is not None and
              hasattr(annotation, '__result__')):
            # Skipped in the case of plain typing.Callable
            args = annotation.__args__
            if args is None:
                return qualified_name
            elif args is Ellipsis:
                args_str = '...'
            else:
                formatted_args = (format_annotation(a) for a in args)
                args_str = '[%s]' % ', '.join(formatted_args)
            return '%s[%s, %s]' % (qualified_name,
                                   args_str,
                                   format_annotation(annotation.__result__))
        elif (hasattr(typing, 'TupleMeta') and
              isinstance(annotation, typing.TupleMeta) and  # type: ignore
              hasattr(annotation, '__tuple_params__') and
              hasattr(annotation, '__tuple_use_ellipsis__')):
            params = annotation.__tuple_params__
            if params is not None:
                param_strings = [format_annotation(p) for p in params]
                if annotation.__tuple_use_ellipsis__:
                    param_strings.append('...')
                return '%s[%s]' % (qualified_name,
                                   ', '.join(param_strings))
    return qualified_name


def formatargspec(function, args, varargs=None, varkw=None, defaults=None,
                  kwonlyargs=(), kwonlydefaults={}, annotations={}):
    # type: (Callable, Tuple[str, ...], str, str, Any, Tuple, Dict, Dict[str, Any]) -> str
    """Return a string representation of an ``inspect.FullArgSpec`` tuple.

    An enhanced version of ``inspect.formatargspec()`` that handles typing
    annotations better.
    """
    warnings.warn('formatargspec() is now deprecated.  '
                  'Please use sphinx.util.inspect.Signature instead.',
                  RemovedInSphinx20Warning)

    def format_arg_with_annotation(name):
        # type: (str) -> str
        if name in annotations:
            return '%s: %s' % (name, format_annotation(get_annotation(name)))
        return name

    def get_annotation(name):
        # type: (str) -> str
        value = annotations[name]
        if isinstance(value, string_types):
            return introspected_hints.get(name, value)
        else:
            return value

    introspected_hints = (typing.get_type_hints(function)  # type: ignore
                          if typing and hasattr(function, '__code__') else {})

    fd = StringIO()
    fd.write('(')

    formatted = []
    defaults_start = len(args) - len(defaults) if defaults else len(args)

    for i, arg in enumerate(args):
        arg_fd = StringIO()
        if isinstance(arg, list):
            # support tupled arguments list (only for py2): def foo((x, y))
            arg_fd.write('(')
            arg_fd.write(format_arg_with_annotation(arg[0]))
            for param in arg[1:]:
                arg_fd.write(', ')
                arg_fd.write(format_arg_with_annotation(param))
            arg_fd.write(')')
        else:
            arg_fd.write(format_arg_with_annotation(arg))
            if defaults and i >= defaults_start:
                arg_fd.write(' = ' if arg in annotations else '=')
                arg_fd.write(object_description(defaults[i - defaults_start]))  # type: ignore
        formatted.append(arg_fd.getvalue())

    if varargs:
        formatted.append('*' + format_arg_with_annotation(varargs))

    if kwonlyargs:
        if not varargs:
            formatted.append('*')

        for kwarg in kwonlyargs:
            arg_fd = StringIO()
            arg_fd.write(format_arg_with_annotation(kwarg))
            if kwonlydefaults and kwarg in kwonlydefaults:
                arg_fd.write(' = ' if kwarg in annotations else '=')
                arg_fd.write(object_description(kwonlydefaults[kwarg]))  # type: ignore
            formatted.append(arg_fd.getvalue())

    if varkw:
        formatted.append('**' + format_arg_with_annotation(varkw))

    fd.write(', '.join(formatted))
    fd.write(')')

    if 'return' in annotations:
        fd.write(' -> ')
        fd.write(format_annotation(get_annotation('return')))

    return fd.getvalue()
