"""Test suite for the docx.text.tabstops module."""

import pytest

from docx.enum.text import WD_TAB_ALIGNMENT, WD_TAB_LEADER
from docx.shared import Twips
from docx.text.tabstops import TabStop, TabStops

from ..unitutil.cxml import element, xml
from ..unitutil.mock import call, class_mock, instance_mock


class DescribeTabStop:
    def it_knows_its_position(self, position_get_fixture):
        tab_stop, expected_value = position_get_fixture
        assert tab_stop.position == expected_value

    def it_can_change_its_position(self, position_set_fixture):
        tab_stop, value, tabs, new_idx, expected_xml = position_set_fixture
        tab_stop.position = value
        assert tab_stop._tab is tabs[new_idx]
        assert tabs.xml == expected_xml

    def it_knows_its_alignment(self, alignment_get_fixture):
        tab_stop, expected_value = alignment_get_fixture
        assert tab_stop.alignment == expected_value

    def it_can_change_its_alignment(self, alignment_set_fixture):
        tab_stop, value, expected_xml = alignment_set_fixture
        tab_stop.alignment = value
        assert tab_stop._element.xml == expected_xml

    def it_knows_its_leader(self, leader_get_fixture):
        tab_stop, expected_value = leader_get_fixture
        assert tab_stop.leader == expected_value

    def it_can_change_its_leader(self, leader_set_fixture):
        tab_stop, value, expected_xml = leader_set_fixture
        tab_stop.leader = value
        assert tab_stop._element.xml == expected_xml

    # fixture --------------------------------------------------------

    @pytest.fixture(
        params=[
            ("w:tab{w:val=left}", "LEFT"),
            ("w:tab{w:val=right}", "RIGHT"),
        ]
    )
    def alignment_get_fixture(self, request):
        tab_stop_cxml, member = request.param
        tab_stop = TabStop(element(tab_stop_cxml))
        expected_value = getattr(WD_TAB_ALIGNMENT, member)
        return tab_stop, expected_value

    @pytest.fixture(
        params=[
            ("w:tab{w:val=left}", "RIGHT", "w:tab{w:val=right}"),
            ("w:tab{w:val=right}", "LEFT", "w:tab{w:val=left}"),
        ]
    )
    def alignment_set_fixture(self, request):
        tab_stop_cxml, member, expected_cxml = request.param
        tab_stop = TabStop(element(tab_stop_cxml))
        expected_xml = xml(expected_cxml)
        value = getattr(WD_TAB_ALIGNMENT, member)
        return tab_stop, value, expected_xml

    @pytest.fixture(
        params=[
            ("w:tab", "SPACES"),
            ("w:tab{w:leader=none}", "SPACES"),
            ("w:tab{w:leader=dot}", "DOTS"),
        ]
    )
    def leader_get_fixture(self, request):
        tab_stop_cxml, member = request.param
        tab_stop = TabStop(element(tab_stop_cxml))
        expected_value = getattr(WD_TAB_LEADER, member)
        return tab_stop, expected_value

    @pytest.fixture(
        params=[
            ("w:tab", "DOTS", "w:tab{w:leader=dot}"),
            ("w:tab{w:leader=dot}", "DASHES", "w:tab{w:leader=hyphen}"),
            ("w:tab{w:leader=hyphen}", "SPACES", "w:tab"),
            ("w:tab{w:leader=hyphen}", None, "w:tab"),
            ("w:tab", "SPACES", "w:tab"),
            ("w:tab", None, "w:tab"),
        ]
    )
    def leader_set_fixture(self, request):
        tab_stop_cxml, new_value, expected_cxml = request.param
        tab_stop = TabStop(element(tab_stop_cxml))
        value = None if new_value is None else getattr(WD_TAB_LEADER, new_value)
        expected_xml = xml(expected_cxml)
        return tab_stop, value, expected_xml

    @pytest.fixture
    def position_get_fixture(self, request):
        tab_stop = TabStop(element("w:tab{w:pos=720}"))
        return tab_stop, Twips(720)

    @pytest.fixture(
        params=[
            (
                "w:tabs/w:tab{w:pos=360,w:val=left}",
                Twips(720),
                0,
                "w:tabs/w:tab{w:pos=720,w:val=left}",
            ),
            (
                "w:tabs/(w:tab{w:pos=360,w:val=left},w:tab{w:pos=720,w:val=left})",
                Twips(180),
                0,
                "w:tabs/(w:tab{w:pos=180,w:val=left},w:tab{w:pos=720,w:val=left})",
            ),
            (
                "w:tabs/(w:tab{w:pos=360,w:val=left},w:tab{w:pos=720,w:val=left})",
                Twips(960),
                1,
                "w:tabs/(w:tab{w:pos=720,w:val=left},w:tab{w:pos=960,w:val=left})",
            ),
            (
                "w:tabs/(w:tab{w:pos=-72,w:val=left},w:tab{w:pos=-36,w:val=left})",
                Twips(-48),
                0,
                "w:tabs/(w:tab{w:pos=-48,w:val=left},w:tab{w:pos=-36,w:val=left})",
            ),
            (
                "w:tabs/(w:tab{w:pos=-72,w:val=left},w:tab{w:pos=-36,w:val=left})",
                Twips(-16),
                1,
                "w:tabs/(w:tab{w:pos=-36,w:val=left},w:tab{w:pos=-16,w:val=left})",
            ),
        ]
    )
    def position_set_fixture(self, request):
        tabs_cxml, value, new_idx, expected_cxml = request.param
        tabs = element(tabs_cxml)
        tab = tabs.tab_lst[0]
        tab_stop = TabStop(tab)
        expected_xml = xml(expected_cxml)
        return tab_stop, value, tabs, new_idx, expected_xml


