# system modules
import math
import pytest
import unittest
from tests import BaseTest
from OCP.gp import gp_Vec, gp_Pnt, gp_Ax2, gp_Circ, gp_Elips, gp, gp_XYZ, gp_Trsf
from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge

from cadquery import *

DEG2RAD = math.pi / 180
RAD2DEG = 180 / math.pi


class TestCadObjects(BaseTest):
    def _make_circle(self):

        circle = gp_Circ(gp_Ax2(gp_Pnt(1, 2, 3), gp.DZ_s()), 2.0)
        return Shape.cast(BRepBuilderAPI_MakeEdge(circle).Edge())

    def _make_ellipse(self):

        ellipse = gp_Elips(gp_Ax2(gp_Pnt(1, 2, 3), gp.DZ_s()), 4.0, 2.0)
        return Shape.cast(BRepBuilderAPI_MakeEdge(ellipse).Edge())

    def testVectorConstructors(self):
        v1 = Vector(1, 2, 3)
        v2 = Vector((1, 2, 3))
        v3 = Vector(gp_Vec(1, 2, 3))
        v4 = Vector([1, 2, 3])
        v5 = Vector(gp_XYZ(1, 2, 3))

        for v in [v1, v2, v3, v4, v5]:
            self.assertTupleAlmostEquals((1, 2, 3), v.toTuple(), 4)

        v6 = Vector((1, 2))
        v7 = Vector([1, 2])
        v8 = Vector(1, 2)

        for v in [v6, v7, v8]:
            self.assertTupleAlmostEquals((1, 2, 0), v.toTuple(), 4)

        v9 = Vector()
        self.assertTupleAlmostEquals((0, 0, 0), v9.toTuple(), 4)

        v9.x = 1.0
        v9.y = 2.0
        v9.z = 3.0
        self.assertTupleAlmostEquals((1, 2, 3), (v9.x, v9.y, v9.z), 4)

        with self.assertRaises(TypeError):
            Vector("vector")
        with self.assertRaises(TypeError):
            Vector(1, 2, 3, 4)

    def testVertex(self):
        """
        Tests basic vertex functions
        """
        v = Vertex.makeVertex(1, 1, 1)
        self.assertEqual(1, v.X)
        self.assertEqual(Vector, type(v.Center()))

    def testBasicBoundingBox(self):
        v = Vertex.makeVertex(1, 1, 1)
        v2 = Vertex.makeVertex(2, 2, 2)
        self.assertEqual(BoundBox, type(v.BoundingBox()))
        self.assertEqual(BoundBox, type(v2.BoundingBox()))

        bb1 = v.BoundingBox().add(v2.BoundingBox())

        # OCC uses some approximations
        self.assertAlmostEqual(bb1.xlen, 1.0, 1)

        # Test adding to an existing bounding box
        v0 = Vertex.makeVertex(0, 0, 0)
        bb2 = v0.BoundingBox().add(v.BoundingBox())

        bb3 = bb1.add(bb2)
        self.assertTupleAlmostEquals((2, 2, 2), (bb3.xlen, bb3.ylen, bb3.zlen), 7)

        bb3 = bb2.add((3, 3, 3))
        self.assertTupleAlmostEquals((3, 3, 3), (bb3.xlen, bb3.ylen, bb3.zlen), 7)

        bb3 = bb2.add(Vector(3, 3, 3))
        self.assertTupleAlmostEquals((3, 3, 3), (bb3.xlen, bb3.ylen, bb3.zlen), 7)

        # Test 2D bounding boxes
        bb1 = (
            Vertex.makeVertex(1, 1, 0)
            .BoundingBox()
            .add(Vertex.makeVertex(2, 2, 0).BoundingBox())
        )
        bb2 = (
            Vertex.makeVertex(0, 0, 0)
            .BoundingBox()
            .add(Vertex.makeVertex(3, 3, 0).BoundingBox())
        )
        bb3 = (
            Vertex.makeVertex(0, 0, 0)
            .BoundingBox()
            .add(Vertex.makeVertex(1.5, 1.5, 0).BoundingBox())
        )
        # Test that bb2 contains bb1
        self.assertEqual(bb2, BoundBox.findOutsideBox2D(bb1, bb2))
        self.assertEqual(bb2, BoundBox.findOutsideBox2D(bb2, bb1))
        # Test that neither bounding box contains the other
        self.assertIsNone(BoundBox.findOutsideBox2D(bb1, bb3))

        # Test creation of a bounding box from a shape - note the low accuracy comparison
        # as the box is a little larger than the shape
        bb1 = BoundBox._fromTopoDS(Solid.makeCylinder(1, 1).wrapped, optimal=False)
        self.assertTupleAlmostEquals((2, 2, 1), (bb1.xlen, bb1.ylen, bb1.zlen), 1)

    def testEdgeWrapperCenter(self):
        e = self._make_circle()

        self.assertTupleAlmostEquals((1.0, 2.0, 3.0), e.Center().toTuple(), 3)

    def testEdgeWrapperEllipseCenter(self):
        e = self._make_ellipse()
        w = Wire.assembleEdges([e])
        self.assertTupleAlmostEquals(
            (1.0, 2.0, 3.0), Face.makeFromWires(w).Center().toTuple(), 3
        )

    def testEdgeWrapperMakeCircle(self):
        halfCircleEdge = Edge.makeCircle(
            radius=10, pnt=(0, 0, 0), dir=(0, 0, 1), angle1=0, angle2=180
        )

        # self.assertTupleAlmostEquals((0.0, 5.0, 0.0), halfCircleEdge.CenterOfBoundBox(0.0001).toTuple(),3)
        self.assertTupleAlmostEquals(
            (10.0, 0.0, 0.0), halfCircleEdge.startPoint().toTuple(), 3
        )
        self.assertTupleAlmostEquals(
            (-10.0, 0.0, 0.0), halfCircleEdge.endPoint().toTuple(), 3
        )

    def testEdgeWrapperMakeTangentArc(self):
        tangent_arc = Edge.makeTangentArc(
            Vector(1, 1),  # starts at 1, 1
            Vector(0, 1),  # tangent at start of arc is in the +y direction
            Vector(2, 1),  # arc curves 180 degrees and ends at 2, 1
        )
        self.assertTupleAlmostEquals((1, 1, 0), tangent_arc.startPoint().toTuple(), 3)
        self.assertTupleAlmostEquals((2, 1, 0), tangent_arc.endPoint().toTuple(), 3)
        self.assertTupleAlmostEquals(
            (0, 1, 0), tangent_arc.tangentAt(locationParam=0).toTuple(), 3
        )
        self.assertTupleAlmostEquals(
            (1, 0, 0), tangent_arc.tangentAt(locationParam=0.5).toTuple(), 3
        )
        self.assertTupleAlmostEquals(
            (0, -1, 0), tangent_arc.tangentAt(locationParam=1).toTuple(), 3
        )

    def testEdgeWrapperMakeEllipse1(self):
        # Check x_radius > y_radius
        x_radius, y_radius = 20, 10
        angle1, angle2 = -75.0, 90.0
        arcEllipseEdge = Edge.makeEllipse(
            x_radius=x_radius,
            y_radius=y_radius,
            pnt=(0, 0, 0),
            dir=(0, 0, 1),
            angle1=angle1,
            angle2=angle2,
        )

        start = (
            x_radius * math.cos(angle1 * DEG2RAD),
            y_radius * math.sin(angle1 * DEG2RAD),
            0.0,
        )
        end = (
            x_radius * math.cos(angle2 * DEG2RAD),
            y_radius * math.sin(angle2 * DEG2RAD),
            0.0,
        )
        self.assertTupleAlmostEquals(start, arcEllipseEdge.startPoint().toTuple(), 3)
        self.assertTupleAlmostEquals(end, arcEllipseEdge.endPoint().toTuple(), 3)

    def testEdgeWrapperMakeEllipse2(self):
        # Check x_radius < y_radius
        x_radius, y_radius = 10, 20
        angle1, angle2 = 0.0, 45.0
        arcEllipseEdge = Edge.makeEllipse(
            x_radius=x_radius,
            y_radius=y_radius,
            pnt=(0, 0, 0),
            dir=(0, 0, 1),
            angle1=angle1,
            angle2=angle2,
        )

        start = (
            x_radius * math.cos(angle1 * DEG2RAD),
            y_radius * math.sin(angle1 * DEG2RAD),
            0.0,
        )
        end = (
            x_radius * math.cos(angle2 * DEG2RAD),
            y_radius * math.sin(angle2 * DEG2RAD),
            0.0,
        )
        self.assertTupleAlmostEquals(start, arcEllipseEdge.startPoint().toTuple(), 3)
        self.assertTupleAlmostEquals(end, arcEllipseEdge.endPoint().toTuple(), 3)

    def testEdgeWrapperMakeCircleWithEllipse(self):
        # Check x_radius == y_radius
        x_radius, y_radius = 20, 20
        angle1, angle2 = 15.0, 60.0
        arcEllipseEdge = Edge.makeEllipse(
            x_radius=x_radius,
            y_radius=y_radius,
            pnt=(0, 0, 0),
            dir=(0, 0, 1),
            angle1=angle1,
            angle2=angle2,
        )

        start = (
            x_radius * math.cos(angle1 * DEG2RAD),
            y_radius * math.sin(angle1 * DEG2RAD),
            0.0,
        )
        end = (
            x_radius * math.cos(angle2 * DEG2RAD),
            y_radius * math.sin(angle2 * DEG2RAD),
            0.0,
        )
        self.assertTupleAlmostEquals(start, arcEllipseEdge.startPoint().toTuple(), 3)
        self.assertTupleAlmostEquals(end, arcEllipseEdge.endPoint().toTuple(), 3)

    def testFaceWrapperMakePlane(self):
        mplane = Face.makePlane(10, 10)

        self.assertTupleAlmostEquals((0.0, 0.0, 1.0), mplane.normalAt().toTuple(), 3)

    def testMatrixOfInertia(self):
        """
        Tests the calculation of the matrix of inertia for a solid
        """
        radius = 1.0
        height = 2.0
        cylinder = Solid.makeCylinder(radius=radius, height=height)
        moi = Shape.matrixOfInertia(cylinder)
        two_pi = 2 * math.pi
        true_moi = (
            two_pi * (radius ** 2 / 4 + height ** 2 / 12),
            two_pi * (radius ** 2 / 4 + height ** 2 / 12),
            two_pi * radius ** 2 / 2,
        )
        self.assertTupleAlmostEquals((moi[0][0], moi[1][1], moi[2][2]), true_moi, 3)

    def testVertexMatrixOfInertiaNotImplemented(self):
        with self.assertRaises(NotImplementedError):
            vertex = Vertex.makeVertex(1, 1, 1)
            Shape.matrixOfInertia(vertex)

    def testCenterOfBoundBox(self):
        pass

    def testCombinedCenterOfBoundBox(self):
        pass

    def testCompoundCenter(self):
        """
        Tests whether or not a proper weighted center can be found for a compound
        """

        def cylinders(self, radius, height):

            c = Solid.makeCylinder(radius, height, Vector())

            # Combine all the cylinders into a single compound
            r = self.eachpoint(lambda loc: c.located(loc), True).union()

            return r

        Workplane.cyl = cylinders

        # Now test. here we want weird workplane to see if the objects are transformed right
        s = (
            Workplane("XY")
            .rect(2.0, 3.0, forConstruction=True)
            .vertices()
            .cyl(0.25, 0.5)
        )

        self.assertEqual(4, len(s.val().Solids()))
        self.assertTupleAlmostEquals((0.0, 0.0, 0.25), s.val().Center().toTuple(), 3)

    def testDot(self):
        v1 = Vector(2, 2, 2)
        v2 = Vector(1, -1, 1)
        self.assertEqual(2.0, v1.dot(v2))

    def testVectorAdd(self):
        result = Vector(1, 2, 0) + Vector(0, 0, 3)
        self.assertTupleAlmostEquals((1.0, 2.0, 3.0), result.toTuple(), 3)

    def testVectorOperators(self):
        result = Vector(1, 1, 1) + Vector(2, 2, 2)
        self.assertEqual(Vector(3, 3, 3), result)

        result = Vector(1, 2, 3) - Vector(3, 2, 1)
        self.assertEqual(Vector(-2, 0, 2), result)

        result = Vector(1, 2, 3) * 2
        self.assertEqual(Vector(2, 4, 6), result)

        result = 3 * Vector(1, 2, 3)
        self.assertEqual(Vector(3, 6, 9), result)

        result = Vector(2, 4, 6) / 2
        self.assertEqual(Vector(1, 2, 3), result)

        self.assertEqual(Vector(-1, -1, -1), -Vector(1, 1, 1))

        self.assertEqual(0, abs(Vector(0, 0, 0)))
        self.assertEqual(1, abs(Vector(1, 0, 0)))
        self.assertEqual((1 + 4 + 9) ** 0.5, abs(Vector(1, 2, 3)))

    def testVectorEquals(self):
        a = Vector(1, 2, 3)
        b = Vector(1, 2, 3)
        c = Vector(1, 2, 3.000001)
        self.assertEqual(a, b)
        self.assertEqual(a, c)

    def testVectorProject(self):
        """
        Test line projection and plane projection methods of cq.Vector
        """
        decimal_places = 9

        normal = Vector(1, 2, 3)
        base = Vector(5, 7, 9)
        x_dir = Vector(1, 0, 0)

        # test passing Plane object
        point = Vector(10, 11, 12).projectToPlane(Plane(base, x_dir, normal))
        self.assertTupleAlmostEquals(
            point.toTuple(), (59 / 7, 55 / 7, 51 / 7), decimal_places
        )

        # test line projection
        vec = Vector(10, 10, 10)
        line = Vector(3, 4, 5)
        angle = vec.getAngle(line)

        vecLineProjection = vec.projectToLine(line)

        self.assertTupleAlmostEquals(
            vecLineProjection.normalized().toTuple(),
            line.normalized().toTuple(),
            decimal_places,
        )
        self.assertAlmostEqual(
            vec.Length * math.cos(angle), vecLineProjection.Length, decimal_places
        )

    def testVectorNotImplemented(self):
        v = Vector(1, 2, 3)
        with self.assertRaises(NotImplementedError):
            v.distanceToLine()
        with self.assertRaises(NotImplementedError):
            v.distanceToPlane()

    def testVectorSpecialMethods(self):
        v = Vector(1, 2, 3)
        self.assertEqual(repr(v), "Vector: (1.0, 2.0, 3.0)")
        self.assertEqual(str(v), "Vector: (1.0, 2.0, 3.0)")

    def testMatrixCreationAndAccess(self):
        def matrix_vals(m):
            return [[m[r, c] for c in range(4)] for r in range(4)]

        # default constructor creates a 4x4 identity matrix
        m = Matrix()
        identity = [
            [1.0, 0.0, 0.0, 0.0],
            [0.0, 1.0, 0.0, 0.0],
            [0.0, 0.0, 1.0, 0.0],
            [0.0, 0.0, 0.0, 1.0],
        ]
        self.assertEqual(identity, matrix_vals(m))

        vals4x4 = [
            [1.0, 0.0, 0.0, 1.0],
            [0.0, 1.0, 0.0, 2.0],
            [0.0, 0.0, 1.0, 3.0],
            [0.0, 0.0, 0.0, 1.0],
        ]
        vals4x4_tuple = tuple(tuple(r) for r in vals4x4)

        # test constructor with 16-value input
        m = Matrix(vals4x4)
        self.assertEqual(vals4x4, matrix_vals(m))
        m = Matrix(vals4x4_tuple)
        self.assertEqual(vals4x4, matrix_vals(m))

        # test constructor with 12-value input (the last 4 are an implied
        # [0,0,0,1])
        m = Matrix(vals4x4[:3])
        self.assertEqual(vals4x4, matrix_vals(m))
        m = Matrix(vals4x4_tuple[:3])
        self.assertEqual(vals4x4, matrix_vals(m))

        # Test 16-value input with invalid values for the last 4
        invalid = [
            [1.0, 0.0, 0.0, 1.0],
            [0.0, 1.0, 0.0, 2.0],
            [0.0, 0.0, 1.0, 3.0],
            [1.0, 2.0, 3.0, 4.0],
        ]
        with self.assertRaises(ValueError):
            Matrix(invalid)
        # Test input with invalid type
        with self.assertRaises(TypeError):
            Matrix("invalid")
        # Test input with invalid size / nested types
        with self.assertRaises(TypeError):
            Matrix([[1, 2, 3, 4], [1, 2, 3], [1, 2, 3, 4]])
        with self.assertRaises(TypeError):
            Matrix([1, 2, 3])

        # Invalid sub-type
        with self.assertRaises(TypeError):
            Matrix([[1, 2, 3, 4], "abc", [1, 2, 3, 4]])

        # test out-of-bounds access
        m = Matrix()
        with self.assertRaises(IndexError):
            m[0, 4]
        with self.assertRaises(IndexError):
            m[4, 0]
        with self.assertRaises(IndexError):
            m["ab"]

        # test __repr__ methods
        m = Matrix(vals4x4)
        mRepr = "Matrix([[1.0, 0.0, 0.0, 1.0],\n        [0.0, 1.0, 0.0, 2.0],\n        [0.0, 0.0, 1.0, 3.0],\n        [0.0, 0.0, 0.0, 1.0]])"
        self.assertEqual(repr(m), mRepr)
        self.assertEqual(str(eval(repr(m))), mRepr)

    def testMatrixFunctionality(self):
        # Test rotate methods
        def matrix_almost_equal(m, target_matrix):
            for r, row in enumerate(target_matrix):
                for c, target_value in enumerate(row):
                    self.assertAlmostEqual(m[r, c], target_value)

        root_3_over_2 = math.sqrt(3) / 2
        m_rotate_x_30 = [
            [1, 0, 0, 0],
            [0, root_3_over_2, -1 / 2, 0],
            [0, 1 / 2, root_3_over_2, 0],
            [0, 0, 0, 1],
        ]
        mx = Matrix()
        mx.rotateX(30 * DEG2RAD)
        matrix_almost_equal(mx, m_rotate_x_30)

        m_rotate_y_30 = [
            [root_3_over_2, 0, 1 / 2, 0],
            [0, 1, 0, 0],
            [-1 / 2, 0, root_3_over_2, 0],
            [0, 0, 0, 1],
        ]
        my = Matrix()
        my.rotateY(30 * DEG2RAD)
        matrix_almost_equal(my, m_rotate_y_30)

        m_rotate_z_30 = [
            [root_3_over_2, -1 / 2, 0, 0],
            [1 / 2, root_3_over_2, 0, 0],
            [0, 0, 1, 0],
            [0, 0, 0, 1],
        ]
        mz = Matrix()
        mz.rotateZ(30 * DEG2RAD)
        matrix_almost_equal(mz, m_rotate_z_30)

        # Test matrix multipy vector
        v = Vector(1, 0, 0)
        self.assertTupleAlmostEquals(
            mz.multiply(v).toTuple(), (root_3_over_2, 1 / 2, 0), 7
        )

        # Test matrix multipy matrix
        m_rotate_xy_30 = [
            [root_3_over_2, 0, 1 / 2, 0],
            [1 / 4, root_3_over_2, -root_3_over_2 / 2, 0],
            [-root_3_over_2 / 2, 1 / 2, 3 / 4, 0],
            [0, 0, 0, 1],
        ]
        mxy = mx.multiply(my)
        matrix_almost_equal(mxy, m_rotate_xy_30)

        # Test matrix inverse
        vals4x4 = [[1, 2, 3, 4], [5, 1, 6, 7], [8, 9, 1, 10], [0, 0, 0, 1]]
        vals4x4_invert = [
            [-53 / 144, 25 / 144, 1 / 16, -53 / 144],
            [43 / 144, -23 / 144, 1 / 16, -101 / 144],
            [37 / 144, 7 / 144, -1 / 16, -107 / 144],
            [0, 0, 0, 1],
        ]
        m = Matrix(vals4x4).inverse()
        matrix_almost_equal(m, vals4x4_invert)

    def testTranslate(self):
        e = Edge.makeCircle(2, (1, 2, 3))
        e2 = e.translate(Vector(0, 0, 1))

        self.assertTupleAlmostEquals((1.0, 2.0, 4.0), e2.Center().toTuple(), 3)

    def testVertices(self):
        e = Shape.cast(BRepBuilderAPI_MakeEdge(gp_Pnt(0, 0, 0), gp_Pnt(1, 1, 0)).Edge())
        self.assertEqual(2, len(e.Vertices()))

    def testPlaneEqual(self):
        # default orientation
        self.assertEqual(
            Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)),
            Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)),
        )
        # moved origin
        self.assertEqual(
            Plane(origin=(2, 1, -1), xDir=(1, 0, 0), normal=(0, 0, 1)),
            Plane(origin=(2, 1, -1), xDir=(1, 0, 0), normal=(0, 0, 1)),
        )
        # moved x-axis
        self.assertEqual(
            Plane(origin=(0, 0, 0), xDir=(1, 1, 0), normal=(0, 0, 1)),
            Plane(origin=(0, 0, 0), xDir=(1, 1, 0), normal=(0, 0, 1)),
        )
        # moved z-axis
        self.assertEqual(
            Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 1, 1)),
            Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 1, 1)),
        )

    def testPlaneNotEqual(self):
        # type difference
        for value in [None, 0, 1, "abc"]:
            self.assertNotEqual(
                Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)), value
            )
        # origin difference
        self.assertNotEqual(
            Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)),
            Plane(origin=(0, 0, 1), xDir=(1, 0, 0), normal=(0, 0, 1)),
        )
        # x-axis difference
        self.assertNotEqual(
            Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)),
            Plane(origin=(0, 0, 0), xDir=(1, 1, 0), normal=(0, 0, 1)),
        )
        # z-axis difference
        self.assertNotEqual(
            Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)),
            Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 1, 1)),
        )

    def testInvalidPlane(self):
        # Test plane creation error handling
        with self.assertRaises(ValueError):
            Plane.named("XX", (0, 0, 0))
        with self.assertRaises(ValueError):
            Plane(origin=(0, 0, 0), xDir=(0, 0, 0), normal=(0, 1, 1))
        with self.assertRaises(ValueError):
            Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 0))

    def testPlaneMethods(self):
        # Test error checking
        p = Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 1, 0))
        with self.assertRaises(ValueError):
            p.toLocalCoords("box")
        with self.assertRaises(NotImplementedError):
            p.mirrorInPlane([Solid.makeBox(1, 1, 1)], "Z")

        # Test translation to local coordinates
        local_box = Workplane(p.toLocalCoords(Solid.makeBox(1, 1, 1)))
        local_box_vertices = [(v.X, v.Y, v.Z) for v in local_box.vertices().vals()]
        target_vertices = [
            (0, -1, 0),
            (0, 0, 0),
            (0, -1, 1),
            (0, 0, 1),
            (1, -1, 0),
            (1, 0, 0),
            (1, -1, 1),
            (1, 0, 1),
        ]
        for i, target_point in enumerate(target_vertices):
            self.assertTupleAlmostEquals(target_point, local_box_vertices[i], 7)

        # Test mirrorInPlane
        mirror_box = Workplane(p.mirrorInPlane([Solid.makeBox(1, 1, 1)], "Y")[0])
        mirror_box_vertices = [(v.X, v.Y, v.Z) for v in mirror_box.vertices().vals()]
        target_vertices = [
            (0, 0, 1),
            (0, 0, 0),
            (0, -1, 1),
            (0, -1, 0),
            (-1, 0, 1),
            (-1, 0, 0),
            (-1, -1, 1),
            (-1, -1, 0),
        ]
        for i, target_point in enumerate(target_vertices):
            self.assertTupleAlmostEquals(target_point, mirror_box_vertices[i], 7)

    def testLocation(self):

        # empty
        loc = Location()

        T = loc.wrapped.Transformation().TranslationPart()
        self.assertTupleAlmostEquals((T.X(), T.Y(), T.Z()), (0, 0, 0), 6)

        # Tuple
        loc0 = Location((0, 0, 1))

        T = loc0.wrapped.Transformation().TranslationPart()
        self.assertTupleAlmostEquals((T.X(), T.Y(), T.Z()), (0, 0, 1), 6)

        # Vector
        loc1 = Location(Vector(0, 0, 1))

        T = loc1.wrapped.Transformation().TranslationPart()
        self.assertTupleAlmostEquals((T.X(), T.Y(), T.Z()), (0, 0, 1), 6)

        # rotation + translation
        loc2 = Location(Vector(0, 0, 1), Vector(0, 0, 1), 45)

        angle = loc2.wrapped.Transformation().GetRotation().GetRotationAngle() * RAD2DEG
        self.assertAlmostEqual(45, angle)

        # gp_Trsf
        T = gp_Trsf()
        T.SetTranslation(gp_Vec(0, 0, 1))
        loc3 = Location(T)

        assert (
            loc1.wrapped.Transformation().TranslationPart().Z()
            == loc3.wrapped.Transformation().TranslationPart().Z()
        )

        # Test creation from the OCP.gp.gp_Trsf object
        loc4 = Location(gp_Trsf())
        self.assertTupleAlmostEquals(loc4.toTuple()[0], (0, 0, 0), 7)
        self.assertTupleAlmostEquals(loc4.toTuple()[1], (0, 0, 0), 7)

        # Test composition
        loc4 = Location((0, 0, 0), Vector(0, 0, 1), 15)

        loc5 = loc1 * loc4
        loc6 = loc4 * loc4
        loc7 = loc4 ** 2

        T = loc5.wrapped.Transformation().TranslationPart()
        self.assertTupleAlmostEquals((T.X(), T.Y(), T.Z()), (0, 0, 1), 6)

        angle5 = (
            loc5.wrapped.Transformation().GetRotation().GetRotationAngle() * RAD2DEG
        )
        self.assertAlmostEqual(15, angle5)

        angle6 = (
            loc6.wrapped.Transformation().GetRotation().GetRotationAngle() * RAD2DEG
        )
        self.assertAlmostEqual(30, angle6)

        angle7 = (
            loc7.wrapped.Transformation().GetRotation().GetRotationAngle() * RAD2DEG
        )
        self.assertAlmostEqual(30, angle7)

        # Test error handling on creation
        with self.assertRaises(TypeError):
            Location([0, 0, "a"])
        with self.assertRaises(TypeError):
            Location("xy_plane")

        # test to tuple
        loc8 = Location(z=2, ry=15)

        trans, rot = loc8.toTuple()

        self.assertTupleAlmostEquals(trans, (0, 0, 2), 6)
        self.assertTupleAlmostEquals(rot, (0, 15, 0), 6)

    def testEdgeWrapperRadius(self):

        # get a radius from a simple circle
        e0 = Edge.makeCircle(2.4)
        self.assertAlmostEqual(e0.radius(), 2.4)

        # radius of an arc
        e1 = Edge.makeCircle(1.8, pnt=(5, 6, 7), dir=(1, 1, 1), angle1=20, angle2=30)
        self.assertAlmostEqual(e1.radius(), 1.8)

        # test value errors
        e2 = Edge.makeEllipse(10, 20)
        with self.assertRaises(ValueError):
            e2.radius()

        # radius from a wire
        w0 = Wire.makeCircle(10, Vector(1, 2, 3), (-1, 0, 1))
        self.assertAlmostEqual(w0.radius(), 10)

        # radius from a wire with multiple edges
        rad = 2.3
        pnt = (7, 8, 9)
        direction = (1, 0.5, 0.1)
        w1 = Wire.assembleEdges(
            [
                Edge.makeCircle(rad, pnt, direction, 0, 10),
                Edge.makeCircle(rad, pnt, direction, 10, 25),
                Edge.makeCircle(rad, pnt, direction, 25, 230),
            ]
        )
        self.assertAlmostEqual(w1.radius(), rad)

        # test value error from wire
        w2 = Wire.makePolygon([Vector(-1, 0, 0), Vector(0, 1, 0), Vector(1, -1, 0),])
        with self.assertRaises(ValueError):
            w2.radius()

        # (I think) the radius of a wire is the radius of it's first edge.
        # Since this is stated in the docstring better make sure.
        no_rad = Wire.assembleEdges(
            [
                Edge.makeLine(Vector(0, 0, 0), Vector(0, 1, 0)),
                Edge.makeCircle(1.0, angle1=90, angle2=270),
            ]
        )
        with self.assertRaises(ValueError):
            no_rad.radius()
        yes_rad = Wire.assembleEdges(
            [
                Edge.makeCircle(1.0, angle1=90, angle2=270),
                Edge.makeLine(Vector(0, -1, 0), Vector(0, 1, 0)),
            ]
        )
        self.assertAlmostEqual(yes_rad.radius(), 1.0)
        many_rad = Wire.assembleEdges(
            [
                Edge.makeCircle(1.0, angle1=0, angle2=180),
                Edge.makeCircle(3.0, pnt=Vector(2, 0, 0), angle1=180, angle2=359),
            ]
        )
        self.assertAlmostEqual(many_rad.radius(), 1.0)

    def testWireFillet(self):
        points = [
            (0.000, 0.000, 0.000),
            (-0.287, 1.183, -0.592),
            (-1.404, 4.113, -2.787),
            (-1.332, 1.522, 0.553),
            (7.062, 0.433, -0.097),
            (8.539, -0.000, -0.000),
        ]
        wire = Wire.makePolygon(points, close=False)

        # Fillet the wire
        wfillet = wire.fillet(radius=0.560)
        assert len(wfillet.Edges()) == 2 * len(points) - 3

        # Fillet a single vertex
        wfillet = wire.fillet(radius=0.560, vertices=wire.Vertices()[1:2])
        assert len(wfillet.Edges()) == len(points)

        # Assert exception if trying to fillet with too big
        # a radius
        with self.assertRaises(ValueError):
            wfillet = wire.fillet(radius=1.0)

        # Test a closed fillet
        points = [[0, 0, 0], [5, 4, 0], [8, 3, 1], [10, 0, 0]]

        wire = Wire.makePolygon(points, close=True)
        wfillet = wire.fillet(radius=0.5)
        assert len(wfillet.Edges()) == 2 * len(points)

        # Fillet a single vertex
        wfillet = wire.fillet(radius=0.5, vertices=wire.Vertices()[0:1])
        assert len(wfillet.Edges()) == len(points) + 1


@pytest.mark.parametrize(
    "points, close, expected_edges",
    [
        (((0, 0, 0), (0, 1, 0), (1, 0, 0)), False, 2),
        (((0, 0, 0), (0, 1, 0), (1, 0, 0)), True, 3),
        (((0, 0, 0), (0, 1, 0), (1, 0, 0), (0, 0, 0)), False, 3),
        (((0, 0, 0), (0, 1, 0), (1, 0, 0), (0, 0, 0)), True, 3),
    ],
)
def test_wire_makepolygon(points, close, expected_edges):
    assert len(Wire.makePolygon(points, False, close).Edges()) == expected_edges


def test_equality():

    # do not raise error comparing with other type
    assert (Vector(0, 0, 0) == 0) == False
    assert (Plane.XY() == 0) == False

    list1 = [
        Vector(0, 0, 0),
        Plane.XY(),
        Vertex.makeVertex(0, 0, 0),
        "a string",
        4,
    ]
    assert [list1.index(item) for item in list1] == [0, 1, 2, 3, 4]


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