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

[((9, 12, 9, 32), 'nexus_constructor.geometry.OFFGeometryNoNexus', 'OFFGeometryNoNexus', ({}, {}), '()', False, 'from nexus_constructor.geometry import OFFGeometryNoNexus\n'), ((84, 22, 84, 40), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(84, 32, 84, 33): '0', (84, 35, 84, 36): '0', (84, 38, 84, 39): '0'}, {}), '(0, 0, 0)', False, 'from PySide2.QtGui import QVector3D\n'), ((85, 23, 85, 46), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(85, 33, 85, 39): 'length', (85, 41, 85, 42): '0', (85, 44, 85, 45): '0'}, {}), '(length, 0, 0)', False, 'from PySide2.QtGui import QVector3D\n'), ((86, 22, 86, 45), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(86, 32, 86, 33): '0', (86, 35, 86, 41): 'length', (86, 43, 86, 44): '0'}, {}), '(0, length, 0)', False, 'from PySide2.QtGui import QVector3D\n'), ((87, 23, 87, 51), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(87, 33, 87, 39): 'length', (87, 41, 87, 47): 'length', (87, 49, 87, 50): '0'}, {}), '(length, length, 0)', False, 'from PySide2.QtGui import QVector3D\n'), ((88, 23, 88, 46), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(88, 33, 88, 34): '0', (88, 36, 88, 37): '0', (88, 39, 88, 45): 'length'}, {}), '(0, 0, length)', False, 'from PySide2.QtGui import QVector3D\n'), ((89, 24, 89, 52), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(89, 34, 89, 40): 'length', (89, 42, 89, 43): '0', (89, 45, 89, 51): 'length'}, {}), '(length, 0, length)', False, 'from PySide2.QtGui import QVector3D\n'), ((90, 23, 90, 51), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(90, 33, 90, 34): '0', (90, 36, 90, 42): 'length', (90, 44, 90, 50): 'length'}, {}), '(0, length, length)', False, 'from PySide2.QtGui import QVector3D\n'), ((91, 24, 91, 57), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(91, 34, 91, 40): 'length', (91, 42, 91, 48): 'length', (91, 50, 91, 56): 'length'}, {}), '(length, length, length)', False, 'from PySide2.QtGui import QVector3D\n'), ((265, 11, 265, 79), 'nexus_constructor.geometry.OFFGeometryNoNexus', 'OFFGeometryNoNexus', (), '', False, 'from nexus_constructor.geometry import OFFGeometryNoNexus\n'), ((273, 22, 273, 74), 'nexus_constructor.off_renderer.repeat_shape_over_positions', 'repeat_shape_over_positions', ({(273, 50, 273, 62): 'off_geometry', (273, 64, 273, 73): 'positions'}, {}), '(off_geometry, positions)', False, 'from nexus_constructor.off_renderer import repeat_shape_over_positions\n'), ((284, 22, 284, 74), 'nexus_constructor.off_renderer.repeat_shape_over_positions', 'repeat_shape_over_positions', ({(284, 50, 284, 62): 'off_geometry', (284, 64, 284, 73): 'positions'}, {}), '(off_geometry, positions)', False, 'from nexus_constructor.off_renderer import repeat_shape_over_positions\n'), ((295, 22, 295, 74), 'nexus_constructor.off_renderer.repeat_shape_over_positions', 'repeat_shape_over_positions', ({(295, 50, 295, 62): 'off_geometry', (295, 64, 295, 73): 'positions'}, {}), '(off_geometry, positions)', False, 'from nexus_constructor.off_renderer import repeat_shape_over_positions\n'), ((33, 35, 33, 53), 'io.StringIO', 'StringIO', ({(33, 44, 33, 52): 'off_file'}, {}), '(off_file)', False, 'from io import StringIO\n'), ((204, 46, 204, 60), 'io.StringIO', 'StringIO', ({(204, 55, 204, 59): 'cube'}, {}), '(cube)', False, 'from io import StringIO\n'), ((249, 46, 249, 56), 'io.StringIO', 'StringIO', ({}, {}), '()', False, 'from io import StringIO\n'), ((257, 8, 257, 26), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(257, 18, 257, 19): '(0)', (257, 21, 257, 22): '(0)', (257, 24, 257, 25): '(0)'}, {}), '(0, 0, 0)', False, 'from PySide2.QtGui import QVector3D\n'), ((258, 8, 258, 26), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(258, 18, 258, 19): '(0)', (258, 21, 258, 22): '(1)', (258, 24, 258, 25): '(0)'}, {}), '(0, 1, 0)', False, 'from PySide2.QtGui import QVector3D\n'), ((259, 8, 259, 26), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(259, 18, 259, 19): '(1)', (259, 21, 259, 22): '(1)', (259, 24, 259, 25): '(0)'}, {}), '(1, 1, 0)', False, 'from PySide2.QtGui import QVector3D\n'), ((260, 8, 260, 26), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(260, 18, 260, 19): '(1)', (260, 21, 260, 22): '(0)', (260, 24, 260, 25): '(0)'}, {}), '(1, 0, 0)', False, 'from PySide2.QtGui import QVector3D\n'), ((261, 8, 261, 30), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(261, 18, 261, 21): '(1.5)', (261, 23, 261, 26): '(0.5)', (261, 28, 261, 29): '(0)'}, {}), '(1.5, 0.5, 0)', False, 'from PySide2.QtGui import QVector3D\n'), ((271, 17, 271, 35), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(271, 27, 271, 28): '(0)', (271, 30, 271, 31): '(0)', (271, 33, 271, 34): '(0)'}, {}), '(0, 0, 0)', False, 'from PySide2.QtGui import QVector3D\n'), ((282, 17, 282, 35), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(282, 27, 282, 28): '(0)', (282, 30, 282, 31): '(0)', (282, 33, 282, 34): '(0)'}, {}), '(0, 0, 0)', False, 'from PySide2.QtGui import QVector3D\n'), ((282, 37, 282, 55), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(282, 47, 282, 48): '(0)', (282, 50, 282, 51): '(0)', (282, 53, 282, 54): '(1)'}, {}), '(0, 0, 1)', False, 'from PySide2.QtGui import QVector3D\n'), ((282, 57, 282, 75), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(282, 67, 282, 68): '(1)', (282, 70, 282, 71): '(0)', (282, 73, 282, 74): '(0)'}, {}), '(1, 0, 0)', False, 'from PySide2.QtGui import QVector3D\n'), ((293, 17, 293, 35), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(293, 27, 293, 28): '(0)', (293, 30, 293, 31): '(0)', (293, 33, 293, 34): '(0)'}, {}), '(0, 0, 0)', False, 'from PySide2.QtGui import QVector3D\n'), ((317, 54, 317, 72), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(317, 64, 317, 65): '1', (317, 67, 317, 68): '0', (317, 70, 317, 71): '0'}, {}), '(1, 0, 0)', False, 'from PySide2.QtGui import QVector3D\n'), ((330, 54, 330, 72), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(330, 64, 330, 65): '0', (330, 67, 330, 68): '1', (330, 70, 330, 71): '0'}, {}), '(0, 1, 0)', False, 'from PySide2.QtGui import QVector3D\n'), ((343, 54, 343, 73), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(343, 64, 343, 65): '0', (343, 67, 343, 68): '0', (343, 70, 343, 72): '-1'}, {}), '(0, 0, -1)', False, 'from PySide2.QtGui import QVector3D\n'), ((356, 54, 356, 73), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(356, 64, 356, 65): '0', (356, 67, 356, 68): '1', (356, 70, 356, 72): '-1'}, {}), '(0, 1, -1)', False, 'from PySide2.QtGui import QVector3D\n'), ((36, 8, 36, 34), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(36, 18, 36, 22): '(-0.5)', (36, 24, 36, 28): '(-0.5)', (36, 30, 36, 33): '(0.5)'}, {}), '(-0.5, -0.5, 0.5)', False, 'from PySide2.QtGui import QVector3D\n'), ((37, 8, 37, 33), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(37, 18, 37, 21): '(0.5)', (37, 23, 37, 27): '(-0.5)', (37, 29, 37, 32): '(0.5)'}, {}), '(0.5, -0.5, 0.5)', False, 'from PySide2.QtGui import QVector3D\n'), ((38, 8, 38, 33), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(38, 18, 38, 22): '(-0.5)', (38, 24, 38, 27): '(0.5)', (38, 29, 38, 32): '(0.5)'}, {}), '(-0.5, 0.5, 0.5)', False, 'from PySide2.QtGui import QVector3D\n'), ((39, 8, 39, 32), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(39, 18, 39, 21): '(0.5)', (39, 23, 39, 26): '(0.5)', (39, 28, 39, 31): '(0.5)'}, {}), '(0.5, 0.5, 0.5)', False, 'from PySide2.QtGui import QVector3D\n'), ((40, 8, 40, 34), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(40, 18, 40, 22): '(-0.5)', (40, 24, 40, 27): '(0.5)', (40, 29, 40, 33): '(-0.5)'}, {}), '(-0.5, 0.5, -0.5)', False, 'from PySide2.QtGui import QVector3D\n'), ((41, 8, 41, 33), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(41, 18, 41, 21): '(0.5)', (41, 23, 41, 26): '(0.5)', (41, 28, 41, 32): '(-0.5)'}, {}), '(0.5, 0.5, -0.5)', False, 'from PySide2.QtGui import QVector3D\n'), ((42, 8, 42, 35), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(42, 18, 42, 22): '(-0.5)', (42, 24, 42, 28): '(-0.5)', (42, 30, 42, 34): '(-0.5)'}, {}), '(-0.5, -0.5, -0.5)', False, 'from PySide2.QtGui import QVector3D\n'), ((43, 8, 43, 34), 'PySide2.QtGui.QVector3D', 'QVector3D', ({(43, 18, 43, 21): '(0.5)', (43, 23, 43, 27): '(-0.5)', (43, 29, 43, 33): '(-0.5)'}, {}), '(0.5, -0.5, -0.5)', False, 'from PySide2.QtGui import QVector3D\n')]