from lib.elements import Clone, EmbroideryElement
from lib.svg.tags import INKSTITCH_ATTRIBS, SVG_RECT_TAG
from inkex import SvgDocumentElement, Rectangle, Circle, Group, Use, Transform, TextElement
from inkex.tester import TestCase
from inkex.tester.svg import svg

from typing import Optional

from math import sqrt


def element_fill_angle(element: EmbroideryElement) -> Optional[float]:
    angle = element.node.get(INKSTITCH_ATTRIBS['angle'])
    if angle is not None:
        angle = float(angle)
    return angle


class CloneElementTest(TestCase):
    def assertAngleAlmostEqual(self, a, b):
        # Take the mod 180 of the returned angles, because e.g. -130deg and 50deg produce fills along the same angle.
        # We have to use a precision of 4 decimal digits because of the precision of the matrices as they are stored in the svg trees
        # generated by these tests.
        self.assertAlmostEqual(a % 180, b % 180, 4)

    def test_not_embroiderable(self):
        root: SvgDocumentElement = svg()
        text = root.add(TextElement())
        text.text = "Can't embroider this!"
        use = root.add(Use())
        use.href = text

        clone = Clone(use)
        stitch_groups = clone.to_stitch_groups(None)
        self.assertEqual(len(stitch_groups), 0)

    # These tests make sure the element cloning works as expected, using the `clone_elements` method.

    def test_basic(self):
        root: SvgDocumentElement = svg()
        rect = root.add(Rectangle(attrib={
            "width": "10",
            "height": "10",
            INKSTITCH_ATTRIBS["angle"]: "30"
        }))
        use = root.add(Use())
        use.href = rect

        clone = Clone(use)
        with clone.clone_elements() as elements:
            self.assertEqual(len(elements), 1)
            self.assertAlmostEqual(element_fill_angle(elements[0]), 30)

    def test_angle_rotated(self):
        root: SvgDocumentElement = svg()
        rect = root.add(Rectangle(attrib={
            "width": "10",
            "height": "10",
            INKSTITCH_ATTRIBS["angle"]: "30"
        }))
        use = root.add(Use())
        use.href = rect
        use.set('transform', Transform().add_rotate(20))

        clone = Clone(use)
        with clone.clone_elements() as elements:
            self.assertEqual(len(elements), 1)
            self.assertAngleAlmostEqual(element_fill_angle(elements[0]), 10)

    def test_angle_flipped(self):
        root: SvgDocumentElement = svg()
        rect = root.add(Rectangle(attrib={
            "width": "10",
            "height": "10",
            INKSTITCH_ATTRIBS["angle"]: "30"
        }))
        use = root.add(Use())
        use.href = rect
        use.set('transform', Transform().add_scale(-1, 1))

        clone = Clone(use)
        with clone.clone_elements() as elements:
            self.assertEqual(len(elements), 1)
            self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -30)

    def test_angle_flipped_rotated(self):
        root: SvgDocumentElement = svg()
        rect = root.add(Rectangle(attrib={
            "width": "10",
            "height": "10",
            INKSTITCH_ATTRIBS["angle"]: "30"
        }))
        use = root.add(Use())
        use.href = rect
        use.set('transform', Transform().add_rotate(20).add_scale(-1, 1))

        clone = Clone(use)
        with clone.clone_elements() as elements:
            self.assertEqual(len(elements), 1)
            # Fill angle goes from 30 -> -30 after flip -> -50 after rotate
            self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -50)

    def test_angle_non_uniform_scale(self):
        """
        The angle isn't *as* well-defined for non-rotational scales, but we try to follow how the slope will be altered.
        """
        root: SvgDocumentElement = svg()
        rect = root.add(Rectangle(attrib={
            "width": "10",
            "height": "10",
            INKSTITCH_ATTRIBS["angle"]: "30"
        }))
        use = root.add(Use())
        use.href = rect
        use.set('transform', Transform().add_rotate(10).add_scale(1, -sqrt(3)))

        clone = Clone(use)
        with clone.clone_elements() as elements:
            self.assertEqual(len(elements), 1)
            # Slope of the stitching goes from tan(30deg) = 1/sqrt(3) to -sqrt(3)/sqrt(3) = tan(-45deg),
            # then rotated another -10 degrees to -55
            self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -55)

    def test_angle_inherits_down_tree(self):
        """
        The stitching angle of a clone is based in part on the relative transforms of the source and clone.
        """
        root: SvgDocumentElement = svg()
        g1 = root.add(Group())
        g1.set('transform', Transform().add_rotate(3))
        rect = g1.add(Rectangle(attrib={
            "width": "10",
            "height": "10",
            INKSTITCH_ATTRIBS["angle"]: "30"
        }))
        g2 = root.add(Group())
        g2.set('transform', Transform().add_translate((20, 0)).add_rotate(-7))
        use = g2.add(Use())
        use.href = rect
        use.set('transform', Transform().add_rotate(11))

        clone = Clone(use)
        with clone.clone_elements() as elements:
            self.assertEqual(len(elements), 1)
            # Angle goes from 30 -> 40 (g1 -> g2) -> 29 (use)
            self.assertAngleAlmostEqual(element_fill_angle(elements[0]), 29)

    def test_angle_not_applied_twice(self):
        """Make sure that angle changes are not applied twice to an element with both stroke and fill."""
        root: SvgDocumentElement = svg()
        rect = root.add(Rectangle(attrib={
            "width": "10",
            "height": "10",
            "style": "stroke: skyblue; fill: red;"
        }))
        use = root.add(Use())
        use.href = rect
        use.set('transform', Transform().add_rotate(30))

        clone = Clone(use)
        with clone.clone_elements() as elements:
            self.assertEqual(len(elements), 2)  # One for the stroke, one for the fill
            self.assertEqual(elements[0].node, elements[1].node)
            # Angle goes from 0 -> -30
            self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -30)

    def test_style_inherits(self):
        root: SvgDocumentElement = svg()
        rect = root.add(Rectangle(attrib={
            "width": "10",
            "height": "10"
        }))
        rect.set('style', 'stroke: skyblue; fill-opacity: 0;')
        use = root.add(Use())
        use.href = rect
        use.set('style', 'stroke: red; stroke-width: 2;')

        clone = Clone(use)
        with clone.clone_elements() as elements:
            self.assertEqual(len(elements), 1)
            style = elements[0].node.cascaded_style()
            # Source style takes precedence over any attributes specified in the clone
            self.assertEqual(style["stroke"], "skyblue")
            self.assertEqual(style["stroke-width"], "2")

    def test_transform_inherits_from_cloned_element(self):
        """
        Elements cloned by cloned_elements need to inherit their transform from their href'd element and their use to match what's shown.
        """
        root: SvgDocumentElement = svg()
        rect = root.add(Rectangle(attrib={
            "width": "10",
            "height": "10",
            INKSTITCH_ATTRIBS["angle"]: "30",
        }))
        rect.set('transform', Transform().add_scale(2, 2))
        use = root.add(Use())
        use.href = rect
        use.set('transform', Transform().add_translate((5, 10)))

        clone = Clone(use)
        with clone.clone_elements() as elements:
            self.assertEqual(len(elements), 1)
            self.assertTransformEqual(
                elements[0].node.composed_transform(),
                Transform().add_translate((5, 10)).add_scale(2, 2))

    def test_transform_inherits_from_tree(self):
        root: SvgDocumentElement = svg()
        g1 = root.add(Group())
        g1.set('transform', Transform().add_translate((0, 5)).add_rotate(5))
        rect = g1.add(Rectangle(attrib={
            "width": "10",
            "height": "10",
            INKSTITCH_ATTRIBS["angle"]: "30",
        }))
        rect.set('transform', Transform().add_scale(2, 2))
        use = root.add(Use())
        use.href = g1
        use.set('transform', Transform().add_translate((5, 10)))

        clone = Clone(use)
        with clone.clone_elements() as elements:
            self.assertEqual(len(elements), 1)
            self.assertTransformEqual(
                elements[0].node.composed_transform(),
                Transform().add_translate((5, 10))  # use
                .add_translate((0, 5)).add_rotate(5)  # g1
                .add_scale(2, 2),  # rect
                5)

    def test_transform_inherits_from_tree_up_tree(self):
        root: SvgDocumentElement = svg()
        g1 = root.add(Group())
        g1.set('transform', Transform().add_translate((0, 5)).add_rotate(5))
        rect = g1.add(Rectangle(attrib={
            "width": "10",
            "height": "10",
            INKSTITCH_ATTRIBS["angle"]: "30",
        }))
        rect.set('transform', Transform().add_scale(2, 2))
        circ = g1.add(Circle())
        circ.radius = 5
        g2 = root.add(Group())
        g2.set('transform', Transform().add_translate((1, 2)).add_scale(0.5, 1))
        use = g2.add(Use())
        use.href = g1
        use.set('transform', Transform().add_translate((5, 10)))

        clone = Clone(use)
        with clone.clone_elements() as elements:
            self.assertEqual(len(elements), 2)
            self.assertTransformEqual(
                elements[0].node.composed_transform(),
                Transform().add_translate((1, 2)).add_scale(0.5, 1)  # g2
                .add_translate((5, 10))  # use
                .add_translate((0, 5)).add_rotate(5)  # g1
                .add_scale(2, 2),  # rect
                5)
            self.assertTransformEqual(
                elements[1].node.composed_transform(),
                Transform().add_translate((1, 2)).add_scale(0.5, 1)  # g2
                .add_translate((5, 10))  # use
                .add_translate((0, 5)).add_rotate(5),  # g1
                5)

    def test_clone_fill_angle_not_specified(self):
        root: SvgDocumentElement = svg()
        rect = root.add(Rectangle(attrib={
            "width": "10",
            "height": "10",
            INKSTITCH_ATTRIBS["angle"]: "30"
        }))
        use = root.add(Use())
        use.href = rect
        use.set('transform', Transform().add_rotate(20))

        clone = Clone(use)
        self.assertEqual(clone.clone_fill_angle, None)

    def test_clone_fill_angle(self):
        root: SvgDocumentElement = svg()
        rect = root.add(Rectangle(attrib={
            "width": "10",
            "height": "10",
            INKSTITCH_ATTRIBS["angle"]: "30"
        }))
        use = root.add(Use())
        use.href = rect
        use.set(INKSTITCH_ATTRIBS["angle"], 42)
        use.set('transform', Transform().add_rotate(20))

        clone = Clone(use)
        self.assertEqual(clone.clone_fill_angle, 42)

        with clone.clone_elements() as elements:
            self.assertEqual(len(elements), 1)
            self.assertAngleAlmostEqual(element_fill_angle(elements[0]), 42)

    def test_angle_manually_flipped(self):
        root: SvgDocumentElement = svg()
        rect = root.add(Rectangle(attrib={
            "width": "10",
            "height": "10",
            INKSTITCH_ATTRIBS["angle"]: "30"
        }))
        use = root.add(Use())
        use.href = rect
        use.set('transform', Transform().add_rotate(20))
        use.set(INKSTITCH_ATTRIBS["flip_angle"], True)

        clone = Clone(use)
        self.assertTrue(clone.flip_angle)
        with clone.clone_elements() as elements:
            self.assertEqual(len(elements), 1)
            self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -10)

    def test_recursive_uses(self):
        root: SvgDocumentElement = svg()
        g1 = root.add(Group())
        rect = g1.add(Rectangle(attrib={
            "width": "10",
            "height": "10",
        }))
        u1 = g1.add(Use())
        u1.set('transform', Transform().add_translate((20, 0)))
        u1.href = rect
        u2 = root.add(Use())
        u2.set('transform', Transform().add_translate((0, 20)).add_scale(0.5, 0.5))
        u2.href = g1
        u3 = root.add(Use())
        u3.set('transform', Transform().add_translate((0, 30)))
        u3.href = u2

        clone = Clone(u3)
        with clone.clone_elements() as elements:
            # There should be two elements cloned from u3, two rects, one corresponding to rect and one corresponding to u1.
            # Their transforms should derive from the elements they href.
            self.assertEqual(len(elements), 2)
            self.assertEqual(elements[0].node.tag, SVG_RECT_TAG)
            self.assertTransformEqual(elements[0].node.composed_transform(),
                                      Transform().add_translate((0, 30))  # u3
                                      .add_translate(0, 20).add_scale(0.5, 0.5)  # u2
                                      )

            self.assertEqual(elements[1].node.tag, SVG_RECT_TAG)
            self.assertTransformEqual(elements[1].node.composed_transform(),
                                      Transform().add_translate((0, 30))  # u3
                                      .add_translate((0, 20)).add_scale(0.5, 0.5)  # u2
                                      .add_translate((20, 0))  # u1
                                      )

    def test_recursive_uses_angle(self):
        root: SvgDocumentElement = svg()
        rect = root.add(Rectangle(attrib={
            "width": "10",
            "height": "10",
            INKSTITCH_ATTRIBS["angle"]: "30"
        }))
        u1 = root.add(Use())
        u1.set('transform', Transform().add_rotate(60))
        u1.href = rect

        clone = Clone(u1)
        with clone.clone_elements() as elements:
            self.assertEqual(len(elements), 1)
            # Angle goes from 30 -> -30
            self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -30)

        g = root.add(Group())
        g.set('transform', Transform().add_rotate(-10))
        u2 = g.add(Use())
        u2.href = u1

        clone = Clone(u2)
        with clone.clone_elements() as elements:
            self.assertEqual(len(elements), 1)
            # Angle goes from -30 -> -20 (u1 -> g)
            self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -20)

        u3 = root.add(Use())
        u3.set('transform', Transform().add_rotate(7))
        u3.href = g

        clone = Clone(u3)
        with clone.clone_elements() as elements:
            self.assertEqual(len(elements), 1)
            # Angle goes from -20 -> -27
            self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -27)

        # Cloning u2 directly, the relative transform of g does not apply
        u4 = root.add(Use())
        u4.set('transform', Transform().add_rotate(7))
        u4.href = u2

        clone = Clone(u4)
        with clone.clone_elements() as elements:
            self.assertEqual(len(elements), 1)
            # Angle goes from -30 -> -37
            self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -37)

    def test_recursive_uses_angle_with_specified_angle(self):
        root: SvgDocumentElement = svg()
        rect = root.add(Rectangle(attrib={
            "width": "10",
            "height": "10",
            INKSTITCH_ATTRIBS["angle"]: "30"
        }))
        u1 = root.add(Use())
        u1.set('transform', Transform().add_rotate(60))
        u1.href = rect
        g = root.add(Group())
        g.set('transform', Transform().add_rotate(-10))
        u2 = g.add(Use())
        u2.href = u1
        u2.set(INKSTITCH_ATTRIBS["angle"], "0")
        u3 = root.add(Use())
        u3.set_id('U3')
        u3.set('transform', Transform().add_rotate(7))
        u3.href = g

        clone = Clone(u3)
        with clone.clone_elements() as elements:
            self.assertEqual(len(elements), 1)
            # Angle goes from 0 (g -> u2) -> -7 (u3)
            self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -7)
