# encoding: utf-8

"""
Test suite for opc.pkgreader module
"""

from __future__ import absolute_import, print_function, unicode_literals

import pytest

from pptx.opc.constants import CONTENT_TYPE as CT, RELATIONSHIP_TARGET_MODE as RTM
from pptx.opc.oxml import CT_Relationship
from pptx.opc.packuri import PackURI
from pptx.opc.phys_pkg import _ZipPkgReader
from pptx.opc.pkgreader import (
    _ContentTypeMap,
    PackageReader,
    _SerializedPart,
    _SerializedRelationship,
    _SerializedRelationshipCollection,
)

from .unitdata.types import a_Default, a_Types, an_Override
from ..unitutil.mock import (
    call,
    class_mock,
    function_mock,
    initializer_mock,
    method_mock,
    Mock,
    patch,
)


class DescribePackageReader(object):
    @pytest.fixture
    def from_xml(self, request):
        return method_mock(request, _ContentTypeMap, "from_xml")

    @pytest.fixture
    def init(self, request):
        return initializer_mock(request, PackageReader)

    @pytest.fixture
    def _load_serialized_parts(self, request):
        return method_mock(request, PackageReader, "_load_serialized_parts")

    @pytest.fixture
    def PhysPkgReader_(self, request):
        _patch = patch("pptx.opc.pkgreader.PhysPkgReader", spec_set=_ZipPkgReader)
        request.addfinalizer(_patch.stop)
        return _patch.start()

    @pytest.fixture
    def _SerializedPart_(self, request):
        return class_mock(request, "pptx.opc.pkgreader._SerializedPart")

    @pytest.fixture
    def _SerializedRelationshipCollection_(self, request):
        return class_mock(
            request, "pptx.opc.pkgreader._SerializedRelationshipCollection"
        )

    @pytest.fixture
    def _srels_for(self, request):
        return method_mock(request, PackageReader, "_srels_for")

    @pytest.fixture
    def _walk_phys_parts(self, request):
        return method_mock(request, PackageReader, "_walk_phys_parts")

    def it_can_construct_from_pkg_file(
        self, init, PhysPkgReader_, from_xml, _srels_for, _load_serialized_parts
    ):
        # mockery ----------------------
        phys_reader = PhysPkgReader_.return_value
        content_types = from_xml.return_value
        pkg_srels = _srels_for.return_value
        sparts = _load_serialized_parts.return_value
        pkg_file = Mock(name="pkg_file")
        # exercise ---------------------
        pkg_reader = PackageReader.from_file(pkg_file)
        # verify -----------------------
        PhysPkgReader_.assert_called_once_with(pkg_file)
        from_xml.assert_called_once_with(phys_reader.content_types_xml)
        _srels_for.assert_called_once_with(phys_reader, "/")
        _load_serialized_parts.assert_called_once_with(
            phys_reader, pkg_srels, content_types
        )
        phys_reader.close.assert_called_once_with()
        init.assert_called_once_with(content_types, pkg_srels, sparts)
        assert isinstance(pkg_reader, PackageReader)

    def it_can_iterate_over_the_serialized_parts(self):
        # mockery ----------------------
        partname, content_type, blob = ("part/name.xml", "app/vnd.type", "<Part_1/>")
        spart = Mock(
            name="spart", partname=partname, content_type=content_type, blob=blob
        )
        pkg_reader = PackageReader(None, None, [spart])
        iter_count = 0
        # exercise ---------------------
        for retval in pkg_reader.iter_sparts():
            iter_count += 1
        # verify -----------------------
        assert retval == (partname, content_type, blob)
        assert iter_count == 1

    def it_can_iterate_over_all_the_srels(self):
        # mockery ----------------------
        pkg_srels = ["srel1", "srel2"]
        sparts = [
            Mock(name="spart1", partname="pn1", srels=["srel3", "srel4"]),
            Mock(name="spart2", partname="pn2", srels=["srel5", "srel6"]),
        ]
        pkg_reader = PackageReader(None, pkg_srels, sparts)
        # exercise ---------------------
        generated_tuples = [t for t in pkg_reader.iter_srels()]
        # verify -----------------------
        expected_tuples = [
            ("/", "srel1"),
            ("/", "srel2"),
            ("pn1", "srel3"),
            ("pn1", "srel4"),
            ("pn2", "srel5"),
            ("pn2", "srel6"),
        ]
        assert generated_tuples == expected_tuples

    def it_can_load_serialized_parts(self, _SerializedPart_, _walk_phys_parts):
        # test data --------------------
        test_data = (
            ("/part/name1.xml", "app/vnd.type_1", "<Part_1/>", "srels_1"),
            ("/part/name2.xml", "app/vnd.type_2", "<Part_2/>", "srels_2"),
        )
        iter_vals = [(t[0], t[2], t[3]) for t in test_data]
        content_types = dict((t[0], t[1]) for t in test_data)
        # mockery ----------------------
        phys_reader = Mock(name="phys_reader")
        pkg_srels = Mock(name="pkg_srels")
        _walk_phys_parts.return_value = iter_vals
        _SerializedPart_.side_effect = expected_sparts = (
            Mock(name="spart_1"),
            Mock(name="spart_2"),
        )
        # exercise ---------------------
        retval = PackageReader._load_serialized_parts(
            phys_reader, pkg_srels, content_types
        )
        # verify -----------------------
        expected_calls = [
            call("/part/name1.xml", "app/vnd.type_1", "<Part_1/>", "srels_1"),
            call("/part/name2.xml", "app/vnd.type_2", "<Part_2/>", "srels_2"),
        ]
        assert _SerializedPart_.call_args_list == expected_calls
        assert retval == expected_sparts

    def it_can_walk_phys_pkg_parts(self, _srels_for):
        # test data --------------------
        # +----------+       +--------+
        # | pkg_rels |-----> | part_1 |
        # +----------+       +--------+
        #      |               |    ^
        #      v               v    |
        #   external         +--------+     +--------+
        #                    | part_2 |---> | part_3 |
        #                    +--------+     +--------+
        partname_1, partname_2, partname_3 = (
            "/part/name1.xml",
            "/part/name2.xml",
            "/part/name3.xml",
        )
        part_1_blob, part_2_blob, part_3_blob = ("<Part_1/>", "<Part_2/>", "<Part_3/>")
        srels = [
            Mock(name="rId1", is_external=True),
            Mock(name="rId2", is_external=False, target_partname=partname_1),
            Mock(name="rId3", is_external=False, target_partname=partname_2),
            Mock(name="rId4", is_external=False, target_partname=partname_1),
            Mock(name="rId5", is_external=False, target_partname=partname_3),
        ]
        pkg_srels = srels[:2]
        part_1_srels = srels[2:3]
        part_2_srels = srels[3:5]
        part_3_srels = []
        # mockery ----------------------
        phys_reader = Mock(name="phys_reader")
        _srels_for.side_effect = [part_1_srels, part_2_srels, part_3_srels]
        phys_reader.blob_for.side_effect = [part_1_blob, part_2_blob, part_3_blob]
        # exercise ---------------------
        generated_tuples = [
            t for t in PackageReader._walk_phys_parts(phys_reader, pkg_srels)
        ]
        # verify -----------------------
        expected_tuples = [
            (partname_1, part_1_blob, part_1_srels),
            (partname_2, part_2_blob, part_2_srels),
            (partname_3, part_3_blob, part_3_srels),
        ]
        assert generated_tuples == expected_tuples

    def it_can_retrieve_srels_for_a_source_uri(
        self, _SerializedRelationshipCollection_
    ):
        # mockery ----------------------
        phys_reader = Mock(name="phys_reader")
        source_uri = Mock(name="source_uri")
        rels_xml = phys_reader.rels_xml_for.return_value
        load_from_xml = _SerializedRelationshipCollection_.load_from_xml
        srels = load_from_xml.return_value
        # exercise ---------------------
        retval = PackageReader._srels_for(phys_reader, source_uri)
        # verify -----------------------
        phys_reader.rels_xml_for.assert_called_once_with(source_uri)
        load_from_xml.assert_called_once_with(source_uri.baseURI, rels_xml)
        assert retval == srels


