import unittest
import openmesh

import numpy as np

class Collapse(unittest.TestCase):

    def setUp(self):
        self.mesh = openmesh.TriMesh()
        self.vhandle = []
    
    def test_collapse_quad_with_center(self):
        
        # 0--------1
        # |\      /|
        # | \    / |
        # |  \  /  |
        # |    2   |
        # |  /  \  |
        # | /    \ |
        # 3--------4

        # Add some vertices
        self.vhandle.append(self.mesh.add_vertex(np.array([0, 0, 0])))
        self.vhandle.append(self.mesh.add_vertex(np.array([2, 0, 0])))
        self.vhandle.append(self.mesh.add_vertex(np.array([1, 1, 0])))
        self.vhandle.append(self.mesh.add_vertex(np.array([0, 2, 0])))
        self.vhandle.append(self.mesh.add_vertex(np.array([2, 2, 0])))

        # Add four faces
        face_vhandles = []

        face_vhandles.append(self.vhandle[0])
        face_vhandles.append(self.vhandle[2])
        face_vhandles.append(self.vhandle[1])
        self.mesh.add_face(face_vhandles)

        face_vhandles = []

        face_vhandles.append(self.vhandle[0])
        face_vhandles.append(self.vhandle[3])
        face_vhandles.append(self.vhandle[2])
        self.mesh.add_face(face_vhandles)

        face_vhandles = []

        face_vhandles.append(self.vhandle[4])
        face_vhandles.append(self.vhandle[2])
        face_vhandles.append(self.vhandle[3])
        self.mesh.add_face(face_vhandles)

        face_vhandles = []

        face_vhandles.append(self.vhandle[4])
        face_vhandles.append(self.vhandle[1])
        face_vhandles.append(self.vhandle[2])
        self.mesh.add_face(face_vhandles)

        # Get the halfedge
        v2v1 = self.mesh.find_halfedge(self.vhandle[2], self.vhandle[1])

        self.assertTrue(v2v1.is_valid())
        self.assertTrue(self.mesh.is_collapse_ok(v2v1))

        # Execute it as a crash test
        self.mesh.collapse(v2v1)

    def test_collapse_tetrahedron_complex(self):
        # Add some vertices
        self.vhandle.append(self.mesh.add_vertex(np.array([0, 0, 0])))
        self.vhandle.append(self.mesh.add_vertex(np.array([0, 1, 0])))
        self.vhandle.append(self.mesh.add_vertex(np.array([1, 1, 0])))
        self.vhandle.append(self.mesh.add_vertex(np.array([1, 0, 0])))
        
        # Add four faces
        face_vhandles = []
        
        face_vhandles.append(self.vhandle[0])
        face_vhandles.append(self.vhandle[1])
        face_vhandles.append(self.vhandle[2])
        self.mesh.add_face(face_vhandles)
        
        face_vhandles = []
        
        face_vhandles.append(self.vhandle[0])
        face_vhandles.append(self.vhandle[2])
        face_vhandles.append(self.vhandle[3])
        self.mesh.add_face(face_vhandles)
        
        face_vhandles = []
        
        face_vhandles.append(self.vhandle[2])
        face_vhandles.append(self.vhandle[1])
        face_vhandles.append(self.vhandle[3])
        self.mesh.add_face(face_vhandles)
        
        face_vhandles = []
        
        face_vhandles.append(self.vhandle[3])
        face_vhandles.append(self.vhandle[1])
        face_vhandles.append(self.vhandle[0])
        self.mesh.add_face(face_vhandles)
        
        v0v1 = self.mesh.halfedge_handle(0)
        v1v0 = self.mesh.opposite_halfedge_handle(v0v1)

        v1vL = self.mesh.next_halfedge_handle(v0v1)
        vLv1 = self.mesh.opposite_halfedge_handle(v1vL)
        vLv0 = self.mesh.next_halfedge_handle(v1vL)
        v0vL = self.mesh.opposite_halfedge_handle(vLv0)

        vLvR = self.mesh.next_halfedge_handle(v0vL)
        vRvL = self.mesh.opposite_halfedge_handle(vLvR)

        v0vR = self.mesh.next_halfedge_handle(v1v0)
        vRv0 = self.mesh.opposite_halfedge_handle(v0vR)
        vRv1 = self.mesh.next_halfedge_handle(v0vR)
        v1vR = self.mesh.opposite_halfedge_handle(vRv1)

        v0 = self.mesh.from_vertex_handle(v0v1)
        v1 = self.mesh.to_vertex_handle(v0v1)
        vL = self.mesh.to_vertex_handle(self.mesh.next_halfedge_handle(v0v1))
        vR = self.mesh.to_vertex_handle(self.mesh.next_halfedge_handle(v1v0))

        # ===================================================================
        # Check preconditions
        # ===================================================================

        self.assertTrue(self.mesh.is_collapse_ok(v0v1))
        self.assertTrue(self.mesh.is_collapse_ok(v1v0))

        # Test the Vertex indices
        self.assertEqual(v0.idx(), 0)
        self.assertEqual(v1.idx(), 1)
        self.assertEqual(vL.idx(), 2)
        self.assertEqual(vR.idx(), 3)

        # Check the halfedges
        self.assertEqual(v0v1.idx(), 0)
        self.assertEqual(v1v0.idx(), 1)
        
        self.assertEqual(v1vL.idx(), 2)
        self.assertEqual(vLv1.idx(), 3)
        self.assertEqual(vLv0.idx(), 4)
        self.assertEqual(v0vL.idx(), 5)
        
        self.assertEqual(vLvR.idx(), 6)
        self.assertEqual(vRvL.idx(), 7)
        
        self.assertEqual(vRv0.idx(), 8)
        self.assertEqual(v0vR.idx(), 9)
        
        self.assertEqual(v1vR.idx(), 10)
        self.assertEqual(vRv1.idx(), 11)

        # ===================================================================
        # Execute collapse
        # ===================================================================

        self.mesh.collapse(v0v1)

        # ===================================================================
        # Check configuration afterwards
        # ===================================================================

        # Now the configuration should look like this:
        # The numbers at the side denote the halfedges
        #          1
        #         / \
        #        /   \
        #      //    \\
        #     3/2    11\10
        #     //        \\
        #    /    6-->   \
        #   2 ----------- 3
        #       <--7

        self.assertEqual(self.mesh.n_faces(), 4)

        # Check if the right vertices got deleted
        self.assertTrue(self.mesh.is_deleted(self.mesh.face_handle(0)))
        self.assertFalse(self.mesh.is_deleted(self.mesh.face_handle(1)))
        self.assertFalse(self.mesh.is_deleted(self.mesh.face_handle(2)))
        self.assertTrue(self.mesh.is_deleted(self.mesh.face_handle(3)))

        # Check the vertices of the two remaining faces
        fh_1 = self.mesh.face_handle(1)
        fh_2 = self.mesh.face_handle(2)

        fv_it = self.mesh.fv(fh_1)

        self.assertEqual(next(fv_it).idx(), 1)
        self.assertEqual(next(fv_it).idx(), 2)
        self.assertEqual(next(fv_it).idx(), 3)

        fv_it = self.mesh.fv(fh_2)

        self.assertEqual(next(fv_it).idx(), 2)
        self.assertEqual(next(fv_it).idx(), 1)
        self.assertEqual(next(fv_it).idx(), 3)

        # Get the first halfedge of face 1
        fh_1_he = self.mesh.halfedge_handle(fh_1)

        self.assertEqual(fh_1_he.idx(), 11)
        self.assertEqual(self.mesh.to_vertex_handle(fh_1_he).idx(), 1)

        next_he = self.mesh.next_halfedge_handle(fh_1_he)
        self.assertEqual(next_he.idx(), 2)
        self.assertEqual(self.mesh.to_vertex_handle(next_he).idx(), 2)

        next_he = self.mesh.next_halfedge_handle(next_he)
        self.assertEqual(next_he.idx(), 6)
        self.assertEqual(self.mesh.to_vertex_handle(next_he).idx(), 3)

        # Get the first halfedge of face 2
        fh_2_he = self.mesh.halfedge_handle(fh_2)

        self.assertEqual(fh_2_he.idx(), 7)
        self.assertEqual(self.mesh.to_vertex_handle(fh_2_he).idx(), 2)

        next_he = self.mesh.next_halfedge_handle(fh_2_he)
        self.assertEqual(next_he.idx(), 3)
        self.assertEqual(self.mesh.to_vertex_handle(next_he).idx(), 1)

        next_he = self.mesh.next_halfedge_handle(next_he)
        self.assertEqual(next_he.idx(), 10)
        self.assertEqual(self.mesh.to_vertex_handle(next_he).idx(), 3)

        # Vertex 1 outgoing
        voh_it = self.mesh.voh(self.mesh.vertex_handle(1))
        self.assertEqual(next(voh_it).idx(), 10)
        self.assertEqual(next(voh_it).idx(), 2)
        with self.assertRaises(StopIteration):
            next(voh_it)

        # Vertex 2 outgoing
        voh_it = self.mesh.voh(self.mesh.vertex_handle(2))
        self.assertEqual(next(voh_it).idx(), 3)
        self.assertEqual(next(voh_it).idx(), 6)
        with self.assertRaises(StopIteration):
            next(voh_it)

        # Vertex 2 outgoing
        voh_it = self.mesh.voh(self.mesh.vertex_handle(3))
        self.assertEqual(next(voh_it).idx(), 11)
        self.assertEqual(next(voh_it).idx(), 7)
        with self.assertRaises(StopIteration):
            next(voh_it)

        # ===================================================================
        # Cleanup
        # ===================================================================
        self.mesh.garbage_collection()

        # ===================================================================
        # Check configuration afterwards
        # ===================================================================

        # Now the configuration should look like this:
        # The numbers at the side denote the halfedges
        #          0
        #         / \
        #        /   \
        #      //    \\
        #     4/5     0\1
        #     //        \\
        #    /    3-->   \
        #   2 ----------- 1
        #       <--2

        self.assertEqual(self.mesh.n_faces(), 2)

        # Check the vertices of the two remaining faces
        fh_0 = self.mesh.face_handle(0)
        fh_1 = self.mesh.face_handle(1)

        fv_it = self.mesh.fv(fh_0)

        self.assertEqual(next(fv_it).idx(), 2)
        self.assertEqual(next(fv_it).idx(), 1)
        self.assertEqual(next(fv_it).idx(), 0)

        fv_it = self.mesh.fv(fh_1)

        self.assertEqual(next(fv_it).idx(), 1)
        self.assertEqual(next(fv_it).idx(), 2)
        self.assertEqual(next(fv_it).idx(), 0)

        # Get the first halfedge of face 1
        fh_0_he = self.mesh.halfedge_handle(fh_0)

        self.assertEqual(fh_0_he.idx(), 5)
        self.assertEqual(self.mesh.to_vertex_handle(fh_0_he).idx(), 2)

        next_he = self.mesh.next_halfedge_handle(fh_0_he)
        self.assertEqual(next_he.idx(), 3)
        self.assertEqual(self.mesh.to_vertex_handle(next_he).idx(), 1)

        next_he = self.mesh.next_halfedge_handle(next_he)
        self.assertEqual(next_he.idx(), 0)
        self.assertEqual(self.mesh.to_vertex_handle(next_he).idx(), 0)

        # Get the first halfedge of face 1
        fh_1_he = self.mesh.halfedge_handle(fh_1)

        self.assertEqual(fh_1_he.idx(), 1)
        self.assertEqual(self.mesh.to_vertex_handle(fh_1_he).idx(), 1)

        next_he = self.mesh.next_halfedge_handle(fh_1_he)
        self.assertEqual(next_he.idx(), 2)
        self.assertEqual(self.mesh.to_vertex_handle(next_he).idx(), 2)

        next_he = self.mesh.next_halfedge_handle(next_he)
        self.assertEqual(next_he.idx(), 4)
        self.assertEqual(self.mesh.to_vertex_handle(next_he).idx(), 0)

        # Vertex 0 outgoing
        voh_it = self.mesh.voh(self.mesh.vertex_handle(0))
        self.assertEqual(next(voh_it).idx(), 1)
        self.assertEqual(next(voh_it).idx(), 5)
        with self.assertRaises(StopIteration):
            next(voh_it)

        # Vertex 1 outgoing
        voh_it = self.mesh.voh(self.mesh.vertex_handle(1))
        self.assertEqual(next(voh_it).idx(), 0)
        self.assertEqual(next(voh_it).idx(), 2)
        with self.assertRaises(StopIteration):
            next(voh_it)

        # Vertex 2 outgoing
        voh_it = self.mesh.voh(self.mesh.vertex_handle(2))
        self.assertEqual(next(voh_it).idx(), 3)
        self.assertEqual(next(voh_it).idx(), 4)
        with self.assertRaises(StopIteration):
            next(voh_it)

        self.assertFalse(self.mesh.is_collapse_ok(self.mesh.halfedge_handle(0)))
        self.assertFalse(self.mesh.is_collapse_ok(self.mesh.halfedge_handle(1)))
        self.assertFalse(self.mesh.is_collapse_ok(self.mesh.halfedge_handle(2)))
        self.assertFalse(self.mesh.is_collapse_ok(self.mesh.halfedge_handle(3)))
        self.assertFalse(self.mesh.is_collapse_ok(self.mesh.halfedge_handle(4)))
        self.assertFalse(self.mesh.is_collapse_ok(self.mesh.halfedge_handle(5)))

    def test_collapse_tetrahedron(self):
        # Add some vertices
        self.vhandle.append(self.mesh.add_vertex(np.array([ 0,  0, 0])))
        self.vhandle.append(self.mesh.add_vertex(np.array([ 1,  0, 0])))
        self.vhandle.append(self.mesh.add_vertex(np.array([ 0, -1, 0])))
        self.vhandle.append(self.mesh.add_vertex(np.array([ 0,  1, 0])))
        self.vhandle.append(self.mesh.add_vertex(np.array([-1,  0, 0])))
        
        # Add six faces
        face_vhandles = []
        
        face_vhandles.append(self.vhandle[0])
        face_vhandles.append(self.vhandle[4])
        face_vhandles.append(self.vhandle[2])
        self.mesh.add_face(face_vhandles)

        face_vhandles = []

        face_vhandles.append(self.vhandle[3])
        face_vhandles.append(self.vhandle[4])
        face_vhandles.append(self.vhandle[0])
        self.mesh.add_face(face_vhandles)

        face_vhandles = []

        face_vhandles.append(self.vhandle[2])
        face_vhandles.append(self.vhandle[4])
        face_vhandles.append(self.vhandle[3])
        self.mesh.add_face(face_vhandles)

        face_vhandles = []

        face_vhandles.append(self.vhandle[2])
        face_vhandles.append(self.vhandle[1])
        face_vhandles.append(self.vhandle[0])
        self.mesh.add_face(face_vhandles)

        face_vhandles = []

        face_vhandles.append(self.vhandle[0])
        face_vhandles.append(self.vhandle[1])
        face_vhandles.append(self.vhandle[3])
        self.mesh.add_face(face_vhandles)

        # =============================================
        # Collapse halfedge from 0 to 4
        # =============================================

        heh_collapse1 = self.mesh.halfedge_handle(0)

        self.assertEqual(self.mesh.to_vertex_handle(heh_collapse1).idx(), 4)
        self.assertEqual(self.mesh.from_vertex_handle(heh_collapse1).idx(), 0)

        self.assertTrue(self.mesh.is_collapse_ok(heh_collapse1))
        self.mesh.collapse(heh_collapse1)

        heh_collapse2 = self.mesh.halfedge_handle(2)

        self.assertEqual(self.mesh.to_vertex_handle(heh_collapse2).idx(), 2)
        self.assertEqual(self.mesh.from_vertex_handle(heh_collapse2).idx(), 4)

        self.assertTrue(self.mesh.is_collapse_ok(heh_collapse2))
        self.mesh.collapse(heh_collapse2)

        heh_collapse3 = self.mesh.halfedge_handle(6)

        self.assertEqual(self.mesh.to_vertex_handle(heh_collapse3).idx(), 2)
        self.assertEqual(self.mesh.from_vertex_handle(heh_collapse3).idx(), 3)

        self.assertFalse(self.mesh.is_collapse_ok(heh_collapse3))

    def test_large_collapse_halfedge(self):
        # Add some vertices
        self.vhandle.append(self.mesh.add_vertex(np.array([ 0,  1, 0])))
        self.vhandle.append(self.mesh.add_vertex(np.array([ 1,  0, 0])))
        self.vhandle.append(self.mesh.add_vertex(np.array([ 2,  1, 0])))
        self.vhandle.append(self.mesh.add_vertex(np.array([ 0, -1, 0])))
        self.vhandle.append(self.mesh.add_vertex(np.array([ 2, -1, 0])))
        self.vhandle.append(self.mesh.add_vertex(np.array([-1,  0, 0])))
        self.vhandle.append(self.mesh.add_vertex(np.array([ 3,  0, 0])))
        
        # Add six faces
        face_vhandles = []
        
        face_vhandles.append(self.vhandle[0])
        face_vhandles.append(self.vhandle[5])
        face_vhandles.append(self.vhandle[1])
        self.mesh.add_face(face_vhandles)

        face_vhandles = []

        face_vhandles.append(self.vhandle[1])
        face_vhandles.append(self.vhandle[5])
        face_vhandles.append(self.vhandle[3])
        self.mesh.add_face(face_vhandles)

        face_vhandles = []

        face_vhandles.append(self.vhandle[0])
        face_vhandles.append(self.vhandle[1])
        face_vhandles.append(self.vhandle[2])
        self.mesh.add_face(face_vhandles)

        face_vhandles = []

        face_vhandles.append(self.vhandle[1])
        face_vhandles.append(self.vhandle[3])
        face_vhandles.append(self.vhandle[4])
        self.mesh.add_face(face_vhandles)

        face_vhandles = []

        face_vhandles.append(self.vhandle[2])
        face_vhandles.append(self.vhandle[1])
        face_vhandles.append(self.vhandle[4])
        self.mesh.add_face(face_vhandles)

        face_vhandles = []

        face_vhandles.append(self.vhandle[2])
        face_vhandles.append(self.vhandle[4])
        face_vhandles.append(self.vhandle[6])
        self.mesh.add_face(face_vhandles)

        # Test setup:
        #    0 ==== 2
        #   / \    /|\
        #  /   \  / | \
        # 5 --- 1   |  6
        #  \   /  \ | /
        #   \ /    \|/
        #    3 ==== 4

        # =============================================
        # Collapse halfedge from 1 to 4
        # =============================================

        heh_collapse = openmesh.HalfedgeHandle()

        for he in self.mesh.halfedges():
            if self.mesh.from_vertex_handle(he).idx() == 1 and self.mesh.to_vertex_handle(he).idx() == 4:
                heh_collapse = he

        # Check our halfedge
        self.assertEqual(self.mesh.to_vertex_handle(heh_collapse).idx(), 4)
        self.assertEqual(self.mesh.from_vertex_handle(heh_collapse).idx(), 1)
        self.assertTrue(self.mesh.is_collapse_ok(heh_collapse))

        # Remember the end vertices
        vh_from = self.mesh.from_vertex_handle(heh_collapse)
        vh_to = self.mesh.to_vertex_handle(heh_collapse)

        # Collapse it
        self.mesh.collapse(heh_collapse)

        self.assertTrue(self.mesh.is_deleted(vh_from))
        self.assertFalse(self.mesh.is_deleted(vh_to))


if __name__ == '__main__':
    suite = unittest.TestLoader().loadTestsFromTestCase(Collapse)
    unittest.TextTestRunner(verbosity=2).run(suite)
