from nexus_constructor.geometry import OFFGeometryNoNexus
from nexus_constructor.geometry.geometry_loader import load_geometry_from_file_object
from nexus_constructor.off_renderer import repeat_shape_over_positions
from PySide2.QtGui import QVector3D
from io import StringIO


def test_GIVEN_off_file_containing_geometry_WHEN_loading_geometry_to_file_THEN_vertices_and_faces_loaded_are_the_same_as_the_file():
    model = OFFGeometryNoNexus()
    model.units = "m"

    off_file = (
        "OFF\n"
        "#  cube.off\n"
        "#  A cube\n"
        "8 6 0\n"
        "-0.500000 -0.500000 0.500000\n"
        "0.500000 -0.500000 0.500000\n"
        "-0.500000 0.500000 0.500000\n"
        "0.500000 0.500000 0.500000\n"
        "-0.500000 0.500000 -0.500000\n"
        "0.500000 0.500000 -0.500000\n"
        "-0.500000 -0.500000 -0.500000\n"
        "0.500000 -0.500000 -0.500000\n"
        "4 0 1 3 2\n"
        "4 2 3 5 4\n"
        "4 4 5 7 6\n"
        "4 6 7 1 0\n"
        "4 1 7 5 3\n"
        "4 6 0 2 4\n"
    )

    load_geometry_from_file_object(StringIO(off_file), ".off", model.units, model)

    assert model.vertices == [
        QVector3D(-0.5, -0.5, 0.5),
        QVector3D(0.5, -0.5, 0.5),
        QVector3D(-0.5, 0.5, 0.5),
        QVector3D(0.5, 0.5, 0.5),
        QVector3D(-0.5, 0.5, -0.5),
        QVector3D(0.5, 0.5, -0.5),
        QVector3D(-0.5, -0.5, -0.5),
        QVector3D(0.5, -0.5, -0.5),
    ]
    assert model.faces == [
        [0, 1, 3, 2],
        [2, 3, 5, 4],
        [4, 5, 7, 6],
        [6, 7, 1, 0],
        [1, 7, 5, 3],
        [6, 0, 2, 4],
    ]
    assert model.winding_order == [
        0,
        1,
        3,
        2,
        2,
        3,
        5,
        4,
        4,
        5,
        7,
        6,
        6,
        7,
        1,
        0,
        1,
        7,
        5,
        3,
        6,
        0,
        2,
        4,
    ]
    assert model.winding_order_indices == [0, 4, 8, 12, 16, 20]