class Describe_ContentTypeMap(object):
    def it_can_construct_from_ct_item_xml(self, from_xml_fixture):
        content_types_xml, expected_defaults, expected_overrides = from_xml_fixture
        ct_map = _ContentTypeMap.from_xml(content_types_xml)
        assert ct_map._defaults == expected_defaults
        assert ct_map._overrides == expected_overrides

    def it_matches_an_override_on_case_insensitive_partname(
        self, match_override_fixture
    ):
        ct_map, partname, content_type = match_override_fixture
        assert ct_map[partname] == content_type

    def it_falls_back_to_case_insensitive_extension_default_match(
        self, match_default_fixture
    ):
        ct_map, partname, content_type = match_default_fixture
        assert ct_map[partname] == content_type

    def it_should_raise_on_partname_not_found(self):
        ct_map = _ContentTypeMap()
        with pytest.raises(KeyError):
            ct_map[PackURI("/!blat/rhumba.1x&")]

    def it_should_raise_on_key_not_instance_of_PackURI(self):
        ct_map = _ContentTypeMap()
        ct_map._add_override(PackURI("/part/name1.xml"), "app/vnd.type1")
        with pytest.raises(KeyError):
            ct_map["/part/name1.xml"]

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

    @pytest.fixture
    def from_xml_fixture(self):
        entries = (
            ("Default", "xml", CT.XML),
            ("Default", "PNG", CT.PNG),
            ("Override", "/ppt/presentation.xml", CT.PML_PRESENTATION_MAIN),
        )
        content_types_xml = self._xml_from(entries)
        expected_defaults = {}
        expected_overrides = {}
        for entry in entries:
            if entry[0] == "Default":
                ext = entry[1].lower()
                content_type = entry[2]
                expected_defaults[ext] = content_type
            elif entry[0] == "Override":
                partname, content_type = entry[1:]
                expected_overrides[partname] = content_type
        return content_types_xml, expected_defaults, expected_overrides

    @pytest.fixture(
        params=[
            ("/foo/bar.xml", "xml", "application/xml"),
            ("/foo/bar.PNG", "png", "image/png"),
            ("/foo/bar.jpg", "JPG", "image/jpeg"),
        ]
    )
    def match_default_fixture(self, request):
        partname_str, ext, content_type = request.param
        partname = PackURI(partname_str)
        ct_map = _ContentTypeMap()
        ct_map._add_override(PackURI("/bar/foo.xyz"), "application/xyz")
        ct_map._add_default(ext, content_type)
        return ct_map, partname, content_type

    @pytest.fixture(
        params=[
            ("/foo/bar.xml", "/foo/bar.xml"),
            ("/foo/bar.xml", "/FOO/Bar.XML"),
            ("/FoO/bAr.XmL", "/foo/bar.xml"),
        ]
    )
    def match_override_fixture(self, request):
        partname_str, should_match_partname_str = request.param
        partname = PackURI(partname_str)
        should_match_partname = PackURI(should_match_partname_str)
        content_type = "appl/vnd-foobar"
        ct_map = _ContentTypeMap()
        ct_map._add_override(partname, content_type)
        return ct_map, should_match_partname, content_type

    def _xml_from(self, entries):
        """
        Return XML for a [Content_Types].xml based on items in *entries*.
        """
        types_bldr = a_Types().with_nsdecls()
        for entry in entries:
            if entry[0] == "Default":
                ext, content_type = entry[1:]
                default_bldr = a_Default()
                default_bldr.with_Extension(ext)
                default_bldr.with_ContentType(content_type)
                types_bldr.with_child(default_bldr)
            elif entry[0] == "Override":
                partname, content_type = entry[1:]
                override_bldr = an_Override()
                override_bldr.with_PartName(partname)
                override_bldr.with_ContentType(content_type)
                types_bldr.with_child(override_bldr)
        return types_bldr.xml()