class DescribeTabStops:
    def it_knows_its_length(self, len_fixture):
        tab_stops, expected_value = len_fixture
        assert len(tab_stops) == expected_value

    def it_can_iterate_over_its_tab_stops(self, iter_fixture):
        tab_stops, expected_count, tab_stop_, TabStop_, expected_calls = iter_fixture
        count = 0
        for tab_stop in tab_stops:
            assert tab_stop is tab_stop_
            count += 1
        assert count == expected_count
        assert TabStop_.call_args_list == expected_calls

    def it_can_get_a_tab_stop_by_index(self, index_fixture):
        tab_stops, idx, TabStop_, tab, tab_stop_ = index_fixture
        tab_stop = tab_stops[idx]
        TabStop_.assert_called_once_with(tab)
        assert tab_stop is tab_stop_

    def it_raises_on_indexed_access_when_empty(self):
        tab_stops = TabStops(element("w:pPr"))
        with pytest.raises(IndexError):
            tab_stops[0]

    def it_can_add_a_tab_stop(self, add_tab_fixture):
        tab_stops, position, kwargs, expected_xml = add_tab_fixture
        tab_stops.add_tab_stop(position, **kwargs)
        assert tab_stops._element.xml == expected_xml

    def it_can_delete_a_tab_stop(self, del_fixture):
        tab_stops, idx, expected_xml = del_fixture
        del tab_stops[idx]
        assert tab_stops._element.xml == expected_xml

    def it_raises_on_del_idx_invalid(self, del_raises_fixture):
        tab_stops, idx = del_raises_fixture
        with pytest.raises(IndexError) as exc:
            del tab_stops[idx]
        assert exc.value.args[0] == "tab index out of range"

    def it_can_clear_all_its_tab_stops(self, clear_all_fixture):
        tab_stops, expected_xml = clear_all_fixture
        tab_stops.clear_all()
        assert tab_stops._element.xml == expected_xml

    # fixture --------------------------------------------------------

    @pytest.fixture(
        params=[
            "w:pPr",
            "w:pPr/w:tabs/w:tab{w:pos=42}",
            "w:pPr/w:tabs/(w:tab{w:pos=24},w:tab{w:pos=42})",
        ]
    )
    def clear_all_fixture(self, request):
        pPr_cxml = request.param
        tab_stops = TabStops(element(pPr_cxml))
        expected_xml = xml("w:pPr")
        return tab_stops, expected_xml

    @pytest.fixture(
        params=[
            ("w:pPr/w:tabs/w:tab{w:pos=42}", 0, "w:pPr"),
            (
                "w:pPr/w:tabs/(w:tab{w:pos=24},w:tab{w:pos=42})",
                0,
                "w:pPr/w:tabs/w:tab{w:pos=42}",
            ),
            (
                "w:pPr/w:tabs/(w:tab{w:pos=24},w:tab{w:pos=42})",
                1,
                "w:pPr/w:tabs/w:tab{w:pos=24}",
            ),
        ]
    )
    def del_fixture(self, request):
        pPr_cxml, idx, expected_cxml = request.param
        tab_stops = TabStops(element(pPr_cxml))
        expected_xml = xml(expected_cxml)
        return tab_stops, idx, expected_xml

    @pytest.fixture(
        params=[
            ("w:pPr", 0),
            ("w:pPr/w:tabs/w:tab{w:pos=42}", 1),
        ]
    )
    def del_raises_fixture(self, request):
        tab_stops_cxml, idx = request.param
        tab_stops = TabStops(element(tab_stops_cxml))
        return tab_stops, idx

    @pytest.fixture(
        params=[
            ("w:pPr", Twips(42), {}, "w:pPr/w:tabs/w:tab{w:pos=42,w:val=left}"),
            (
                "w:pPr",
                Twips(72),
                {"alignment": WD_TAB_ALIGNMENT.RIGHT},
                "w:pPr/w:tabs/w:tab{w:pos=72,w:val=right}",
            ),
            (
                "w:pPr",
                Twips(24),
                {"alignment": WD_TAB_ALIGNMENT.CENTER, "leader": WD_TAB_LEADER.DOTS},
                "w:pPr/w:tabs/w:tab{w:pos=24,w:val=center,w:leader=dot}",
            ),
            (
                "w:pPr/w:tabs/w:tab{w:pos=42}",
                Twips(72),
                {},
                "w:pPr/w:tabs/(w:tab{w:pos=42},w:tab{w:pos=72,w:val=left})",
            ),
            (
                "w:pPr/w:tabs/w:tab{w:pos=42}",
                Twips(24),
                {},
                "w:pPr/w:tabs/(w:tab{w:pos=24,w:val=left},w:tab{w:pos=42})",
            ),
            (
                "w:pPr/w:tabs/w:tab{w:pos=42}",
                Twips(42),
                {},
                "w:pPr/w:tabs/(w:tab{w:pos=42},w:tab{w:pos=42,w:val=left})",
            ),
        ]
    )
    def add_tab_fixture(self, request):
        pPr_cxml, position, kwargs, expected_cxml = request.param
        tab_stops = TabStops(element(pPr_cxml))
        expected_xml = xml(expected_cxml)
        return tab_stops, position, kwargs, expected_xml

    @pytest.fixture(
        params=[
            ("w:pPr/w:tabs/w:tab{w:pos=0}", 0),
            ("w:pPr/w:tabs/(w:tab{w:pos=1},w:tab{w:pos=2},w:tab{w:pos=3})", 1),
            ("w:pPr/w:tabs/(w:tab{w:pos=4},w:tab{w:pos=5},w:tab{w:pos=6})", 2),
        ]
    )
    def index_fixture(self, request, TabStop_, tab_stop_):
        pPr_cxml, idx = request.param
        pPr = element(pPr_cxml)
        tab = pPr.xpath("./w:tabs/w:tab")[idx]
        tab_stops = TabStops(pPr)
        return tab_stops, idx, TabStop_, tab, tab_stop_

    @pytest.fixture(
        params=[
            ("w:pPr", 0),
            ("w:pPr/w:tabs/w:tab{w:pos=2880}", 1),
            ("w:pPr/w:tabs/(w:tab{w:pos=2880},w:tab{w:pos=5760})", 2),
        ]
    )
    def iter_fixture(self, request, TabStop_, tab_stop_):
        pPr_cxml, expected_count = request.param
        pPr = element(pPr_cxml)
        tab_elms = pPr.xpath("//w:tab")
        tab_stops = TabStops(pPr)
        expected_calls = [call(tab) for tab in tab_elms]
        return tab_stops, expected_count, tab_stop_, TabStop_, expected_calls

    @pytest.fixture(
        params=[
            ("w:pPr", 0),
            ("w:pPr/w:tabs/w:tab{w:pos=2880}", 1),
        ]
    )
    def len_fixture(self, request):
        tab_stops_cxml, expected_value = request.param
        tab_stops = TabStops(element(tab_stops_cxml))
        return tab_stops, expected_value

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

    @pytest.fixture
    def TabStop_(self, request, tab_stop_):
        return class_mock(request, "docx.text.tabstops.TabStop", return_value=tab_stop_)

    @pytest.fixture
    def tab_stop_(self, request):
        return instance_mock(request, TabStop)
