# encoding: utf-8

"""
Test suite for opc.pkgwriter module
"""

import pytest

from docx.opc.constants import CONTENT_TYPE as CT
from docx.opc.package import Part
from docx.opc.packuri import PackURI
from docx.opc.phys_pkg import _ZipPkgWriter
from docx.opc.pkgwriter import _ContentTypesItem, PackageWriter

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


class DescribePackageWriter(object):

    def it_can_write_a_package(self, PhysPkgWriter_, _write_methods):
        # mockery ----------------------
        pkg_file = Mock(name='pkg_file')
        pkg_rels = Mock(name='pkg_rels')
        parts = Mock(name='parts')
        phys_writer = PhysPkgWriter_.return_value
        # exercise ---------------------
        PackageWriter.write(pkg_file, pkg_rels, parts)
        # verify -----------------------
        expected_calls = [
            call._write_content_types_stream(phys_writer, parts),
            call._write_pkg_rels(phys_writer, pkg_rels),
            call._write_parts(phys_writer, parts),
        ]
        PhysPkgWriter_.assert_called_once_with(pkg_file)
        assert _write_methods.mock_calls == expected_calls
        phys_writer.close.assert_called_once_with()

    def it_can_write_a_content_types_stream(self, write_cti_fixture):
        _ContentTypesItem_, parts_, phys_pkg_writer_, blob_ = (
            write_cti_fixture
        )
        PackageWriter._write_content_types_stream(phys_pkg_writer_, parts_)
        _ContentTypesItem_.from_parts.assert_called_once_with(parts_)
        phys_pkg_writer_.write.assert_called_once_with(
            '/[Content_Types].xml', blob_
        )

    def it_can_write_a_pkg_rels_item(self):
        # mockery ----------------------
        phys_writer = Mock(name='phys_writer')
        pkg_rels = Mock(name='pkg_rels')
        # exercise ---------------------
        PackageWriter._write_pkg_rels(phys_writer, pkg_rels)
        # verify -----------------------
        phys_writer.write.assert_called_once_with('/_rels/.rels',
                                                  pkg_rels.xml)

    def it_can_write_a_list_of_parts(self):
        # mockery ----------------------
        phys_writer = Mock(name='phys_writer')
        rels = MagicMock(name='rels')
        rels.__len__.return_value = 1
        part1 = Mock(name='part1', _rels=rels)
        part2 = Mock(name='part2', _rels=[])
        # exercise ---------------------
        PackageWriter._write_parts(phys_writer, [part1, part2])
        # verify -----------------------
        expected_calls = [
            call(part1.partname, part1.blob),
            call(part1.partname.rels_uri, part1._rels.xml),
            call(part2.partname, part2.blob),
        ]
        assert phys_writer.write.mock_calls == expected_calls

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

    @pytest.fixture
    def blob_(self, request):
        return instance_mock(request, str)

    @pytest.fixture
    def cti_(self, request, blob_):
        return instance_mock(request, _ContentTypesItem, blob=blob_)

    @pytest.fixture
    def _ContentTypesItem_(self, request, cti_):
        _ContentTypesItem_ = class_mock(
            request, 'docx.opc.pkgwriter._ContentTypesItem'
        )
        _ContentTypesItem_.from_parts.return_value = cti_
        return _ContentTypesItem_

    @pytest.fixture
    def parts_(self, request):
        return instance_mock(request, list)

    @pytest.fixture
    def PhysPkgWriter_(self, request):
        _patch = patch('docx.opc.pkgwriter.PhysPkgWriter')
        request.addfinalizer(_patch.stop)
        return _patch.start()

    @pytest.fixture
    def phys_pkg_writer_(self, request):
        return instance_mock(request, _ZipPkgWriter)

    @pytest.fixture
    def write_cti_fixture(
            self, _ContentTypesItem_, parts_, phys_pkg_writer_, blob_):
        return _ContentTypesItem_, parts_, phys_pkg_writer_, blob_

    @pytest.fixture
    def _write_methods(self, request):
        """Mock that patches all the _write_* methods of PackageWriter"""
        root_mock = Mock(name='PackageWriter')
        patch1 = patch.object(PackageWriter, '_write_content_types_stream')
        patch2 = patch.object(PackageWriter, '_write_pkg_rels')
        patch3 = patch.object(PackageWriter, '_write_parts')
        root_mock.attach_mock(patch1.start(), '_write_content_types_stream')
        root_mock.attach_mock(patch2.start(), '_write_pkg_rels')
        root_mock.attach_mock(patch3.start(), '_write_parts')

        def fin():
            patch1.stop()
            patch2.stop()
            patch3.stop()

        request.addfinalizer(fin)
        return root_mock

    @pytest.fixture
    def xml_for_(self, request):
        return method_mock(request, _ContentTypesItem, 'xml_for')


class Describe_ContentTypesItem(object):

    def it_can_compose_content_types_element(self, xml_for_fixture):
        cti, expected_xml = xml_for_fixture
        types_elm = cti._element
        assert types_elm.xml == expected_xml

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

    def _mock_part(self, request, name, partname_str, content_type):
        partname = PackURI(partname_str)
        return instance_mock(
            request, Part, name=name, partname=partname,
            content_type=content_type
        )

    @pytest.fixture(params=[
        ('Default',  '/ppt/MEDIA/image.PNG',   CT.PNG),
        ('Default',  '/ppt/media/image.xml',   CT.XML),
        ('Default',  '/ppt/media/image.rels',  CT.OPC_RELATIONSHIPS),
        ('Default',  '/ppt/media/image.jpeg',  CT.JPEG),
        ('Override', '/docProps/core.xml',     'app/vnd.core'),
        ('Override', '/ppt/slides/slide1.xml', 'app/vnd.ct_sld'),
        ('Override', '/zebra/foo.bar',         'app/vnd.foobar'),
    ])
    def xml_for_fixture(self, request):
        elm_type, partname_str, content_type = request.param
        part_ = self._mock_part(request, 'part_', partname_str, content_type)
        cti = _ContentTypesItem.from_parts([part_])
        # expected_xml -----------------
        types_bldr = a_Types().with_nsdecls()
        ext = partname_str.split('.')[-1].lower()
        if elm_type == 'Default' and ext not in ('rels', 'xml'):
            default_bldr = a_Default()
            default_bldr.with_Extension(ext)
            default_bldr.with_ContentType(content_type)
            types_bldr.with_child(default_bldr)

        types_bldr.with_child(
            a_Default().with_Extension('rels')
                       .with_ContentType(CT.OPC_RELATIONSHIPS)
        )
        types_bldr.with_child(
            a_Default().with_Extension('xml').with_ContentType(CT.XML)
        )

        if elm_type == 'Override':
            override_bldr = an_Override()
            override_bldr.with_PartName(partname_str)
            override_bldr.with_ContentType(content_type)
            types_bldr.with_child(override_bldr)

        expected_xml = types_bldr.xml()
        return cti, expected_xml
