import chainer
import warnings


def experimental(api_name):
    """Declares that user is using an experimental feature.

    The developer of an API can mark it as *experimental* by calling
    this function. When users call experimental APIs, :class:`FutureWarning`
    is issued.
    The presentation of :class:`FutureWarning` is disabled by setting
    ``chainer.disable_experimental_warning`` to ``True``,
    which is ``False`` by default.

    The basic usage is to call it in the function or method we want to
    mark as experimental along with the API name.

    .. testsetup::

        import sys
        import warnings

        warnings.simplefilter('always')

        def wrapper(message, category, filename, lineno, file=None, line=None):
            sys.stdout.write(warnings.formatwarning(
                message, category, filename, lineno))

        showwarning_orig = warnings.showwarning
        warnings.showwarning = wrapper

    .. testcleanup::

        warnings.showwarning = showwarning_orig

    .. testcode::
        :options: +ELLIPSIS, +NORMALIZE_WHITESPACE

        from chainer.utils import experimental

        def f(x):
            experimental('chainer.foo.bar.f')
            # concrete implementation of f follows

        f(1)

    .. testoutput::

        ... FutureWarning: chainer.experimental.f is an experimental API. \
The interface can change in the future.

    We can also make a whole class experimental. In that case,
    we should call this function in its ``__init__`` method.

    .. testcode::
        :options: +ELLIPSIS, +NORMALIZE_WHITESPACE

        class C():
            def __init__(self):
              experimental('chainer.foo.C')

        C()

    .. testoutput::

        ... FutureWarning: chainer.foo.C is an experimental API. \
The interface can change in the future

    If we want to mark ``__init__`` method only, rather than class itself,
    it is recommended that we explicitly feed its API name.

    .. testcode::
        :options: +ELLIPSIS, +NORMALIZE_WHITESPACE

        class D():
            def __init__(self):
                experimental('D.__init__')

        D()

    .. testoutput::

       ...  FutureWarning: D.__init__ is an experimental API. \
The interface can change in the future

    Currently, we do not have any sophisticated way to mark some usage of
    non-experimental function as experimental.
    But we can support such usage by explicitly branching it.

    .. testcode::

        def g(x, experimental_arg=None):
            if experimental_arg is not None:
                experimental('experimental_arg of chainer.foo.g')

    Args:
        api_name(str): The name of an API marked as experimental.
    """

    if not chainer.disable_experimental_feature_warning:
        warnings.warn('{} is experimental. '
                      'The interface can change in the future.'.format(
                          api_name),
                      FutureWarning)
