# We thought about making pycaption.base objects immutable. This would be nice
# in a lot of cases, but since the transformations on them could be quite
# complex, the deepcopy method is good enough sometimes.
from copy import deepcopy
from xml.sax.saxutils import escape

from bs4 import BeautifulSoup

from ..base import BaseWriter, CaptionNode, merge_concurrent_captions
from .base import DFXP_DEFAULT_REGION, DFXPWriter

LEGACY_DFXP_BASE_MARKUP = """
<tt xmlns="http://www.w3.org/ns/ttml"
    xmlns:tts="http://www.w3.org/ns/ttml#styling">
    <head>
        <styling/>
        <layout/>
    </head>
    <body/>
</tt>
"""

LEGACY_DFXP_DEFAULT_STYLE = {
    "color": "white",
    "font-family": "monospace",
    "font-size": "1c",
}

LEGACY_DFXP_DEFAULT_STYLE_ID = "default"
LEGACY_DFXP_DEFAULT_REGION_ID = "bottom"

LEGACY_DFXP_DEFAULT_REGION = {"text-align": "center", "display-align": "after"}


class SinglePositioningDFXPWriter(DFXPWriter):
    """
    A dfxp writer, that ignores all positioning, using a single provided value
    """

    def __init__(self, default_positioning=DFXP_DEFAULT_REGION, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.default_positioning = default_positioning

    def write(self, captions_set, force=""):
        """Writes a DFXP file using the positioning provided in the initializer

        :type captions_set: pycaption.base.CaptionSet
        :param force: only write this language, if available in the CaptionSet
        :rtype: str
        """
        captions_set = self._create_single_positioning_caption_set(
            captions_set, self.default_positioning
        )

        return super().write(captions_set, force)  # noqa

    @staticmethod
    def _create_single_positioning_caption_set(caption_set, positioning):
        """Return a caption where all the positioning information was
        replaced from positioning

        :type caption_set: pycaption.base.CaptionSet
        :rtype: pycaption.base.CaptionSet
        """
        # If SinglePositioningDFXPWriter would modify the state of the caption
        # set, any writer using the same caption_set thereafter would be
        # affected. At the moment we know we don't use any other writers, but
        # this is important and mustn't be neglected
        caption_set = deepcopy(caption_set)
        caption_set = merge_concurrent_captions(caption_set)
        caption_set.layout_info = positioning

        for lang in caption_set.get_languages():
            caption_set.set_layout_info(lang, positioning)

            caption_list = caption_set.get_captions(lang)
            for caption in caption_list:
                caption.layout_info = positioning

                for node in caption.nodes:
                    if hasattr(node, "layout_info"):
                        node.layout_info = positioning

        for _, style in caption_set.get_styles():
            if "text-align" in style:
                style.pop("text-align")

        return caption_set


class LegacyDFXPWriter(BaseWriter):
    """Ported the legacy DFXPWriter from 0.4.5"""

    def __init__(self, *args, **kw):
        self.p_style = False
        self.open_span = False

    def write(self, caption_set, force=""):
        caption_set = deepcopy(caption_set)
        caption_set = merge_concurrent_captions(caption_set)

        dfxp = BeautifulSoup(LEGACY_DFXP_BASE_MARKUP, "lxml-xml")
        dfxp.find("tt")["xml:lang"] = "en"

        for style_id, style in caption_set.get_styles():
            if style != {}:
                dfxp = self._recreate_styling_tag(style_id, style, dfxp)
        if not caption_set.get_styles():
            dfxp = self._recreate_styling_tag(
                LEGACY_DFXP_DEFAULT_STYLE_ID, LEGACY_DFXP_DEFAULT_STYLE, dfxp
            )

        # XXX For now we will always use this default region. In the future if
        # regions are provided, they will be kept
        dfxp = self._recreate_region_tag(
            LEGACY_DFXP_DEFAULT_REGION_ID, LEGACY_DFXP_DEFAULT_REGION, dfxp
        )

        body = dfxp.find("body")

        if force:
            langs = [self._force_language(force, caption_set.get_languages())]
        else:
            langs = caption_set.get_languages()

        for lang in langs:
            div = dfxp.new_tag("div")
            div["xml:lang"] = lang

            for caption in caption_set.get_captions(lang):
                if caption.style:
                    caption_style = caption.style
                    caption_style.update({"region": LEGACY_DFXP_DEFAULT_REGION_ID})
                else:
                    caption_style = {
                        "class": LEGACY_DFXP_DEFAULT_STYLE_ID,
                        "region": LEGACY_DFXP_DEFAULT_REGION_ID,
                    }
                p = self._recreate_p_tag(caption, caption_style, dfxp)
                div.append(p)

            body.append(div)

        caption_content = dfxp.prettify(formatter=None)
        return caption_content

    # force the DFXP to only have one language, trying to match on "force"
    def _force_language(self, force, langs):
        for lang in langs:
            if force == lang:
                return lang

        return langs[-1]

    def _recreate_region_tag(self, region_id, styling, dfxp):
        dfxp_region = dfxp.new_tag("region")
        dfxp_region.attrs.update({"xml:id": region_id})

        attributes = self._recreate_style(styling, dfxp)
        dfxp_region.attrs.update(attributes)

        new_tag = dfxp.new_tag("region")
        new_tag.attrs.update({"xml:id": region_id})
        if dfxp_region != new_tag:
            dfxp.find("layout").append(dfxp_region)
        return dfxp

    def _recreate_styling_tag(self, style, content, dfxp):
        dfxp_style = dfxp.new_tag("style")
        dfxp_style.attrs.update({"xml:id": style})

        attributes = self._recreate_style(content, dfxp)
        dfxp_style.attrs.update(attributes)

        new_tag = dfxp.new_tag("style")
        new_tag.attrs.update({"xml:id": style})
        if dfxp_style != new_tag:
            dfxp.find("styling").append(dfxp_style)

        return dfxp

    def _recreate_p_tag(self, caption, caption_style, dfxp):
        start = caption.format_start()
        end = caption.format_end()
        p = dfxp.new_tag("p", begin=start, end=end)
        p.string = self._recreate_text(caption, dfxp)

        if dfxp.find("style", {"xml:id": "p"}):
            p["style"] = "p"

        p.attrs.update(self._recreate_style(caption_style, dfxp))

        return p

    def _recreate_text(self, caption, dfxp):
        line = ""

        for node in caption.nodes:
            if node.type_ == CaptionNode.TEXT:
                line += escape(node.content) + " "

            elif node.type_ == CaptionNode.BREAK:
                line = line.rstrip() + "<br/>\n    "

            elif node.type_ == CaptionNode.STYLE:
                line = self._recreate_span(line, node, dfxp)

        return line.rstrip()

    def _recreate_span(self, line, node, dfxp):
        if node.start:
            styles = ""

            content_with_style = self._recreate_style(node.content, dfxp)
            for style, value in list(content_with_style.items()):
                styles += f' {style}="{value}"'

            if styles:
                if self.open_span:
                    line = line.rstrip() + "</span> "
                line += f"<span{styles}>"
                self.open_span = True

        elif self.open_span:
            line = line.rstrip() + "</span> "
            self.open_span = False

        return line

    def _recreate_style(self, content, dfxp):
        dfxp_style = {}

        if "region" in content:
            if dfxp.find("region", {"xml:id": content["region"]}):
                dfxp_style["region"] = content["region"]
        if "class" in content:
            if dfxp.find("style", {"xml:id": content["class"]}):
                dfxp_style["style"] = content["class"]
        if "text-align" in content:
            dfxp_style["tts:textAlign"] = content["text-align"]
        if "italics" in content:
            dfxp_style["tts:fontStyle"] = "italic"
        if "font-family" in content:
            dfxp_style["tts:fontFamily"] = content["font-family"]
        if "font-size" in content:
            dfxp_style["tts:fontSize"] = content["font-size"]
        if "color" in content:
            dfxp_style["tts:color"] = content["color"]
        if "display-align" in content:
            dfxp_style["tts:displayAlign"] = content["display-align"]

        return dfxp_style
