# encoding: utf-8

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

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

from docx.enum.style import WD_STYLE_TYPE
from docx.enum.text import WD_BREAK, WD_UNDERLINE
from docx.parts.document import DocumentPart
from docx.shape import InlineShape
from docx.text.font import Font
from docx.text.run import Run

import pytest

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


class DescribeRun(object):

    def it_knows_its_bool_prop_states(self, bool_prop_get_fixture):
        run, prop_name, expected_state = bool_prop_get_fixture
        assert getattr(run, prop_name) == expected_state

    def it_can_change_its_bool_prop_settings(self, bool_prop_set_fixture):
        run, prop_name, value, expected_xml = bool_prop_set_fixture
        setattr(run, prop_name, value)
        assert run._r.xml == expected_xml

    def it_knows_its_character_style(self, style_get_fixture):
        run, style_id_, style_ = style_get_fixture
        style = run.style
        run.part.get_style.assert_called_once_with(
            style_id_, WD_STYLE_TYPE.CHARACTER
        )
        assert style is style_

    def it_can_change_its_character_style(self, style_set_fixture):
        run, value, expected_xml = style_set_fixture
        run.style = value
        run.part.get_style_id.assert_called_once_with(
            value, WD_STYLE_TYPE.CHARACTER
        )
        assert run._r.xml == expected_xml

    def it_knows_its_underline_type(self, underline_get_fixture):
        run, expected_value = underline_get_fixture
        assert run.underline is expected_value

    def it_can_change_its_underline_type(self, underline_set_fixture):
        run, underline, expected_xml = underline_set_fixture
        run.underline = underline
        assert run._r.xml == expected_xml

    def it_raises_on_assign_invalid_underline_type(
            self, underline_raise_fixture):
        run, underline = underline_raise_fixture
        with pytest.raises(ValueError):
            run.underline = underline

    def it_provides_access_to_its_font(self, font_fixture):
        run, Font_, font_ = font_fixture
        font = run.font
        Font_.assert_called_once_with(run._element)
        assert font is font_

    def it_can_add_text(self, add_text_fixture):
        run, text_str, expected_xml, Text_ = add_text_fixture
        _text = run.add_text(text_str)
        assert run._r.xml == expected_xml
        assert _text is Text_.return_value

    def it_can_add_a_break(self, add_break_fixture):
        run, break_type, expected_xml = add_break_fixture
        run.add_break(break_type)
        assert run._r.xml == expected_xml

    def it_can_add_a_tab(self, add_tab_fixture):
        run, expected_xml = add_tab_fixture
        run.add_tab()
        assert run._r.xml == expected_xml

    def it_can_add_a_picture(self, add_picture_fixture):
        run, image, width, height, inline = add_picture_fixture[:5]
        expected_xml, InlineShape_, picture_ = add_picture_fixture[5:]

        picture = run.add_picture(image, width, height)

        run.part.new_pic_inline.assert_called_once_with(image, width, height)
        assert run._r.xml == expected_xml
        InlineShape_.assert_called_once_with(inline)
        assert picture is picture_

    def it_can_remove_its_content_but_keep_formatting(self, clear_fixture):
        run, expected_xml = clear_fixture
        _run = run.clear()
        assert run._r.xml == expected_xml
        assert _run is run

    def it_knows_the_text_it_contains(self, text_get_fixture):
        run, expected_text = text_get_fixture
        assert run.text == expected_text

    def it_can_replace_the_text_it_contains(self, text_set_fixture):
        run, text, expected_xml = text_set_fixture
        run.text = text
        assert run._r.xml == expected_xml

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

    @pytest.fixture(params=[
        (WD_BREAK.LINE,   'w:r/w:br'),
        (WD_BREAK.PAGE,   'w:r/w:br{w:type=page}'),
        (WD_BREAK.COLUMN, 'w:r/w:br{w:type=column}'),
        (WD_BREAK.LINE_CLEAR_LEFT,
         'w:r/w:br{w:type=textWrapping, w:clear=left}'),
        (WD_BREAK.LINE_CLEAR_RIGHT,
         'w:r/w:br{w:type=textWrapping, w:clear=right}'),
        (WD_BREAK.LINE_CLEAR_ALL,
         'w:r/w:br{w:type=textWrapping, w:clear=all}'),
    ])
    def add_break_fixture(self, request):
        break_type, expected_cxml = request.param
        run = Run(element('w:r'), None)
        expected_xml = xml(expected_cxml)
        return run, break_type, expected_xml

    @pytest.fixture
    def add_picture_fixture(self, part_prop_, document_part_, InlineShape_,
                            picture_):
        run = Run(element('w:r/wp:x'), None)
        image = 'foobar.png'
        width, height, inline = 1111, 2222, element('wp:inline{id=42}')
        expected_xml = xml('w:r/(wp:x,w:drawing/wp:inline{id=42})')
        document_part_.new_pic_inline.return_value = inline
        InlineShape_.return_value = picture_
        return (
            run, image, width, height, inline, expected_xml, InlineShape_,
            picture_
        )

    @pytest.fixture(params=[
        ('w:r/w:t"foo"', 'w:r/(w:t"foo", w:tab)'),
    ])
    def add_tab_fixture(self, request):
        r_cxml, expected_cxml = request.param
        run = Run(element(r_cxml), None)
        expected_xml = xml(expected_cxml)
        return run, expected_xml

    @pytest.fixture(params=[
        ('w:r',          'foo', 'w:r/w:t"foo"'),
        ('w:r/w:t"foo"', 'bar', 'w:r/(w:t"foo", w:t"bar")'),
        ('w:r',          'fo ', 'w:r/w:t{xml:space=preserve}"fo "'),
        ('w:r',          'f o', 'w:r/w:t"f o"'),
    ])
    def add_text_fixture(self, request, Text_):
        r_cxml, text, expected_cxml = request.param
        run = Run(element(r_cxml), None)
        expected_xml = xml(expected_cxml)
        return run, text, expected_xml, Text_

    @pytest.fixture(params=[
        ('w:r/w:rPr',                  'bold',   None),
        ('w:r/w:rPr/w:b',              'bold',   True),
        ('w:r/w:rPr/w:b{w:val=on}',    'bold',   True),
        ('w:r/w:rPr/w:b{w:val=off}',   'bold',   False),
        ('w:r/w:rPr/w:b{w:val=1}',     'bold',   True),
        ('w:r/w:rPr/w:i{w:val=0}',     'italic', False),
    ])
    def bool_prop_get_fixture(self, request):
        r_cxml, bool_prop_name, expected_value = request.param
        run = Run(element(r_cxml), None)
        return run, bool_prop_name, expected_value

    @pytest.fixture(params=[
        # nothing to True, False, and None ---------------------------
        ('w:r', 'bold',   True,  'w:r/w:rPr/w:b'),
        ('w:r', 'bold',   False, 'w:r/w:rPr/w:b{w:val=0}'),
        ('w:r', 'italic', None,  'w:r/w:rPr'),
        # default to True, False, and None ---------------------------
        ('w:r/w:rPr/w:b', 'bold',   True,  'w:r/w:rPr/w:b'),
        ('w:r/w:rPr/w:b', 'bold',   False, 'w:r/w:rPr/w:b{w:val=0}'),
        ('w:r/w:rPr/w:i', 'italic', None,  'w:r/w:rPr'),
        # True to True, False, and None ------------------------------
        ('w:r/w:rPr/w:b{w:val=on}', 'bold', True,  'w:r/w:rPr/w:b'),
        ('w:r/w:rPr/w:b{w:val=1}',  'bold', False, 'w:r/w:rPr/w:b{w:val=0}'),
        ('w:r/w:rPr/w:b{w:val=1}',  'bold', None,  'w:r/w:rPr'),
        # False to True, False, and None -----------------------------
        ('w:r/w:rPr/w:i{w:val=false}', 'italic', True,  'w:r/w:rPr/w:i'),
        ('w:r/w:rPr/w:i{w:val=0}',     'italic', False,
         'w:r/w:rPr/w:i{w:val=0}'),
        ('w:r/w:rPr/w:i{w:val=off}',   'italic', None,  'w:r/w:rPr'),
    ])
    def bool_prop_set_fixture(self, request):
        initial_r_cxml, bool_prop_name, value, expected_cxml = request.param
        run = Run(element(initial_r_cxml), None)
        expected_xml = xml(expected_cxml)
        return run, bool_prop_name, value, expected_xml

    @pytest.fixture(params=[
        ('w:r',                   'w:r'),
        ('w:r/w:t"foo"',          'w:r'),
        ('w:r/w:br',              'w:r'),
        ('w:r/w:rPr',             'w:r/w:rPr'),
        ('w:r/(w:rPr, w:t"foo")', 'w:r/w:rPr'),
        ('w:r/(w:rPr/(w:b, w:i), w:t"foo", w:cr, w:t"bar")',
         'w:r/w:rPr/(w:b, w:i)'),
    ])
    def clear_fixture(self, request):
        initial_r_cxml, expected_cxml = request.param
        run = Run(element(initial_r_cxml), None)
        expected_xml = xml(expected_cxml)
        return run, expected_xml

    @pytest.fixture
    def font_fixture(self, Font_, font_):
        run = Run(element('w:r'), None)
        return run, Font_, font_

    @pytest.fixture
    def style_get_fixture(self, part_prop_):
        style_id = 'Barfoo'
        r_cxml = 'w:r/w:rPr/w:rStyle{w:val=%s}' % style_id
        run = Run(element(r_cxml), None)
        style_ = part_prop_.return_value.get_style.return_value
        return run, style_id, style_

    @pytest.fixture(params=[
        ('w:r',                                'Foo Font', 'FooFont',
         'w:r/w:rPr/w:rStyle{w:val=FooFont}'),
        ('w:r/w:rPr',                          'Foo Font', 'FooFont',
         'w:r/w:rPr/w:rStyle{w:val=FooFont}'),
        ('w:r/w:rPr/w:rStyle{w:val=FooFont}',  'Bar Font', 'BarFont',
         'w:r/w:rPr/w:rStyle{w:val=BarFont}'),
        ('w:r/w:rPr/w:rStyle{w:val=FooFont}',  None,       None,
         'w:r/w:rPr'),
        ('w:r',                                None,       None,
         'w:r/w:rPr'),
    ])
    def style_set_fixture(self, request, part_prop_):
        r_cxml, value, style_id, expected_cxml = request.param
        run = Run(element(r_cxml), None)
        part_prop_.return_value.get_style_id.return_value = style_id
        expected_xml = xml(expected_cxml)
        return run, value, expected_xml

    @pytest.fixture(params=[
        ('w:r', ''),
        ('w:r/w:t"foobar"', 'foobar'),
        ('w:r/(w:t"abc", w:tab, w:t"def", w:cr)', 'abc\tdef\n'),
        ('w:r/(w:br{w:type=page}, w:t"abc", w:t"def", w:tab)', '\nabcdef\t'),
    ])
    def text_get_fixture(self, request):
        r_cxml, expected_text = request.param
        run = Run(element(r_cxml), None)
        return run, expected_text

    @pytest.fixture(params=[
        ('abc  def', 'w:r/w:t"abc  def"'),
        ('abc\tdef', 'w:r/(w:t"abc", w:tab, w:t"def")'),
        ('abc\ndef', 'w:r/(w:t"abc", w:br,  w:t"def")'),
        ('abc\rdef', 'w:r/(w:t"abc", w:br,  w:t"def")'),
    ])
    def text_set_fixture(self, request):
        new_text, expected_cxml = request.param
        initial_r_cxml = 'w:r/w:t"should get deleted"'
        run = Run(element(initial_r_cxml), None)
        expected_xml = xml(expected_cxml)
        return run, new_text, expected_xml

    @pytest.fixture(params=[
        ('w:r',                         None),
        ('w:r/w:rPr/w:u',               None),
        ('w:r/w:rPr/w:u{w:val=single}', True),
        ('w:r/w:rPr/w:u{w:val=none}',   False),
        ('w:r/w:rPr/w:u{w:val=double}', WD_UNDERLINE.DOUBLE),
        ('w:r/w:rPr/w:u{w:val=wave}',   WD_UNDERLINE.WAVY),
    ])
    def underline_get_fixture(self, request):
        r_cxml, expected_underline = request.param
        run = Run(element(r_cxml), None)
        return run, expected_underline

    @pytest.fixture(params=[
        ('w:r', True,                'w:r/w:rPr/w:u{w:val=single}'),
        ('w:r', False,               'w:r/w:rPr/w:u{w:val=none}'),
        ('w:r', None,                'w:r/w:rPr'),
        ('w:r', WD_UNDERLINE.SINGLE, 'w:r/w:rPr/w:u{w:val=single}'),
        ('w:r', WD_UNDERLINE.THICK,  'w:r/w:rPr/w:u{w:val=thick}'),
        ('w:r/w:rPr/w:u{w:val=single}', True,
         'w:r/w:rPr/w:u{w:val=single}'),
        ('w:r/w:rPr/w:u{w:val=single}', False,
         'w:r/w:rPr/w:u{w:val=none}'),
        ('w:r/w:rPr/w:u{w:val=single}', None,
         'w:r/w:rPr'),
        ('w:r/w:rPr/w:u{w:val=single}', WD_UNDERLINE.SINGLE,
         'w:r/w:rPr/w:u{w:val=single}'),
        ('w:r/w:rPr/w:u{w:val=single}', WD_UNDERLINE.DOTTED,
         'w:r/w:rPr/w:u{w:val=dotted}'),
    ])
    def underline_set_fixture(self, request):
        initial_r_cxml, new_underline, expected_cxml = request.param
        run = Run(element(initial_r_cxml), None)
        expected_xml = xml(expected_cxml)
        return run, new_underline, expected_xml

    @pytest.fixture(params=['foobar', 42, 'single'])
    def underline_raise_fixture(self, request):
        invalid_underline_setting = request.param
        run = Run(element('w:r/w:rPr'), None)
        return run, invalid_underline_setting

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

    @pytest.fixture
    def document_part_(self, request):
        return instance_mock(request, DocumentPart)

    @pytest.fixture
    def Font_(self, request, font_):
        return class_mock(request, 'docx.text.run.Font', return_value=font_)

    @pytest.fixture
    def font_(self, request):
        return instance_mock(request, Font)

    @pytest.fixture
    def InlineShape_(self, request):
        return class_mock(request, 'docx.text.run.InlineShape')

    @pytest.fixture
    def part_prop_(self, request, document_part_):
        return property_mock(
            request, Run, 'part', return_value=document_part_
        )

    @pytest.fixture
    def picture_(self, request):
        return instance_mock(request, InlineShape)

    @pytest.fixture
    def Text_(self, request):
        return class_mock(request, 'docx.text.run._Text')