def test_GIVEN_stl_file_with_cube_geometry_WHEN_loading_geometry_THEN_all_faces_are_present():
    length = 30
    left_lower_rear = QVector3D(0, 0, 0)
    right_lower_rear = QVector3D(length, 0, 0)
    left_upper_rear = QVector3D(0, length, 0)
    right_upper_rear = QVector3D(length, length, 0)
    left_lower_front = QVector3D(0, 0, length)
    right_lower_front = QVector3D(length, 0, length)
    left_upper_front = QVector3D(0, length, length)
    right_upper_front = QVector3D(length, length, length)
    # faces on a cube with a right hand winding order
    faces = [
        [
            left_lower_front,
            left_lower_rear,
            right_lower_rear,
            right_lower_front,
        ],  # bottom
        [left_lower_front, left_upper_front, left_upper_rear, left_lower_rear],  # left
        [
            left_upper_front,
            left_lower_front,
            right_lower_front,
            right_upper_front,
        ],  # front
        [
            right_upper_front,
            right_lower_front,
            right_lower_rear,
            right_upper_rear,
        ],  # right
        [right_upper_rear, right_lower_rear, left_lower_rear, left_upper_rear],  # rear
        [left_upper_rear, left_upper_front, right_upper_front, right_upper_rear],  # top
    ]

    cube = """solid vcg
        facet normal -1.000000e+00  0.000000e+00  0.000000e+00
        outer loop
        vertex   0.000000e+00  3.000000e+01  0.000000e+00
        vertex   0.000000e+00  0.000000e+00  3.000000e+01
        vertex   0.000000e+00  3.000000e+01  3.000000e+01
        endloop
        endfacet
        facet normal -1.000000e+00  0.000000e+00  0.000000e+00
        outer loop
        vertex   0.000000e+00  0.000000e+00  0.000000e+00
        vertex   0.000000e+00  0.000000e+00  3.000000e+01
        vertex   0.000000e+00  3.000000e+01  0.000000e+00
        endloop
        endfacet
        facet normal  1.000000e+00 -0.000000e+00  0.000000e+00
        outer loop
        vertex   3.000000e+01  0.000000e+00  3.000000e+01
        vertex   3.000000e+01  3.000000e+01  0.000000e+00
        vertex   3.000000e+01  3.000000e+01  3.000000e+01
        endloop
        endfacet
        facet normal  1.000000e+00  0.000000e+00  0.000000e+00
        outer loop
        vertex   3.000000e+01  0.000000e+00  3.000000e+01
        vertex   3.000000e+01  0.000000e+00  0.000000e+00
        vertex   3.000000e+01  3.000000e+01  0.000000e+00
        endloop
        endfacet
        facet normal  0.000000e+00 -1.000000e+00  0.000000e+00
        outer loop
        vertex   3.000000e+01  0.000000e+00  0.000000e+00
        vertex   3.000000e+01  0.000000e+00  3.000000e+01
        vertex   0.000000e+00  0.000000e+00  0.000000e+00
        endloop
        endfacet
        facet normal  0.000000e+00 -1.000000e+00  0.000000e+00
        outer loop
        vertex   0.000000e+00  0.000000e+00  0.000000e+00
        vertex   3.000000e+01  0.000000e+00  3.000000e+01
        vertex   0.000000e+00  0.000000e+00  3.000000e+01
        endloop
        endfacet
        facet normal  0.000000e+00  1.000000e+00  0.000000e+00
        outer loop
        vertex   3.000000e+01  3.000000e+01  3.000000e+01
        vertex   3.000000e+01  3.000000e+01  0.000000e+00
        vertex   0.000000e+00  3.000000e+01  0.000000e+00
        endloop
        endfacet
        facet normal  0.000000e+00  1.000000e+00  0.000000e+00
        outer loop
        vertex   3.000000e+01  3.000000e+01  3.000000e+01
        vertex   0.000000e+00  3.000000e+01  0.000000e+00
        vertex   0.000000e+00  3.000000e+01  3.000000e+01
        endloop
        endfacet
        facet normal  0.000000e+00  0.000000e+00 -1.000000e+00
        outer loop
        vertex   0.000000e+00  3.000000e+01  0.000000e+00
        vertex   3.000000e+01  3.000000e+01  0.000000e+00
        vertex   0.000000e+00  0.000000e+00  0.000000e+00
        endloop
        endfacet
        facet normal  0.000000e+00  0.000000e+00 -1.000000e+00
        outer loop
        vertex   0.000000e+00  0.000000e+00  0.000000e+00
        vertex   3.000000e+01  3.000000e+01  0.000000e+00
        vertex   3.000000e+01  0.000000e+00  0.000000e+00
        endloop
        endfacet
        facet normal  0.000000e+00  0.000000e+00  1.000000e+00
        outer loop
        vertex   3.000000e+01  3.000000e+01  3.000000e+01
        vertex   0.000000e+00  3.000000e+01  3.000000e+01
        vertex   0.000000e+00  0.000000e+00  3.000000e+01
        endloop
        endfacet
        facet normal  0.000000e+00  0.000000e+00  1.000000e+00
        outer loop
        vertex   3.000000e+01  3.000000e+01  3.000000e+01
        vertex   0.000000e+00  0.000000e+00  3.000000e+01
        vertex   3.000000e+01  0.000000e+00  3.000000e+01
        endloop
        endfacet
        endsolid vcg"""

    geometry = load_geometry_from_file_object(StringIO(cube), ".stl", "m")

    # 2 triangles per face, 6 faces in the cube
    assert len(geometry.faces) == 6 * 2
    assert geometry.winding_order_indices == [i * 3 for i in range(12)]
    # each expected vertex is in the shape
    for vertex in [
        left_lower_rear,
        right_lower_rear,
        left_upper_rear,
        right_upper_rear,
        left_lower_front,
        right_lower_front,
        left_upper_front,
        right_upper_front,
    ]:
        assert vertex in geometry.vertices
    # each face must be in the loaded geometry
    for face in faces:
        face_found = False
        # each face could be split into triangles in one of two ways
        for triangle_split in [
            [[face[0], face[1], face[2]], [face[2], face[3], face[0]]],
            [[face[1], face[2], face[3]], [face[3], face[0], face[1]]],
        ]:
            triangle_matches = 0
            # each triangle in the square's split must be in the loaded geometry for the square to be
            for triangle in triangle_split:
                # check the triangle against each rotation of each triangle in the geometry
                for candidate_triangle_indices in geometry.faces:
                    a = geometry.vertices[candidate_triangle_indices[0]]
                    b = geometry.vertices[candidate_triangle_indices[1]]
                    c = geometry.vertices[candidate_triangle_indices[2]]
                    if (
                        triangle == [a, b, c]
                        or triangle == [b, c, a]
                        or triangle == [c, a, b]
                    ):
                        triangle_matches += 1
            if triangle_matches == 2:
                face_found = True
        assert face_found


