"""Defines the unit tests for the :mod:`colour.algebra.geometry` module."""

import numpy as np
import unittest
from itertools import permutations

from colour.algebra import (
    normalise_vector,
    euclidean_distance,
    manhattan_distance,
    extend_line_segment,
    intersect_line_segments,
    ellipse_coefficients_general_form,
    ellipse_coefficients_canonical_form,
    point_at_angle_on_ellipse,
    ellipse_fitting_Halir1998,
)
from colour.utilities import ignore_numpy_errors

__author__ = "Colour Developers"
__copyright__ = "Copyright 2013 Colour Developers"
__license__ = "New BSD License - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"

__all__ = [
    "TestNormaliseVector",
    "TestEuclideanDistance",
    "TestManhattanDistance",
    "TestExtendLineSegment",
    "TestIntersectLineSegments",
    "TestEllipseCoefficientsCanonicalForm",
    "TestEllipseCoefficientsGeneralForm",
    "TestPointAtAngleOnEllipse",
    "TestEllipseFittingHalir1998",
]


class TestNormaliseVector(unittest.TestCase):
    """
    Define :func:`colour.algebra.geometry.normalise_vector` definition unit
    tests methods.
    """

    def test_normalise_vector(self):
        """Test :func:`colour.algebra.geometry.normalise_vector` definition."""

        np.testing.assert_almost_equal(
            normalise_vector(np.array([0.20654008, 0.12197225, 0.05136952])),
            np.array([0.84197033, 0.49722560, 0.20941026]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            normalise_vector(np.array([0.14222010, 0.23042768, 0.10495772])),
            np.array([0.48971705, 0.79344877, 0.36140872]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            normalise_vector(np.array([0.07818780, 0.06157201, 0.28099326])),
            np.array([0.26229003, 0.20655044, 0.94262445]),
            decimal=7,
        )


class TestEuclideanDistance(unittest.TestCase):
    """
    Define :func:`colour.algebra.geometry.euclidean_distance` definition unit
    tests methods.
    """

    def test_euclidean_distance(self):
        """Test :func:`colour.algebra.geometry.euclidean_distance` definition."""

        self.assertAlmostEqual(
            euclidean_distance(
                np.array([100.00000000, 21.57210357, 272.22819350]),
                np.array([100.00000000, 426.67945353, 72.39590835]),
            ),
            451.71330197,
            places=7,
        )

        self.assertAlmostEqual(
            euclidean_distance(
                np.array([100.00000000, 21.57210357, 272.22819350]),
                np.array([100.00000000, 74.05216981, 276.45318193]),
            ),
            52.64986116,
            places=7,
        )

        self.assertAlmostEqual(
            euclidean_distance(
                np.array([100.00000000, 21.57210357, 272.22819350]),
                np.array([100.00000000, 8.32281957, -73.58297716]),
            ),
            346.06489172,
            places=7,
        )

    def test_n_dimensional_euclidean_distance(self):
        """
        Test :func:`colour.algebra.geometry.euclidean_distance` definition
        n-dimensional arrays support.
        """

        a = np.array([100.00000000, 21.57210357, 272.22819350])
        b = np.array([100.00000000, 426.67945353, 72.39590835])
        distance = euclidean_distance(a, b)

        a = np.tile(a, (6, 1))
        b = np.tile(b, (6, 1))
        distance = np.tile(distance, 6)
        np.testing.assert_almost_equal(
            euclidean_distance(a, b), distance, decimal=7
        )

        a = np.reshape(a, (2, 3, 3))
        b = np.reshape(b, (2, 3, 3))
        distance = np.reshape(distance, (2, 3))
        np.testing.assert_almost_equal(
            euclidean_distance(a, b), distance, decimal=7
        )

    @ignore_numpy_errors
    def test_nan_euclidean_distance(self):
        """
        Test :func:`colour.algebra.geometry.euclidean_distance` definition nan
        support.
        """

        cases = [-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]
        cases = set(permutations(cases * 3, r=3))
        for case in cases:
            a = np.array(case)
            b = np.array(case)
            euclidean_distance(a, b)


class TestManhattanDistance(unittest.TestCase):
    """
    Define :func:`colour.algebra.geometry.manhattan_distance` definition unit
    tests methods.
    """

    def test_manhattan_distance(self):
        """Test :func:`colour.algebra.geometry.manhattan_distance` definition."""

        self.assertAlmostEqual(
            manhattan_distance(
                np.array([100.00000000, 21.57210357, 272.22819350]),
                np.array([100.00000000, 426.67945353, 72.39590835]),
            ),
            604.93963510999993,
            places=7,
        )

        self.assertAlmostEqual(
            manhattan_distance(
                np.array([100.00000000, 21.57210357, 272.22819350]),
                np.array([100.00000000, 74.05216981, 276.45318193]),
            ),
            56.705054670000052,
            places=7,
        )

        self.assertAlmostEqual(
            manhattan_distance(
                np.array([100.00000000, 21.57210357, 272.22819350]),
                np.array([100.00000000, 8.32281957, -73.58297716]),
            ),
            359.06045465999995,
            places=7,
        )

    def test_n_dimensional_manhattan_distance(self):
        """
        Test :func:`colour.algebra.geometry.manhattan_distance` definition
        n-dimensional arrays support.
        """

        a = np.array([100.00000000, 21.57210357, 272.22819350])
        b = np.array([100.00000000, 426.67945353, 72.39590835])
        distance = manhattan_distance(a, b)

        a = np.tile(a, (6, 1))
        b = np.tile(b, (6, 1))
        distance = np.tile(distance, 6)
        np.testing.assert_almost_equal(
            manhattan_distance(a, b), distance, decimal=7
        )

        a = np.reshape(a, (2, 3, 3))
        b = np.reshape(b, (2, 3, 3))
        distance = np.reshape(distance, (2, 3))
        np.testing.assert_almost_equal(
            manhattan_distance(a, b), distance, decimal=7
        )

    @ignore_numpy_errors
    def test_nan_manhattan_distance(self):
        """
        Test :func:`colour.algebra.geometry.manhattan_distance` definition nan
        support.
        """

        cases = [-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]
        cases = set(permutations(cases * 3, r=3))
        for case in cases:
            a = np.array(case)
            b = np.array(case)
            manhattan_distance(a, b)


class TestExtendLineSegment(unittest.TestCase):
    """
    Define :func:`colour.algebra.geometry.extend_line_segment` definition unit
    tests methods.
    """

    def test_extend_line_segment(self):
        """Test :func:`colour.algebra.geometry.extend_line_segment` definition."""

        np.testing.assert_almost_equal(
            extend_line_segment(
                np.array([0.95694934, 0.13720932]),
                np.array([0.28382835, 0.60608318]),
            ),
            np.array([-0.5367248, 1.17765341]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            extend_line_segment(
                np.array([0.95694934, 0.13720932]),
                np.array([0.28382835, 0.60608318]),
                5,
            ),
            np.array([-3.81893739, 3.46393435]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            extend_line_segment(
                np.array([0.95694934, 0.13720932]),
                np.array([0.28382835, 0.60608318]),
                -1,
            ),
            np.array([1.1043815, 0.03451295]),
            decimal=7,
        )


class TestIntersectLineSegments(unittest.TestCase):
    """
    Define :func:`colour.algebra.geometry.intersect_line_segments` definition
    unit tests methods.
    """

    def test_intersect_line_segments(self):
        """
        Test :func:`colour.algebra.geometry.intersect_line_segments`
        definition.
        """

        l_1 = np.array(
            [
                [[0.15416284, 0.7400497], [0.26331502, 0.53373939]],
                [[0.01457496, 0.91874701], [0.90071485, 0.03342143]],
            ]
        )
        l_2 = np.array(
            [
                [[0.95694934, 0.13720932], [0.28382835, 0.60608318]],
                [[0.94422514, 0.85273554], [0.00225923, 0.52122603]],
                [[0.55203763, 0.48537741], [0.76813415, 0.16071675]],
                [[0.01457496, 0.91874701], [0.90071485, 0.03342143]],
            ]
        )

        s = intersect_line_segments(l_1, l_2)

        np.testing.assert_almost_equal(
            s.xy,
            np.array(
                [
                    [
                        [np.nan, np.nan],
                        [0.22791841, 0.60064309],
                        [np.nan, np.nan],
                        [np.nan, np.nan],
                    ],
                    [
                        [0.42814517, 0.50555685],
                        [0.30560559, 0.62798382],
                        [0.7578749, 0.17613012],
                        [np.nan, np.nan],
                    ],
                ]
            ),
            decimal=7,
        )

        np.testing.assert_array_equal(
            s.intersect,
            np.array([[False, True, False, False], [True, True, True, False]]),
        )

        np.testing.assert_array_equal(
            s.parallel,
            np.array(
                [[False, False, False, False], [False, False, False, True]]
            ),
        )

        np.testing.assert_array_equal(
            s.coincident,
            np.array(
                [[False, False, False, False], [False, False, False, True]]
            ),
        )


class TestEllipseCoefficientsCanonicalForm(unittest.TestCase):
    """
    Define :func:`colour.algebra.geometry.ellipse_coefficients_canonical_form`
    definition unit tests methods.
    """

    def test_ellipse_coefficients_canonical_form(self):
        """
        Test :func:`colour.algebra.geometry.\
ellipse_coefficients_canonical_form` definition.
        """

        np.testing.assert_almost_equal(
            ellipse_coefficients_canonical_form(
                np.array([2.5, -3.0, 2.5, -1.0, -1.0, -3.5])
            ),
            np.array([0.5, 0.5, 2, 1, 45]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            ellipse_coefficients_canonical_form(
                np.array([1.0, 0.0, 1.0, 0.0, 0.0, -1.0])
            ),
            np.array([0.0, 0.0, 1, 1, 0]),
            decimal=7,
        )


class TestEllipseCoefficientsGeneralForm(unittest.TestCase):
    """
    Define :func:`colour.algebra.geometry.ellipse_coefficients_general_form`
    definition unit tests methods.
    """

    def test_ellipse_coefficients_general_form(self):
        """
        Test :func:`colour.algebra.geometry.ellipse_coefficients_general_form`
        definition.
        """

        np.testing.assert_almost_equal(
            ellipse_coefficients_general_form(np.array([0.5, 0.5, 2, 1, 45])),
            np.array([2.5, -3.0, 2.5, -1.0, -1.0, -3.5]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            ellipse_coefficients_general_form(np.array([0.0, 0.0, 1, 1, 0])),
            np.array([1.0, 0.0, 1.0, 0.0, 0.0, -1.0]),
            decimal=7,
        )


class TestPointAtAngleOnEllipse(unittest.TestCase):
    """
    Define :func:`colour.algebra.geometry.point_at_angle_on_ellipse`
    definition unit tests methods.
    """

    def test_point_at_angle_on_ellipse(self):
        """
        Test :func:`colour.algebra.geometry.point_at_angle_on_ellipse`
        definition.
        """

        np.testing.assert_almost_equal(
            point_at_angle_on_ellipse(
                np.array([0, 90, 180, 270]), np.array([0.0, 0.0, 2, 1, 0])
            ),
            np.array([[2, 0], [0, 1], [-2, 0], [0, -1]]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            point_at_angle_on_ellipse(
                np.linspace(0, 360, 10), np.array([0.5, 0.5, 2, 1, 45])
            ),
            np.array(
                [
                    [1.91421356, 1.91421356],
                    [1.12883096, 2.03786992],
                    [0.04921137, 1.44193985],
                    [-0.81947922, 0.40526565],
                    [-1.07077081, -0.58708129],
                    [-0.58708129, -1.07077081],
                    [0.40526565, -0.81947922],
                    [1.44193985, 0.04921137],
                    [2.03786992, 1.12883096],
                    [1.91421356, 1.91421356],
                ]
            ),
            decimal=7,
        )


class TestEllipseFittingHalir1998(unittest.TestCase):
    """
    Define :func:`colour.algebra.geometry.ellipse_fitting_Halir1998`
    definition unit tests methods.
    """

    def test_ellipse_fitting_Halir1998(self):
        """
        Test :func:`colour.algebra.geometry.ellipse_fitting_Halir1998`
        definition.
        """

        np.testing.assert_almost_equal(
            ellipse_fitting_Halir1998(
                np.array([[2, 0], [0, 1], [-2, 0], [0, -1]])
            ),
            np.array(
                [
                    0.24253563,
                    0.00000000,
                    0.97014250,
                    0.00000000,
                    0.00000000,
                    -0.97014250,
                ]
            ),
            decimal=7,
        )


if __name__ == "__main__":
    unittest.main()
