# encoding: utf-8

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

from __future__ import (
    absolute_import, division, print_function, unicode_literals
)

import pytest

from docx.exceptions import InvalidSpanError
from docx.oxml import parse_xml
from docx.oxml.table import CT_Row, CT_Tc

from ..unitutil.cxml import element, xml
from ..unitutil.file import snippet_seq
from ..unitutil.mock import call, instance_mock, method_mock, property_mock


class DescribeCT_Row(object):

    def it_can_add_a_trPr(self, add_trPr_fixture):
        tr, expected_xml = add_trPr_fixture
        tr._add_trPr()
        assert tr.xml == expected_xml

    def it_raises_on_tc_at_grid_col(self, tc_raise_fixture):
        tr, idx = tc_raise_fixture
        with pytest.raises(ValueError):
            tr.tc_at_grid_col(idx)

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

    @pytest.fixture(params=[
        ('w:tr',                    'w:tr/w:trPr'),
        ('w:tr/w:tblPrEx',          'w:tr/(w:tblPrEx,w:trPr)'),
        ('w:tr/w:tc',               'w:tr/(w:trPr,w:tc)'),
        ('w:tr/(w:sdt,w:del,w:tc)', 'w:tr/(w:trPr,w:sdt,w:del,w:tc)'),
    ])
    def add_trPr_fixture(self, request):
        tr_cxml, expected_cxml = request.param
        tr = element(tr_cxml)
        expected_xml = xml(expected_cxml)
        return tr, expected_xml

    @pytest.fixture(params=[(0, 0, 3), (1, 0, 1)])
    def tc_raise_fixture(self, request):
        snippet_idx, row_idx, col_idx = request.param
        tbl = parse_xml(snippet_seq('tbl-cells')[snippet_idx])
        tr = tbl.tr_lst[row_idx]
        return tr, col_idx


