import numpy as np
from skimage.morphology import convex_hull_image, convex_hull_object
from skimage.morphology._convex_hull import possible_hull

from skimage._shared import testing
from skimage._shared.testing import assert_array_equal
from skimage._shared._warnings import expected_warnings


def test_basic():
    image = np.array(
        [
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 1, 0, 0, 0, 0],
            [0, 0, 0, 1, 0, 1, 0, 0, 0],
            [0, 0, 1, 0, 0, 0, 1, 0, 0],
            [0, 1, 0, 0, 0, 0, 0, 1, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
        ],
        dtype=bool,
    )

    expected = np.array(
        [
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 1, 0, 0, 0, 0],
            [0, 0, 0, 1, 1, 1, 0, 0, 0],
            [0, 0, 1, 1, 1, 1, 1, 0, 0],
            [0, 1, 1, 1, 1, 1, 1, 1, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
        ],
        dtype=bool,
    )

    assert_array_equal(convex_hull_image(image), expected)


def test_empty_image():
    image = np.zeros((6, 6), dtype=bool)
    with expected_warnings(['entirely zero']):
        assert_array_equal(convex_hull_image(image), image)


def test_qhull_offset_example():
    nonzeros = (
        (
            [
                1367,
                1368,
                1368,
                1368,
                1369,
                1369,
                1369,
                1369,
                1369,
                1370,
                1370,
                1370,
                1370,
                1370,
                1370,
                1370,
                1371,
                1371,
                1371,
                1371,
                1371,
                1371,
                1371,
                1371,
                1371,
                1372,
                1372,
                1372,
                1372,
                1372,
                1372,
                1372,
                1372,
                1372,
                1373,
                1373,
                1373,
                1373,
                1373,
                1373,
                1373,
                1373,
                1373,
                1374,
                1374,
                1374,
                1374,
                1374,
                1374,
                1374,
                1375,
                1375,
                1375,
                1375,
                1375,
                1376,
                1376,
                1376,
                1377,
                1372,
            ]
        ),
        (
            [
                151,
                150,
                151,
                152,
                149,
                150,
                151,
                152,
                153,
                148,
                149,
                150,
                151,
                152,
                153,
                154,
                147,
                148,
                149,
                150,
                151,
                152,
                153,
                154,
                155,
                146,
                147,
                148,
                149,
                150,
                151,
                152,
                153,
                154,
                146,
                147,
                148,
                149,
                150,
                151,
                152,
                153,
                154,
                147,
                148,
                149,
                150,
                151,
                152,
                153,
                148,
                149,
                150,
                151,
                152,
                149,
                150,
                151,
                150,
                155,
            ]
        ),
    )
    image = np.zeros((1392, 1040), dtype=bool)
    image[nonzeros] = True
    expected = image.copy()
    assert_array_equal(convex_hull_image(image), expected)


def test_pathological_qhull_example():
    image = np.array(
        [[0, 0, 0, 0, 1, 0, 0], [0, 0, 1, 1, 1, 1, 1], [1, 1, 1, 0, 0, 0, 0]],
        dtype=bool,
    )
    expected = np.array(
        [[0, 0, 0, 1, 1, 1, 0], [0, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 0, 0]],
        dtype=bool,
    )
    assert_array_equal(convex_hull_image(image), expected)


def test_pathological_qhull_labels():
    image = np.array(
        [[0, 0, 0, 0, 1, 0, 0], [0, 0, 1, 1, 1, 1, 1], [1, 1, 1, 0, 0, 0, 0]],
        dtype=bool,
    )

    expected = np.array(
        [[0, 0, 0, 0, 1, 0, 0], [0, 0, 1, 1, 1, 1, 1], [1, 1, 1, 1, 0, 0, 0]],
        dtype=bool,
    )

    actual = convex_hull_image(image, include_borders=False)
    assert_array_equal(actual, expected)