class Describe_SerializedPart(object):
    def it_remembers_construction_values(self):
        # test data --------------------
        partname = "/part/name.xml"
        content_type = "app/vnd.type"
        blob = "<Part/>"
        srels = "srels proxy"
        # exercise ---------------------
        spart = _SerializedPart(partname, content_type, blob, srels)
        # verify -----------------------
        assert spart.partname == partname
        assert spart.content_type == content_type
        assert spart.blob == blob
        assert spart.srels == srels


class Describe_SerializedRelationship(object):
    def it_remembers_construction_values(self):
        # test data --------------------
        rel_elm = CT_Relationship.new(
            "rId9", "ReLtYpE", "docProps/core.xml", RTM.INTERNAL
        )
        # exercise ---------------------
        srel = _SerializedRelationship("/", rel_elm)
        # verify -----------------------
        assert srel.rId == "rId9"
        assert srel.reltype == "ReLtYpE"
        assert srel.target_ref == "docProps/core.xml"
        assert srel.target_mode == RTM.INTERNAL

    def it_knows_when_it_is_external(self):
        cases = (RTM.INTERNAL, RTM.EXTERNAL)
        expected_values = (False, True)
        for target_mode, expected_value in zip(cases, expected_values):
            rel_elm = CT_Relationship.new(
                "rId9", "ReLtYpE", "docProps/core.xml", target_mode
            )
            srel = _SerializedRelationship(None, rel_elm)
            assert srel.is_external is expected_value

    def it_can_calculate_its_target_partname(self):
        # test data --------------------
        cases = (
            ("/", "docProps/core.xml", "/docProps/core.xml"),
            ("/ppt", "viewProps.xml", "/ppt/viewProps.xml"),
            (
                "/ppt/slides",
                "../slideLayouts/slideLayout1.xml",
                "/ppt/slideLayouts/slideLayout1.xml",
            ),
        )
        for baseURI, target_ref, expected_partname in cases:
            # setup --------------------
            rel_elm = Mock(
                name="rel_elm",
                rId=None,
                reltype=None,
                target_ref=target_ref,
                target_mode=RTM.INTERNAL,
            )
            # exercise -----------------
            srel = _SerializedRelationship(baseURI, rel_elm)
            # verify -------------------
            assert srel.target_partname == expected_partname

    def it_raises_on_target_partname_when_external(self):
        rel_elm = CT_Relationship.new(
            "rId9", "ReLtYpE", "docProps/core.xml", RTM.EXTERNAL
        )
        srel = _SerializedRelationship("/", rel_elm)
        with pytest.raises(ValueError):
            srel.target_partname