def test_GIVEN_unrecognised_file_extension_WHEN_loading_geometry_THEN_returns_empty_geometry():
    geometry = load_geometry_from_file_object(StringIO(), ".txt", "m")
    assert len(geometry.vertices) == 0
    assert len(geometry.faces) == 0


def get_dummy_OFF():
    # A square with a triangle on the side
    original_vertices = [
        QVector3D(0, 0, 0),
        QVector3D(0, 1, 0),
        QVector3D(1, 1, 0),
        QVector3D(1, 0, 0),
        QVector3D(1.5, 0.5, 0),
    ]
    original_faces = [[0, 1, 2, 3], [2, 3, 4]]

    return OFFGeometryNoNexus(vertices=original_vertices, faces=original_faces)


def test_WHEN_generate_off_mesh_with_no_repeat_THEN_off_unchanged():
    off_geometry = get_dummy_OFF()

    positions = [QVector3D(0, 0, 0)]

    faces, vertices = repeat_shape_over_positions(off_geometry, positions)

    assert faces == off_geometry.faces
    assert vertices == off_geometry.vertices


def test_WHEN_generate_off_mesh_with_three_copies_THEN_original_shape_remains():
    off_geometry = get_dummy_OFF()

    positions = [QVector3D(0, 0, 0), QVector3D(0, 0, 1), QVector3D(1, 0, 0)]

    faces, vertices = repeat_shape_over_positions(off_geometry, positions)

    assert faces[: len(off_geometry.faces)] == off_geometry.faces
    assert vertices[: len(off_geometry.vertices)] == off_geometry.vertices


def _test_position_with_single_translation_helper(translation):
    off_geometry = get_dummy_OFF()

    positions = [QVector3D(0, 0, 0), translation]

    faces, vertices = repeat_shape_over_positions(off_geometry, positions)

    second_shape_faces = faces[len(off_geometry.faces) :]
    second_shape_vertices = vertices[len(off_geometry.vertices) :]

    # Faces will just be the same but every vertex added to be len(vertices)
    shifted_faces = []
    for face in second_shape_faces:
        shifted_face = []
        for vertex in face:
            shifted_face.append(vertex - len(off_geometry.vertices))
        shifted_faces.append(shifted_face)

    assert shifted_faces == off_geometry.faces

    return off_geometry.vertices, second_shape_vertices


def test_WHEN_generate_off_mesh_with_single_x_position_THEN_second_shape_just_translation_of_first():
    (
        original_vertices,
        second_shape_vertices,
    ) = _test_position_with_single_translation_helper(QVector3D(1, 0, 0))

    # Vertices will be the same by shifted by 1
    for vertex in second_shape_vertices:
        vertex.setX(vertex.x() - 1)

    assert second_shape_vertices == original_vertices


def test_WHEN_generate_off_mesh_with_single_y_position_THEN_second_shape_just_translation_of_first():
    (
        original_vertices,
        second_shape_vertices,
    ) = _test_position_with_single_translation_helper(QVector3D(0, 1, 0))

    # Vertices will be the same by shifted by 1
    for vertex in second_shape_vertices:
        vertex.setY(vertex.y() - 1)

    assert second_shape_vertices == original_vertices


def test_WHEN_generate_off_mesh_with_single_negative_z_position_THEN_second_shape_just_translation_of_first():
    (
        original_vertices,
        second_shape_vertices,
    ) = _test_position_with_single_translation_helper(QVector3D(0, 0, -1))

    # Vertices will be the same by shifted by 1
    for vertex in second_shape_vertices:
        vertex.setZ(vertex.z() + 1)

    assert second_shape_vertices == original_vertices


