# encoding: utf-8

"""
Unit test suite for the docx.opc.rel module
"""

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

import pytest

from docx.opc.oxml import CT_Relationships
from docx.opc.packuri import PackURI
from docx.opc.part import Part
from docx.opc.rel import _Relationship, Relationships

from ..unitutil.mock import (
    call, class_mock, instance_mock, Mock, patch, PropertyMock
)


class Describe_Relationship(object):

    def it_remembers_construction_values(self):
        # test data --------------------
        rId = 'rId9'
        reltype = 'reltype'
        target = Mock(name='target_part')
        external = False
        # exercise ---------------------
        rel = _Relationship(rId, reltype, target, None, external)
        # verify -----------------------
        assert rel.rId == rId
        assert rel.reltype == reltype
        assert rel.target_part == target
        assert rel.is_external == external

    def it_should_raise_on_target_part_access_on_external_rel(self):
        rel = _Relationship(None, None, None, None, external=True)
        with pytest.raises(ValueError):
            rel.target_part

    def it_should_have_target_ref_for_external_rel(self):
        rel = _Relationship(None, None, 'target', None, external=True)
        assert rel.target_ref == 'target'

    def it_should_have_relative_ref_for_internal_rel(self):
        """
        Internal relationships (TargetMode == 'Internal' in the XML) should
        have a relative ref, e.g. '../slideLayouts/slideLayout1.xml', for
        the target_ref attribute.
        """
        part = Mock(name='part', partname=PackURI('/ppt/media/image1.png'))
        baseURI = '/ppt/slides'
        rel = _Relationship(None, None, part, baseURI)  # external=False
        assert rel.target_ref == '../media/image1.png'


