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))
