"""Test suite for the docx.shape module."""

import pytest

from docx.enum.shape import WD_INLINE_SHAPE
from docx.oxml.ns import nsmap
from docx.shape import InlineShape, InlineShapes
from docx.shared import Length

from .oxml.unitdata.dml import (
    a_blip,
    a_blipFill,
    a_graphic,
    a_graphicData,
    a_pic,
    an_inline,
)
from .unitutil.cxml import element, xml
from .unitutil.mock import loose_mock


class DescribeInlineShapes:
    def it_knows_how_many_inline_shapes_it_contains(self, inline_shapes_fixture):
        inline_shapes, expected_count = inline_shapes_fixture
        assert len(inline_shapes) == expected_count

    def it_can_iterate_over_its_InlineShape_instances(self, inline_shapes_fixture):
        inline_shapes, inline_shape_count = inline_shapes_fixture
        actual_count = 0
        for inline_shape in inline_shapes:
            assert isinstance(inline_shape, InlineShape)
            actual_count += 1
        assert actual_count == inline_shape_count

    def it_provides_indexed_access_to_inline_shapes(self, inline_shapes_fixture):
        inline_shapes, inline_shape_count = inline_shapes_fixture
        for idx in range(-inline_shape_count, inline_shape_count):
            inline_shape = inline_shapes[idx]
            assert isinstance(inline_shape, InlineShape)

    def it_raises_on_indexed_access_out_of_range(self, inline_shapes_fixture):
        inline_shapes, inline_shape_count = inline_shapes_fixture
        too_low = -1 - inline_shape_count
        with pytest.raises(IndexError, match=r"inline shape index \[-3\] out of rang"):
            inline_shapes[too_low]
        too_high = inline_shape_count
        with pytest.raises(IndexError, match=r"inline shape index \[2\] out of range"):
            inline_shapes[too_high]

    def it_knows_the_part_it_belongs_to(self, inline_shapes_with_parent_):
        inline_shapes, parent_ = inline_shapes_with_parent_
        part = inline_shapes.part
        assert part is parent_.part

    # fixtures -------------------------------------------------------

    @pytest.fixture
    def inline_shapes_fixture(self):
        body = element("w:body/w:p/(w:r/w:drawing/wp:inline, w:r/w:drawing/wp:inline)")
        inline_shapes = InlineShapes(body, None)
        expected_count = 2
        return inline_shapes, expected_count

    # fixture components ---------------------------------------------

    @pytest.fixture
    def inline_shapes_with_parent_(self, request):
        parent_ = loose_mock(request, name="parent_")
        inline_shapes = InlineShapes(None, parent_)
        return inline_shapes, parent_


class DescribeInlineShape:
    def it_knows_what_type_of_shape_it_is(self, shape_type_fixture):
        inline_shape, inline_shape_type = shape_type_fixture
        assert inline_shape.type == inline_shape_type

    def it_knows_its_display_dimensions(self, dimensions_get_fixture):
        inline_shape, cx, cy = dimensions_get_fixture
        width = inline_shape.width
        height = inline_shape.height
        assert isinstance(width, Length)
        assert width == cx
        assert isinstance(height, Length)
        assert height == cy

    def it_can_change_its_display_dimensions(self, dimensions_set_fixture):
        inline_shape, cx, cy, expected_xml = dimensions_set_fixture
        inline_shape.width = cx
        inline_shape.height = cy
        assert inline_shape._inline.xml == expected_xml

    # fixtures -------------------------------------------------------

    @pytest.fixture
    def dimensions_get_fixture(self):
        inline_cxml, expected_cx, expected_cy = (
            "wp:inline/wp:extent{cx=333, cy=666}",
            333,
            666,
        )
        inline_shape = InlineShape(element(inline_cxml))
        return inline_shape, expected_cx, expected_cy

    @pytest.fixture
    def dimensions_set_fixture(self):
        inline_cxml, new_cx, new_cy, expected_cxml = (
            "wp:inline/(wp:extent{cx=333,cy=666},a:graphic/a:graphicData/"
            "pic:pic/pic:spPr/a:xfrm/a:ext{cx=333,cy=666})",
            444,
            888,
            "wp:inline/(wp:extent{cx=444,cy=888},a:graphic/a:graphicData/"
            "pic:pic/pic:spPr/a:xfrm/a:ext{cx=444,cy=888})",
        )
        inline_shape = InlineShape(element(inline_cxml))
        expected_xml = xml(expected_cxml)
        return inline_shape, new_cx, new_cy, expected_xml

    @pytest.fixture(
        params=[
            "embed pic",
            "link pic",
            "link+embed pic",
            "chart",
            "smart art",
            "not implemented",
        ]
    )
    def shape_type_fixture(self, request):
        if request.param == "embed pic":
            inline = self._inline_with_picture(embed=True)
            shape_type = WD_INLINE_SHAPE.PICTURE

        elif request.param == "link pic":
            inline = self._inline_with_picture(link=True)
            shape_type = WD_INLINE_SHAPE.LINKED_PICTURE

        elif request.param == "link+embed pic":
            inline = self._inline_with_picture(embed=True, link=True)
            shape_type = WD_INLINE_SHAPE.LINKED_PICTURE

        elif request.param == "chart":
            inline = self._inline_with_uri(nsmap["c"])
            shape_type = WD_INLINE_SHAPE.CHART

        elif request.param == "smart art":
            inline = self._inline_with_uri(nsmap["dgm"])
            shape_type = WD_INLINE_SHAPE.SMART_ART

        elif request.param == "not implemented":
            inline = self._inline_with_uri("foobar")
            shape_type = WD_INLINE_SHAPE.NOT_IMPLEMENTED

        return InlineShape(inline), shape_type

    # fixture components ---------------------------------------------

    def _inline_with_picture(self, embed=False, link=False):
        picture_ns = nsmap["pic"]

        blip_bldr = a_blip()
        if embed:
            blip_bldr.with_embed("rId1")
        if link:
            blip_bldr.with_link("rId2")

        inline = (
            an_inline()
            .with_nsdecls("wp", "r")
            .with_child(
                a_graphic()
                .with_nsdecls()
                .with_child(
                    a_graphicData()
                    .with_uri(picture_ns)
                    .with_child(
                        a_pic()
                        .with_nsdecls()
                        .with_child(a_blipFill().with_child(blip_bldr))
                    )
                )
            )
        ).element
        return inline

    def _inline_with_uri(self, uri):
        inline = (
            an_inline()
            .with_nsdecls("wp")
            .with_child(
                a_graphic().with_nsdecls().with_child(a_graphicData().with_uri(uri))
            )
        ).element
        return inline