class DescribeCT_Tc(object):

    def it_can_merge_to_another_tc(self, merge_fixture):
        tc, other_tc, top_tr_, top_tc_, left, height, width = merge_fixture
        merged_tc = tc.merge(other_tc)
        tc._span_dimensions.assert_called_once_with(other_tc)
        top_tr_.tc_at_grid_col.assert_called_once_with(left)
        top_tc_._grow_to.assert_called_once_with(width, height)
        assert merged_tc is top_tc_

    def it_knows_its_extents_to_help(self, extents_fixture):
        tc, attr_name, expected_value = extents_fixture
        extent = getattr(tc, attr_name)
        assert extent == expected_value

    def it_calculates_the_dimensions_of_a_span_to_help(self, span_fixture):
        tc, other_tc, expected_dimensions = span_fixture
        dimensions = tc._span_dimensions(other_tc)
        assert dimensions == expected_dimensions

    def it_raises_on_invalid_span(self, span_raise_fixture):
        tc, other_tc = span_raise_fixture
        with pytest.raises(InvalidSpanError):
            tc._span_dimensions(other_tc)

    def it_can_grow_itself_to_help_merge(self, grow_to_fixture):
        tc, width, height, top_tc, expected_calls = grow_to_fixture
        tc._grow_to(width, height, top_tc)
        assert tc._span_to_width.call_args_list == expected_calls

    def it_can_extend_its_horz_span_to_help_merge(self, span_width_fixture):
        tc, grid_width, top_tc, vMerge, expected_calls = span_width_fixture
        tc._span_to_width(grid_width, top_tc, vMerge)
        tc._move_content_to.assert_called_once_with(top_tc)
        assert tc._swallow_next_tc.call_args_list == expected_calls
        assert tc.vMerge == vMerge

    def it_can_swallow_the_next_tc_help_merge(self, swallow_fixture):
        tc, grid_width, top_tc, tr, expected_xml = swallow_fixture
        tc._swallow_next_tc(grid_width, top_tc)
        assert tr.xml == expected_xml

    def it_adds_cell_widths_on_swallow(self, add_width_fixture):
        tc, grid_width, top_tc, tr, expected_xml = add_width_fixture
        tc._swallow_next_tc(grid_width, top_tc)
        assert tr.xml == expected_xml

    def it_raises_on_invalid_swallow(self, swallow_raise_fixture):
        tc, grid_width, top_tc, tr = swallow_raise_fixture
        with pytest.raises(InvalidSpanError):
            tc._swallow_next_tc(grid_width, top_tc)

    def it_can_move_its_content_to_help_merge(self, move_fixture):
        tc, tc_2, expected_tc_xml, expected_tc_2_xml = move_fixture
        tc._move_content_to(tc_2)
        assert tc.xml == expected_tc_xml
        assert tc_2.xml == expected_tc_2_xml

    def it_raises_on_tr_above(self, tr_above_raise_fixture):
        tc = tr_above_raise_fixture
        with pytest.raises(ValueError):
            tc._tr_above

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

    @pytest.fixture(params=[
        # both cells have a width
        ('w:tr/(w:tc/(w:tcPr/w:tcW{w:w=1440,w:type=dxa},w:p),'
         'w:tc/(w:tcPr/w:tcW{w:w=1440,w:type=dxa},w:p))',      0, 2,
         'w:tr/(w:tc/(w:tcPr/(w:tcW{w:w=2880,w:type=dxa},'
         'w:gridSpan{w:val=2}),w:p))'),
        # neither have a width
        ('w:tr/(w:tc/w:p,w:tc/w:p)',                           0, 2,
         'w:tr/(w:tc/(w:tcPr/w:gridSpan{w:val=2},w:p))'),
        # only second one has a width
        ('w:tr/(w:tc/w:p,'
         'w:tc/(w:tcPr/w:tcW{w:w=1440,w:type=dxa},w:p))',      0, 2,
         'w:tr/(w:tc/(w:tcPr/w:gridSpan{w:val=2},w:p))'),
        # only first one has a width
        ('w:tr/(w:tc/(w:tcPr/w:tcW{w:w=1440,w:type=dxa},w:p),'
         'w:tc/w:p)',                                          0, 2,
         'w:tr/(w:tc/(w:tcPr/(w:tcW{w:w=1440,w:type=dxa},'
         'w:gridSpan{w:val=2}),w:p))'),
    ])
    def add_width_fixture(self, request):
        tr_cxml, tc_idx, grid_width, expected_tr_cxml = request.param
        tr = element(tr_cxml)
        tc = top_tc = tr[tc_idx]
        expected_tr_xml = xml(expected_tr_cxml)
        return tc, grid_width, top_tc, tr, expected_tr_xml

    @pytest.fixture(params=[
        (0, 0, 0, 'top',    0), (2, 0, 1, 'top',    0),
        (2, 1, 1, 'top',    0), (4, 2, 1, 'top',    1),
        (0, 0, 0, 'left',   0), (1, 0, 1, 'left',   2),
        (3, 1, 0, 'left',   0), (3, 1, 1, 'left',   2),
        (0, 0, 0, 'bottom', 1), (1, 0, 0, 'bottom', 1),
        (2, 0, 1, 'bottom', 2), (4, 1, 1, 'bottom', 3),
        (0, 0, 0, 'right',  1), (1, 0, 0, 'right',  2),
        (0, 0, 0, 'right',  1), (4, 2, 1, 'right',  3),
    ])
    def extents_fixture(self, request):
        snippet_idx, row, col, attr_name, expected_value = request.param
        tbl = self._snippet_tbl(snippet_idx)
        tc = tbl.tr_lst[row].tc_lst[col]
        return tc, attr_name, expected_value

    @pytest.fixture(params=[
        (0, 0, 0, 2, 1),
        (0, 0, 1, 1, 2),
        (0, 1, 1, 2, 2),
        (1, 0, 0, 2, 2),
        (2, 0, 0, 2, 2),
        (2, 1, 2, 1, 2),
    ])
    def grow_to_fixture(self, request, _span_to_width_):
        snippet_idx, row, col, width, height = request.param
        tbl = self._snippet_tbl(snippet_idx)
        tc = tbl.tr_lst[row].tc_lst[col]
        start = 0 if height == 1 else 1
        end = start + height
        expected_calls = [
            call(width, tc, None),
            call(width, tc, 'restart'),
            call(width, tc, 'continue'),
            call(width, tc, 'continue'),
        ][start:end]
        return tc, width, height, None, expected_calls

    @pytest.fixture
    def merge_fixture(
            self, tr_, _span_dimensions_, _tbl_, _grow_to_, top_tc_):
        tc, other_tc = element('w:tc'), element('w:tc')
        top, left, height, width = 0, 1, 2, 3
        _span_dimensions_.return_value = top, left, height, width
        _tbl_.return_value.tr_lst = [tr_]
        tr_.tc_at_grid_col.return_value = top_tc_
        return tc, other_tc, tr_, top_tc_, left, height, width

    @pytest.fixture(params=[
        ('w:tc/w:p',             'w:tc/w:p',
         'w:tc/w:p',             'w:tc/w:p'),
        ('w:tc/w:p',             'w:tc/w:p/w:r',
         'w:tc/w:p',             'w:tc/w:p/w:r'),
        ('w:tc/w:p/w:r',         'w:tc/w:p',
         'w:tc/w:p',             'w:tc/w:p/w:r'),
        ('w:tc/(w:p/w:r,w:sdt)', 'w:tc/w:p',
         'w:tc/w:p',             'w:tc/(w:p/w:r,w:sdt)'),
        ('w:tc/(w:p/w:r,w:sdt)', 'w:tc/(w:tbl,w:p)',
         'w:tc/w:p',             'w:tc/(w:tbl,w:p/w:r,w:sdt)'),
    ])
    def move_fixture(self, request):
        tc_cxml, tc_2_cxml, expected_tc_cxml, expected_tc_2_cxml = (
            request.param
        )
        tc, tc_2 = element(tc_cxml), element(tc_2_cxml)
        expected_tc_xml = xml(expected_tc_cxml)
        expected_tc_2_xml = xml(expected_tc_2_cxml)
        return tc, tc_2, expected_tc_xml, expected_tc_2_xml

    @pytest.fixture(params=[
        (0, 0, 0, 0, 1, (0, 0, 1, 2)),
        (0, 0, 1, 2, 1, (0, 1, 3, 1)),
        (0, 2, 2, 1, 1, (1, 1, 2, 2)),
        (0, 1, 2, 1, 0, (1, 0, 1, 3)),
        (1, 0, 0, 1, 1, (0, 0, 2, 2)),
        (1, 0, 1, 0, 0, (0, 0, 1, 3)),
        (2, 0, 1, 2, 1, (0, 1, 3, 1)),
        (2, 0, 1, 1, 0, (0, 0, 2, 2)),
        (2, 1, 2, 0, 1, (0, 1, 2, 2)),
        (4, 0, 1, 0, 0, (0, 0, 1, 3)),
    ])
    def span_fixture(self, request):
        snippet_idx, row, col, row_2, col_2, expected_value = request.param
        tbl = self._snippet_tbl(snippet_idx)
        tc = tbl.tr_lst[row].tc_lst[col]
        tc_2 = tbl.tr_lst[row_2].tc_lst[col_2]
        return tc, tc_2, expected_value

    @pytest.fixture(params=[
        (1, 0, 0, 1, 0),  # inverted-L horz
        (1, 1, 0, 0, 0),  # same in opposite order
        (2, 0, 2, 0, 1),  # inverted-L vert
        (5, 0, 1, 1, 0),  # tee-shape horz bar
        (5, 1, 0, 2, 1),  # same, opposite side
        (6, 1, 0, 0, 1),  # tee-shape vert bar
        (6, 0, 1, 1, 2),  # same, opposite side
    ])
    def span_raise_fixture(self, request):
        snippet_idx, row, col, row_2, col_2 = request.param
        tbl = self._snippet_tbl(snippet_idx)
        tc = tbl.tr_lst[row].tc_lst[col]
        tc_2 = tbl.tr_lst[row_2].tc_lst[col_2]
        return tc, tc_2

    @pytest.fixture
    def span_width_fixture(
            self, top_tc_, grid_span_, _move_content_to_, _swallow_next_tc_):
        tc = element('w:tc')
        grid_span_.side_effect = [1, 3, 4]
        grid_width, vMerge = 4, 'continue'
        expected_calls = [
            call(grid_width, top_tc_),
            call(grid_width, top_tc_)
        ]
        return tc, grid_width, top_tc_, vMerge, expected_calls

    @pytest.fixture(params=[
        ('w:tr/(w:tc/w:p,w:tc/w:p)',                              0, 2,
         'w:tr/(w:tc/(w:tcPr/w:gridSpan{w:val=2},w:p))'),
        ('w:tr/(w:tc/w:p,w:tc/w:p,w:tc/w:p)',                     1, 2,
         'w:tr/(w:tc/w:p,w:tc/(w:tcPr/w:gridSpan{w:val=2},w:p))'),
        ('w:tr/(w:tc/w:p/w:r/w:t"a",w:tc/w:p/w:r/w:t"b")',        0, 2,
         'w:tr/(w:tc/(w:tcPr/w:gridSpan{w:val=2},w:p/w:r/w:t"a",'
         'w:p/w:r/w:t"b"))'),
        ('w:tr/(w:tc/(w:tcPr/w:gridSpan{w:val=2},w:p),w:tc/w:p)', 0, 3,
         'w:tr/(w:tc/(w:tcPr/w:gridSpan{w:val=3},w:p))'),
        ('w:tr/(w:tc/w:p,w:tc/(w:tcPr/w:gridSpan{w:val=2},w:p))', 0, 3,
         'w:tr/(w:tc/(w:tcPr/w:gridSpan{w:val=3},w:p))'),
    ])
    def swallow_fixture(self, request):
        tr_cxml, tc_idx, grid_width, expected_tr_cxml = request.param
        tr = element(tr_cxml)
        tc = top_tc = tr[tc_idx]
        expected_tr_xml = xml(expected_tr_cxml)
        return tc, grid_width, top_tc, tr, expected_tr_xml

    @pytest.fixture(params=[
        ('w:tr/w:tc/w:p', 0, 2),
        ('w:tr/(w:tc/w:p,w:tc/(w:tcPr/w:gridSpan{w:val=2},w:p))', 0, 2),
    ])
    def swallow_raise_fixture(self, request):
        tr_cxml, tc_idx, grid_width = request.param
        tr = element(tr_cxml)
        tc = top_tc = tr[tc_idx]
        return tc, grid_width, top_tc, tr

    @pytest.fixture(params=[(0, 0, 0), (4, 0, 0)])
    def tr_above_raise_fixture(self, request):
        snippet_idx, row_idx, col_idx = request.param
        tbl = parse_xml(snippet_seq('tbl-cells')[snippet_idx])
        tc = tbl.tr_lst[row_idx].tc_lst[col_idx]
        return tc

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

    @pytest.fixture
    def grid_span_(self, request):
        return property_mock(request, CT_Tc, 'grid_span')

    @pytest.fixture
    def _grow_to_(self, request):
        return method_mock(request, CT_Tc, '_grow_to')

    @pytest.fixture
    def _move_content_to_(self, request):
        return method_mock(request, CT_Tc, '_move_content_to')

    @pytest.fixture
    def _span_dimensions_(self, request):
        return method_mock(request, CT_Tc, '_span_dimensions')

    @pytest.fixture
    def _span_to_width_(self, request):
        return method_mock(request, CT_Tc, '_span_to_width')

    def _snippet_tbl(self, idx):
        """
        Return a <w:tbl> element for snippet at *idx* in 'tbl-cells' snippet
        file.
        """
        return parse_xml(snippet_seq('tbl-cells')[idx])

    @pytest.fixture
    def _swallow_next_tc_(self, request):
        return method_mock(request, CT_Tc, '_swallow_next_tc')

    @pytest.fixture
    def _tbl_(self, request):
        return property_mock(request, CT_Tc, '_tbl')

    @pytest.fixture
    def top_tc_(self, request):
        return instance_mock(request, CT_Tc)

    @pytest.fixture
    def tr_(self, request):
        return instance_mock(request, CT_Row)
