from manifest_edit.plugin.m3u8_media import BasePlugin
from schema import Schema, Optional, Or
from manifest_edit.context import Context
import re


class Plugin(BasePlugin):
    """
    HLS inline custom tag plugin

    This plugin reads the X-MESSAGE-DATA payload from the EXT-X-DATERANGE tags
    and inlines it in the manifest.

    The whole motivation of this plugin lies on the fact that in a Remix-based
    workflow, custom timed metadata always ends up in the EXT-X-DATERANGE tag,
    although this is not always what specific Ad-insertion partners require
    or support. Hence, the need for a plugin able to customize HLS custom
    timed metadata signalling.

    The yaml configuration supports ways of selecting one or more specific
    EXT-X-DATERANGE tag to transform, for cases where a playlist contain more
    than one. Selection is possible based on the "class" attribute (mandatory)
    and the "id" attribute (based on regex, optional).

    The EXT-X-DATERANGE is selected for transformation only if all the above
    are true:

    - EXT-X-DATERANGE id matches the regex provided in the config
    - EXT-X-DATERANGE class == class provided in the config
    - EXT-X-DATERANGE X-MESSAGE-DATA is not empty
    """

    _name = __name__

    _keys = ["id", "class", "remove-EXT-X-DATERANGE"]

    _function_for_schema = {"http://unified-streaming.com/hls/inline": "hls_inline"}

    def schema(self):
        return Schema(
            [
                {
                    Optional(self._keys[0], default=r".*"): lambda s: re.compile(s),
                    self._keys[1]: Or(
                        "http://unified-streaming.com/hls/inline"
                    ),  # selection key, extend to add other valid ones
                    self._keys[2]: bool,  # selection key
                }
            ]
        )

    def inline_and_pop(self, element, dateranges, config_item):
        inlined_dateranges = []
        for i, daterange in enumerate(dateranges):
            if (
                re.fullmatch(
                    config_item[self._keys[0]],
                    str(getattr(daterange, self._keys[0])),
                )
                and config_item[self._keys[1]] == str(getattr(daterange, "class_"))
                and daterange.optMessageData
            ):
                Context.log_debug(
                    f"Matching #EXT-X-DATERANGE with id {daterange.id} X-MESSAGE-DATA {daterange.optMessageData}"
                )
                element.additionalTags.append(
                    "".join([chr(b) for b in daterange.optMessageData])
                )
                inlined_dateranges.append(daterange)

        # Remove all the EXT-X-DATERANGE tags that were inlined
        # Removing stuff in place from bound std::vector is a pain!
        if config_item[self._keys[2]]:
            for inlined_dr in inlined_dateranges:
                for i, dr in enumerate(dateranges):
                    if dr == inlined_dr:
                        dateranges.pop(i)
                        break

    def hls_inline(self, manifest, config_item):
        """
        This function takes the X-MESSAGE-DATA payload from the selected
        EXT-X-DATERANGE tag and inlines it in the manifest.

        Optionally, it removes the originally EXT-X-DATERANGE tag from the
        manifest.
        """

        for extinf in manifest.extInfs:
            self.inline_and_pop(extinf, extinf.dateRanges, config_item)

        self.inline_and_pop(manifest, manifest.dateRanges, config_item)
        self.inline_and_pop(manifest, manifest.dateRangesTail, config_item)

    def process(self, manifest, storage):
        for config_item in self.config(storage):
            # schema check guarantees the key is always there, no need to check
            func = self._function_for_schema[config_item[self._keys[1]]]
            return getattr(self, func)(manifest, config_item)