def test_WHEN_generate_off_mesh_with_single_diagonal_position_THEN_second_shape_just_translation_of_first():
    (
        original_vertices,
        second_shape_vertices,
    ) = _test_position_with_single_translation_helper(QVector3D(0, 1, -1))

    for vertex in second_shape_vertices:
        vertex.setZ(vertex.z() + 1)
        vertex.setY(vertex.y() - 1)

    assert second_shape_vertices == original_vertices

[((422, 442), 'nexus_constructor.geometry.OFFGeometryNoNexus', 'OFFGeometryNoNexus', ([], {}), '()\n', (440, 442), False, 'from nexus_constructor.geometry import OFFGeometryNoNexus\n'), ((2102, 2120), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(0)', '(0)', '(0)'], {}), '(0, 0, 0)\n', (2111, 2120), False, 'from PySide2.QtGui import QVector3D\n'), ((2144, 2167), 'PySide2.QtGui.QVector3D', 'QVector3D', (['length', '(0)', '(0)'], {}), '(length, 0, 0)\n', (2153, 2167), False, 'from PySide2.QtGui import QVector3D\n'), ((2190, 2213), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(0)', 'length', '(0)'], {}), '(0, length, 0)\n', (2199, 2213), False, 'from PySide2.QtGui import QVector3D\n'), ((2237, 2265), 'PySide2.QtGui.QVector3D', 'QVector3D', (['length', 'length', '(0)'], {}), '(length, length, 0)\n', (2246, 2265), False, 'from PySide2.QtGui import QVector3D\n'), ((2289, 2312), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(0)', '(0)', 'length'], {}), '(0, 0, length)\n', (2298, 2312), False, 'from PySide2.QtGui import QVector3D\n'), ((2337, 2365), 'PySide2.QtGui.QVector3D', 'QVector3D', (['length', '(0)', 'length'], {}), '(length, 0, length)\n', (2346, 2365), False, 'from PySide2.QtGui import QVector3D\n'), ((2389, 2417), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(0)', 'length', 'length'], {}), '(0, length, length)\n', (2398, 2417), False, 'from PySide2.QtGui import QVector3D\n'), ((2442, 2475), 'PySide2.QtGui.QVector3D', 'QVector3D', (['length', 'length', 'length'], {}), '(length, length, length)\n', (2451, 2475), False, 'from PySide2.QtGui import QVector3D\n'), ((9135, 9203), 'nexus_constructor.geometry.OFFGeometryNoNexus', 'OFFGeometryNoNexus', ([], {'vertices': 'original_vertices', 'faces': 'original_faces'}), '(vertices=original_vertices, faces=original_faces)\n', (9153, 9203), False, 'from nexus_constructor.geometry import OFFGeometryNoNexus\n'), ((9371, 9423), 'nexus_constructor.off_renderer.repeat_shape_over_positions', 'repeat_shape_over_positions', (['off_geometry', 'positions'], {}), '(off_geometry, positions)\n', (9398, 9423), False, 'from nexus_constructor.off_renderer import repeat_shape_over_positions\n'), ((9728, 9780), 'nexus_constructor.off_renderer.repeat_shape_over_positions', 'repeat_shape_over_positions', (['off_geometry', 'positions'], {}), '(off_geometry, positions)\n', (9755, 9780), False, 'from nexus_constructor.off_renderer import repeat_shape_over_positions\n'), ((10098, 10150), 'nexus_constructor.off_renderer.repeat_shape_over_positions', 'repeat_shape_over_positions', (['off_geometry', 'positions'], {}), '(off_geometry, positions)\n', (10125, 10150), False, 'from nexus_constructor.off_renderer import repeat_shape_over_positions\n'), ((1061, 1079), 'io.StringIO', 'StringIO', (['off_file'], {}), '(off_file)\n', (1069, 1079), False, 'from io import StringIO\n'), ((6834, 6848), 'io.StringIO', 'StringIO', (['cube'], {}), '(cube)\n', (6842, 6848), False, 'from io import StringIO\n'), ((8734, 8744), 'io.StringIO', 'StringIO', ([], {}), '()\n', (8742, 8744), False, 'from io import StringIO\n'), ((8934, 8952), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(0)', '(0)', '(0)'], {}), '(0, 0, 0)\n', (8943, 8952), False, 'from PySide2.QtGui import QVector3D\n'), ((8962, 8980), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(0)', '(1)', '(0)'], {}), '(0, 1, 0)\n', (8971, 8980), False, 'from PySide2.QtGui import QVector3D\n'), ((8990, 9008), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(1)', '(1)', '(0)'], {}), '(1, 1, 0)\n', (8999, 9008), False, 'from PySide2.QtGui import QVector3D\n'), ((9018, 9036), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(1)', '(0)', '(0)'], {}), '(1, 0, 0)\n', (9027, 9036), False, 'from PySide2.QtGui import QVector3D\n'), ((9046, 9068), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(1.5)', '(0.5)', '(0)'], {}), '(1.5, 0.5, 0)\n', (9055, 9068), False, 'from PySide2.QtGui import QVector3D\n'), ((9328, 9346), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(0)', '(0)', '(0)'], {}), '(0, 0, 0)\n', (9337, 9346), False, 'from PySide2.QtGui import QVector3D\n'), ((9645, 9663), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(0)', '(0)', '(0)'], {}), '(0, 0, 0)\n', (9654, 9663), False, 'from PySide2.QtGui import QVector3D\n'), ((9665, 9683), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(0)', '(0)', '(1)'], {}), '(0, 0, 1)\n', (9674, 9683), False, 'from PySide2.QtGui import QVector3D\n'), ((9685, 9703), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(1)', '(0)', '(0)'], {}), '(1, 0, 0)\n', (9694, 9703), False, 'from PySide2.QtGui import QVector3D\n'), ((10042, 10060), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(0)', '(0)', '(0)'], {}), '(0, 0, 0)\n', (10051, 10060), False, 'from PySide2.QtGui import QVector3D\n'), ((10907, 10925), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(1)', '(0)', '(0)'], {}), '(1, 0, 0)\n', (10916, 10925), False, 'from PySide2.QtGui import QVector3D\n'), ((11330, 11348), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(0)', '(1)', '(0)'], {}), '(0, 1, 0)\n', (11339, 11348), False, 'from PySide2.QtGui import QVector3D\n'), ((11762, 11781), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(0)', '(0)', '(-1)'], {}), '(0, 0, -1)\n', (11771, 11781), False, 'from PySide2.QtGui import QVector3D\n'), ((12193, 12212), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(0)', '(1)', '(-1)'], {}), '(0, 1, -1)\n', (12202, 12212), False, 'from PySide2.QtGui import QVector3D\n'), ((1149, 1175), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(-0.5)', '(-0.5)', '(0.5)'], {}), '(-0.5, -0.5, 0.5)\n', (1158, 1175), False, 'from PySide2.QtGui import QVector3D\n'), ((1185, 1210), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(0.5)', '(-0.5)', '(0.5)'], {}), '(0.5, -0.5, 0.5)\n', (1194, 1210), False, 'from PySide2.QtGui import QVector3D\n'), ((1220, 1245), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(-0.5)', '(0.5)', '(0.5)'], {}), '(-0.5, 0.5, 0.5)\n', (1229, 1245), False, 'from PySide2.QtGui import QVector3D\n'), ((1255, 1279), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(0.5)', '(0.5)', '(0.5)'], {}), '(0.5, 0.5, 0.5)\n', (1264, 1279), False, 'from PySide2.QtGui import QVector3D\n'), ((1289, 1315), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(-0.5)', '(0.5)', '(-0.5)'], {}), '(-0.5, 0.5, -0.5)\n', (1298, 1315), False, 'from PySide2.QtGui import QVector3D\n'), ((1325, 1350), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(0.5)', '(0.5)', '(-0.5)'], {}), '(0.5, 0.5, -0.5)\n', (1334, 1350), False, 'from PySide2.QtGui import QVector3D\n'), ((1360, 1387), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(-0.5)', '(-0.5)', '(-0.5)'], {}), '(-0.5, -0.5, -0.5)\n', (1369, 1387), False, 'from PySide2.QtGui import QVector3D\n'), ((1397, 1423), 'PySide2.QtGui.QVector3D', 'QVector3D', (['(0.5)', '(-0.5)', '(-0.5)'], {}), '(0.5, -0.5, -0.5)\n', (1406, 1423), False, 'from PySide2.QtGui import QVector3D\n')]