def test_possible_hull():
    image = np.array(
        [
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 1, 0, 0, 0, 0],
            [0, 0, 0, 1, 0, 1, 0, 0, 0],
            [0, 0, 1, 1, 1, 1, 1, 0, 0],
            [0, 1, 1, 1, 1, 1, 1, 1, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
        ],
        dtype=np.uint8,
    )

    expected = np.array(
        [
            [1, 4],
            [2, 3],
            [3, 2],
            [4, 1],
            [4, 1],
            [3, 2],
            [2, 3],
            [1, 4],
            [2, 5],
            [3, 6],
            [4, 7],
            [2, 5],
            [3, 6],
            [4, 7],
            [4, 2],
            [4, 3],
            [4, 4],
            [4, 5],
            [4, 6],
        ]
    )

    ph = possible_hull(image)
    assert_array_equal(ph, expected)


def test_object():
    image = np.array(
        [
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
            [1, 0, 0, 0, 0, 0, 0, 0, 0],
            [1, 0, 0, 0, 0, 0, 0, 0, 0],
            [1, 0, 0, 0, 0, 0, 0, 0, 0],
            [1, 1, 1, 1, 0, 0, 1, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 1, 0],
            [1, 0, 0, 0, 0, 0, 1, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
        ],
        dtype=bool,
    )

    expected_conn_1 = np.array(
        [
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
            [1, 0, 0, 0, 0, 0, 0, 0, 0],
            [1, 1, 0, 0, 0, 0, 0, 0, 0],
            [1, 1, 1, 0, 0, 0, 0, 0, 0],
            [1, 1, 1, 1, 0, 0, 1, 0, 1],
            [1, 1, 1, 0, 0, 0, 0, 1, 0],
            [1, 1, 0, 0, 0, 0, 1, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
        ],
        dtype=bool,
    )

    assert_array_equal(convex_hull_object(image, connectivity=1), expected_conn_1)

    expected_conn_2 = np.array(
        [
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
            [1, 0, 0, 0, 0, 0, 0, 0, 0],
            [1, 1, 0, 0, 0, 0, 0, 0, 0],
            [1, 1, 1, 0, 0, 0, 0, 0, 0],
            [1, 1, 1, 1, 0, 0, 1, 1, 1],
            [1, 1, 1, 0, 0, 0, 1, 1, 1],
            [1, 1, 0, 0, 0, 0, 1, 1, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
        ],
        dtype=bool,
    )

    assert_array_equal(convex_hull_object(image, connectivity=2), expected_conn_2)

    with testing.raises(ValueError):
        convex_hull_object(image, connectivity=3)

    out = convex_hull_object(image, connectivity=1)
    assert_array_equal(out, expected_conn_1)


def test_non_c_contiguous():
    # 2D Fortran-contiguous
    image = np.ones((2, 2), order='F', dtype=bool)
    assert_array_equal(convex_hull_image(image), image)
    # 3D Fortran-contiguous
    image = np.ones((2, 2, 2), order='F', dtype=bool)
    assert_array_equal(convex_hull_image(image), image)
    # 3D non-contiguous
    image = np.transpose(np.ones((2, 2, 2), dtype=bool), [0, 2, 1])
    assert_array_equal(convex_hull_image(image), image)


@testing.fixture
def images2d3d():
    from ...measure.tests.test_regionprops import SAMPLE as image

    image3d = np.stack((image, image, image))
    return image, image3d


def test_consistent_2d_3d_hulls(images2d3d):
    image, image3d = images2d3d
    chimage = convex_hull_image(image)
    chimage[8, 0] = True  # correct for single point exactly on hull edge
    chimage3d = convex_hull_image(image3d)
    assert_array_equal(chimage3d[1], chimage)


def test_few_points():
    image = np.array(
        [
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 1, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 1, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
        ],
        dtype=np.uint8,
    )
    image3d = np.stack([image, image, image])
    with testing.assert_warns(UserWarning):
        chimage3d = convex_hull_image(image3d)
        assert_array_equal(chimage3d, np.zeros(image3d.shape, dtype=bool))
