import sys
import pytest
import numpy as np
import numpy.testing as npt
from skimage._shared.utils import (check_nD, deprecate_kwarg,
                                   _validate_interpolation_order,
                                   change_default_value)
from skimage._shared import testing
from skimage._shared._warnings import expected_warnings


def test_change_default_value():

    @change_default_value('arg1', new_value=-1, changed_version='0.12')
    def foo(arg0, arg1=0, arg2=1):
        """Expected docstring"""
        return arg0, arg1, arg2

    @change_default_value('arg1', new_value=-1, changed_version='0.12',
                          warning_msg="Custom warning message")
    def bar(arg0, arg1=0, arg2=1):
        """Expected docstring"""
        return arg0, arg1, arg2

    # Assert warning messages
    with pytest.warns(FutureWarning) as record:
        assert foo(0) == (0, 0, 1)
        assert bar(0) == (0, 0, 1)

    expected_msg = ("The new recommended value for arg1 is -1. Until "
                    "version 0.12, the default arg1 value is 0. From "
                    "version 0.12, the arg1 default value will be -1. "
                    "To avoid this warning, please explicitly set arg1 value.")

    assert str(record[0].message) == expected_msg
    assert str(record[1].message) == "Custom warning message"

    # Assert that nothing happens if arg1 is set
    with pytest.warns(None) as record:
        # No kwargs
        assert foo(0, 2) == (0, 2, 1)
        assert foo(0, arg1=0) == (0, 0, 1)

        # Function name and doc is preserved
        assert foo.__name__ == 'foo'
        if sys.flags.optimize < 2:
            # if PYTHONOPTIMIZE is set to 2, docstrings are stripped
            assert foo.__doc__ == 'Expected docstring'

    # Assert no warning was raised
    assert not record.list


def test_deprecated_kwarg():

    @deprecate_kwarg({'old_arg1': 'new_arg1'})
    def foo(arg0, new_arg1=1, arg2=None):
        """Expected docstring"""
        return arg0, new_arg1, arg2

    @deprecate_kwarg({'old_arg1': 'new_arg1'},
                     warning_msg="Custom warning message")
    def bar(arg0, new_arg1=1, arg2=None):
        """Expected docstring"""
        return arg0, new_arg1, arg2

    # Assert that the DeprecationWarning is raised when the deprecated
    # argument name is used and that the reasult is valid
    with pytest.warns(FutureWarning) as record:
        assert foo(0, old_arg1=1) == (0, 1, None)
        assert bar(0, old_arg1=1) == (0, 1, None)

    msg = ("'old_arg1' is a deprecated argument name "
           "for `foo`. Please use 'new_arg1' instead.")
    assert str(record[0].message) == msg
    assert str(record[1].message) == "Custom warning message"

    # Assert that nothing happens when the function is called with the
    # new API
    with pytest.warns(None) as record:
        # No kwargs
        assert foo(0) == (0, 1, None)
        assert foo(0, 2) == (0, 2, None)
        assert foo(0, 1, 2) == (0, 1, 2)
        # Kwargs without deprecated argument
        assert foo(0, new_arg1=1, arg2=2) == (0, 1, 2)
        assert foo(0, new_arg1=2) == (0, 2, None)
        assert foo(0, arg2=2) == (0, 1, 2)
        assert foo(0, 1, arg2=2) == (0, 1, 2)
        # Function name and doc is preserved
        assert foo.__name__ == 'foo'
        if sys.flags.optimize < 2:
            # if PYTHONOPTIMIZE is set to 2, docstrings are stripped
            assert foo.__doc__ == 'Expected docstring'

    # Assert no warning was raised
    assert not record.list


def test_check_nD():
    z = np.random.random(200**2).reshape((200, 200))
    x = z[10:30, 30:10]
    with testing.raises(ValueError):
        check_nD(x, 2)


@pytest.mark.parametrize('dtype', [bool, int, np.uint8, np.uint16,
                                   float, np.float32, np.float64])
@pytest.mark.parametrize('order', [None, -1, 0, 1, 2, 3, 4, 5, 6])
def test_validate_interpolation_order(dtype, order):
    if order is None:
        # Default order
        assert (_validate_interpolation_order(dtype, None) == 0
                if dtype == bool else 1)
    elif order < 0 or order > 5:
        # Order not in valid range
        with testing.raises(ValueError):
            _validate_interpolation_order(dtype, order)
    elif dtype == bool and order != 0:
        # Deprecated order for bool array
        with expected_warnings(["Input image dtype is bool"]):
            assert _validate_interpolation_order(bool, order) == order
    else:
        # Valid use case
        assert _validate_interpolation_order(dtype, order) == order


if __name__ == "__main__":
    npt.run_module_suite()