import numpy as np
from numpy.testing import assert_raises

from skimage.draw import ellipsoid, ellipsoid_stats
from skimage.measure import (marching_cubes, mesh_surface_area,
                             correct_mesh_orientation)


def test_marching_cubes_isotropic():
    ellipsoid_isotropic = ellipsoid(6, 10, 16, levelset=True)
    _, surf = ellipsoid_stats(6, 10, 16)
    verts, faces = marching_cubes(ellipsoid_isotropic, 0.)
    surf_calc = mesh_surface_area(verts, faces)

    # Test within 1% tolerance for isotropic. Will always underestimate.
    assert surf > surf_calc and surf_calc > surf * 0.99


def test_marching_cubes_anisotropic():
    spacing = (1., 10 / 6., 16 / 6.)
    ellipsoid_anisotropic = ellipsoid(6, 10, 16, spacing=spacing,
                                      levelset=True)
    _, surf = ellipsoid_stats(6, 10, 16)
    verts, faces = marching_cubes(ellipsoid_anisotropic, 0.,
                                  spacing=spacing)
    surf_calc = mesh_surface_area(verts, faces)

    # Test within 1.5% tolerance for anisotropic. Will always underestimate.
    assert surf > surf_calc and surf_calc > surf * 0.985


def test_invalid_input():
    assert_raises(ValueError, marching_cubes, np.zeros((2, 2, 1)), 0)
    assert_raises(ValueError, marching_cubes, np.zeros((2, 2, 1)), 1)
    assert_raises(ValueError, marching_cubes, np.ones((3, 3, 3)), 1,
                  spacing=(1, 2))
    assert_raises(ValueError, marching_cubes, np.zeros((20, 20)), 0)


def test_correct_mesh_orientation():
    sphere_small = ellipsoid(1, 1, 1, levelset=True)

    # Mesh with incorrectly oriented faces which was previously returned from
    # `marching_cubes`, before it guaranteed correct mesh orientation
    verts = np.array([[1., 2., 2.],
                      [2., 2., 1.],
                      [2., 1., 2.],
                      [2., 2., 3.],
                      [2., 3., 2.],
                      [3., 2., 2.]])

    faces = np.array([[0, 1, 2],
                      [2, 0, 3],
                      [1, 0, 4],
                      [4, 0, 3],
                      [1, 2, 5],
                      [2, 3, 5],
                      [1, 4, 5],
                      [5, 4, 3]])

    # Correct mesh orientation - descent
    corrected_faces1 = correct_mesh_orientation(sphere_small, verts, faces,
                                                gradient_direction='descent')
    corrected_faces2 = correct_mesh_orientation(sphere_small, verts, faces,
                                                gradient_direction='ascent')

    # Ensure ascent is opposite of descent for all faces
    np.testing.assert_array_equal(corrected_faces1, corrected_faces2[:, ::-1])

    # Ensure correct faces have been reversed: 1, 4, and 5
    idx = [1, 4, 5]
    expected = faces.copy()
    expected[idx] = expected[idx, ::-1]
    np.testing.assert_array_equal(expected, corrected_faces1)


if __name__ == '__main__':
    np.testing.run_module_suite()