class DescribeRelationships(object):

    def it_can_add_a_relationship(self, _Relationship_):
        baseURI, rId, reltype, target, external = (
            'baseURI', 'rId9', 'reltype', 'target', False
        )
        rels = Relationships(baseURI)
        rel = rels.add_relationship(reltype, target, rId, external)
        _Relationship_.assert_called_once_with(
            rId, reltype, target, baseURI, external
        )
        assert rels[rId] == rel
        assert rel == _Relationship_.return_value

    def it_can_add_an_external_relationship(self, add_ext_rel_fixture_):
        rels, reltype, url = add_ext_rel_fixture_
        rId = rels.get_or_add_ext_rel(reltype, url)
        rel = rels[rId]
        assert rel.is_external
        assert rel.target_ref == url
        assert rel.reltype == reltype

    def it_can_find_a_relationship_by_rId(self):
        rel = Mock(name='rel', rId='foobar')
        rels = Relationships(None)
        rels['foobar'] = rel
        assert rels['foobar'] == rel

    def it_can_find_or_add_a_relationship(
            self, rels_with_matching_rel_, rels_with_missing_rel_):

        rels, reltype, part, matching_rel = rels_with_matching_rel_
        assert rels.get_or_add(reltype, part) == matching_rel

        rels, reltype, part, new_rel = rels_with_missing_rel_
        assert rels.get_or_add(reltype, part) == new_rel

    def it_can_find_or_add_an_external_relationship(
            self, add_matching_ext_rel_fixture_):
        rels, reltype, url, rId = add_matching_ext_rel_fixture_
        _rId = rels.get_or_add_ext_rel(reltype, url)
        assert _rId == rId
        assert len(rels) == 1

    def it_can_find_a_related_part_by_rId(self, rels_with_known_target_part):
        rels, rId, known_target_part = rels_with_known_target_part
        part = rels.related_parts[rId]
        assert part is known_target_part

    def it_raises_on_related_part_not_found(self, rels):
        with pytest.raises(KeyError):
            rels.related_parts['rId666']

    def it_can_find_a_related_part_by_reltype(
            self, rels_with_target_known_by_reltype):
        rels, reltype, known_target_part = rels_with_target_known_by_reltype
        part = rels.part_with_reltype(reltype)
        assert part is known_target_part

    def it_can_compose_rels_xml(self, rels, rels_elm):
        # exercise ---------------------
        rels.xml
        # verify -----------------------
        rels_elm.assert_has_calls(
            [
                call.add_rel(
                    'rId1', 'http://rt-hyperlink', 'http://some/link', True
                ),
                call.add_rel(
                    'rId2', 'http://rt-image', '../media/image1.png', False
                ),
                call.xml()
            ],
            any_order=True
        )

    def it_knows_the_next_available_rId_to_help(self, rels_with_rId_gap):
        rels, expected_next_rId = rels_with_rId_gap
        next_rId = rels._next_rId
        assert next_rId == expected_next_rId

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

    @pytest.fixture
    def add_ext_rel_fixture_(self, reltype, url):
        rels = Relationships(None)
        return rels, reltype, url

    @pytest.fixture
    def add_matching_ext_rel_fixture_(self, request, reltype, url):
        rId = 'rId369'
        rels = Relationships(None)
        rels.add_relationship(reltype, url, rId, is_external=True)
        return rels, reltype, url, rId

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

    @pytest.fixture
    def _baseURI(self):
        return '/baseURI'

    @pytest.fixture
    def _Relationship_(self, request):
        return class_mock(request, 'docx.opc.rel._Relationship')

    @pytest.fixture
    def _rel_with_target_known_by_reltype(
            self, _rId, reltype, _target_part, _baseURI):
        rel = _Relationship(_rId, reltype, _target_part, _baseURI)
        return rel, reltype, _target_part

    @pytest.fixture
    def rels(self):
        """
        Populated Relationships instance that will exercise the rels.xml
        property.
        """
        rels = Relationships('/baseURI')
        rels.add_relationship(
            reltype='http://rt-hyperlink', target='http://some/link',
            rId='rId1', is_external=True
        )
        part = Mock(name='part')
        part.partname.relative_ref.return_value = '../media/image1.png'
        rels.add_relationship(reltype='http://rt-image', target=part,
                              rId='rId2')
        return rels

    @pytest.fixture
    def rels_elm(self, request):
        """
        Return a rels_elm mock that will be returned from
        CT_Relationships.new()
        """
        # create rels_elm mock with a .xml property
        rels_elm = Mock(name='rels_elm')
        xml = PropertyMock(name='xml')
        type(rels_elm).xml = xml
        rels_elm.attach_mock(xml, 'xml')
        rels_elm.reset_mock()  # to clear attach_mock call
        # patch CT_Relationships to return that rels_elm
        patch_ = patch.object(CT_Relationships, 'new', return_value=rels_elm)
        patch_.start()
        request.addfinalizer(patch_.stop)
        return rels_elm

    @pytest.fixture
    def _rel_with_known_target_part(
            self, _rId, reltype, _target_part, _baseURI):
        rel = _Relationship(_rId, reltype, _target_part, _baseURI)
        return rel, _rId, _target_part

    @pytest.fixture
    def rels_with_known_target_part(self, rels, _rel_with_known_target_part):
        rel, rId, target_part = _rel_with_known_target_part
        rels.add_relationship(None, target_part, rId)
        return rels, rId, target_part

    @pytest.fixture
    def rels_with_matching_rel_(self, request, rels):
        matching_reltype_ = instance_mock(
            request, str, name='matching_reltype_'
        )
        matching_part_ = instance_mock(
            request, Part, name='matching_part_'
        )
        matching_rel_ = instance_mock(
            request, _Relationship, name='matching_rel_',
            reltype=matching_reltype_, target_part=matching_part_,
            is_external=False
        )
        rels[1] = matching_rel_
        return rels, matching_reltype_, matching_part_, matching_rel_

    @pytest.fixture
    def rels_with_missing_rel_(self, request, rels, _Relationship_):
        missing_reltype_ = instance_mock(
            request, str, name='missing_reltype_'
        )
        missing_part_ = instance_mock(
            request, Part, name='missing_part_'
        )
        new_rel_ = instance_mock(
            request, _Relationship, name='new_rel_',
            reltype=missing_reltype_, target_part=missing_part_,
            is_external=False
        )
        _Relationship_.return_value = new_rel_
        return rels, missing_reltype_, missing_part_, new_rel_

    @pytest.fixture
    def rels_with_rId_gap(self, request):
        rels = Relationships(None)
        rel_with_rId1 = instance_mock(
            request, _Relationship, name='rel_with_rId1', rId='rId1'
        )
        rel_with_rId3 = instance_mock(
            request, _Relationship, name='rel_with_rId3', rId='rId3'
        )
        rels['rId1'] = rel_with_rId1
        rels['rId3'] = rel_with_rId3
        return rels, 'rId2'

    @pytest.fixture
    def rels_with_target_known_by_reltype(
            self, rels, _rel_with_target_known_by_reltype):
        rel, reltype, target_part = _rel_with_target_known_by_reltype
        rels[1] = rel
        return rels, reltype, target_part

    @pytest.fixture
    def reltype(self):
        return 'http://rel/type'

    @pytest.fixture
    def _rId(self):
        return 'rId6'

    @pytest.fixture
    def _target_part(self, request):
        return instance_mock(request, Part)

    @pytest.fixture
    def url(self):
        return 'https://github.com/scanny/python-docx'