class Describe_SerializedRelationshipCollection(object):
    def it_can_load_from_xml(self, parse_xml, _SerializedRelationship_):
        # mockery ----------------------
        baseURI, rels_item_xml, rel_elm_1, rel_elm_2 = (
            Mock(name="baseURI"),
            Mock(name="rels_item_xml"),
            Mock(name="rel_elm_1"),
            Mock(name="rel_elm_2"),
        )
        rels_elm = Mock(name="rels_elm", relationship_lst=[rel_elm_1, rel_elm_2])
        parse_xml.return_value = rels_elm
        # exercise ---------------------
        srels = _SerializedRelationshipCollection.load_from_xml(baseURI, rels_item_xml)
        # verify -----------------------
        expected_calls = [call(baseURI, rel_elm_1), call(baseURI, rel_elm_2)]
        parse_xml.assert_called_once_with(rels_item_xml)
        assert _SerializedRelationship_.call_args_list == expected_calls
        assert isinstance(srels, _SerializedRelationshipCollection)

    def it_should_be_iterable(self):
        srels = _SerializedRelationshipCollection()
        try:
            for x in srels:
                pass
        except TypeError:
            msg = "_SerializedRelationshipCollection object is not iterable"
            pytest.fail(msg)

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

    @pytest.fixture
    def parse_xml(self, request):
        return function_mock(request, "pptx.opc.pkgreader.parse_xml")

    @pytest.fixture
    def _SerializedRelationship_(self, request):
        return class_mock(request, "pptx.opc.pkgreader._SerializedRelationship")
