from manifest_edit.plugin.manifest_iterators import (
    ManifestIterator as BaseManifestIterator,
)
from manifest_edit.plugin.manifest_iterators import FieldMatcher as FieldMatcherBaseClass
from manifest_edit.libfmp4.mpd import AdaptationSet

from schema import Schema, Or, And, Optional
import re


def _is_config_dict_not_empty(data):
    return len(data.keys()) > 1

def _mpd_get_function(element, attribute_name, default=None):
    '''
    This function was designed to work with Attributes, for which it can
    use the getattr function directly.

    It was later expended to support some Elements or array of Elements,
    with a simple strategy returning the str representation of the type in
    question. For selection purposes, after all, it may be acceptable, when
    having e.g. a role

    <Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"/>

    to allow a simple yaml selection syntax

    plugins_config:
      role: "main"

    that will search for "main" in the whole
    " schemeIdUri="urn:mpeg:dash:role:2011" value="main" " string.

    For array of elements, the logic is to return a match if at least one of
    the elements in the array matches.

    This unfortunately needs a logic where specific Element names are explicitly
    intercepted and matched to a certain attribute of the element, because
    many elements, notably

        "EssentialProperty",
        "SupplementalProperty",
        "Accessibility",
        "Role",
        "AudioChannelConfiguration",

    share the same descriptor_t data type.
    '''

    sub_elements = None

    # Intercept supported element names
    if attribute_name.lower() == "essentialproperty":
        sub_elements = getattr(element, "essentialProperties", None)
    elif attribute_name.lower() == "supplementalproperty":
        sub_elements = getattr(element, "supplementalProperties", None)
    elif attribute_name.lower() == "accessibility":
        sub_elements = getattr(element, "accessibilities", None)
    elif attribute_name.lower() == "role":
        sub_elements = getattr(element, "roles", None)
    elif attribute_name.lower() == "audiochannelconfiguration":
        sub_elements = getattr(element, "audioChannelConfigurations", None)

    if sub_elements:
        return " ".join([str(sub_element) for sub_element in sub_elements])

    return getattr(element, attribute_name, None)


def _get_representations_from_adaptationset(node_item):
    # If this is not an adaptation set, the function returns false.
    if not isinstance(node_item, AdaptationSet):
        return

    yield from node_item.representations


# Ok this sucks, why passing the functions and not overriding?
class FieldMatcher(FieldMatcherBaseClass):
    def __init__(self, get_function=getattr, get_children=lambda x: x):
        super().__init__(get_function=get_function, get_children=get_children)

    # Override of base class function, this is only ever needed for MPD
    def match_father_by_child_property(
        self, item_selection_key, node_item, item
    ):
        # Additional matching logic that is applied only for the special case
        # of Adaptation Sets selection based on properties of their
        # Representations.
        #
        # This is useful especially for attributes in the
        # representation_base_t common_;
        # member which is found both in the adaptation_set_t and in the
        # representation_t data types.

        # What we want to do is:
        # - if the field exists in ALL the representations and the match is
        #   true for ALL of them, then return True. Else return False.

        # Let's perform the check for every representation
        matches_list = [
            self.node_matches_one(item_selection_key, representation, item)
            for representation in self.get_children(node_item)
        ]

        # Return true only if the field exists in all representations and if
        # the match is True for all of them.
        # Notice that all([]) returns True for some reason so have to intercept
        # it.
        return matches_list and all(matches_list)


class ManifestIterator(BaseManifestIterator):

    _field_matcher = FieldMatcher(get_function=_mpd_get_function, get_children=_get_representations_from_adaptationset)

    _recurse_in_root = ["periods", "profiles"]

    _recurse_in_period = ["adaptationSets"]

    _recurse_in_adaptation_set = ["representations","accessibilities"]

    def _schema(self):

        return Schema(
            Or(
                {"plugin_config": self._plugin_config_schema},
                {
                    # 'periods' is mandatory. After this, you either have the
                    #   specific plugin configuration or (possibly multiple)
                    #   couples of <key> : <regular expression> couples, followed
                    #   by the 'adaptationSets' keyword.
                    Or(*self._recurse_in_root): Or(
                        {"plugin_config": self._plugin_config_schema},
                        [
                            And(
                                {
                                    str: lambda s: re.compile(s),
                                    Optional(
                                        "plugin_config"
                                    ): self._plugin_config_schema,
                                    Optional(Or(*self._recurse_in_period)): Or(
                                        {"plugin_config": self._plugin_config_schema},
                                        [
                                            And(
                                                {
                                                    str: lambda s: re.compile(s),
                                                    Optional(
                                                        "plugin_config"
                                                    ): self._plugin_config_schema,
                                                    Optional(Or(*self._recurse_in_adaptation_set)): Or(
                                                        {
                                                            "plugin_config": self._plugin_config_schema
                                                        },
                                                        [
                                                            {
                                                                str: lambda s: re.compile(
                                                                    s
                                                                ),
                                                                "plugin_config": self._plugin_config_schema,
                                                            }
                                                        ],
                                                    ),
                                                },
                                                _is_config_dict_not_empty,
                                            )
                                        ],
                                    ),
                                },
                                _is_config_dict_not_empty,
                            )
                        ],
                    )
                },
            )
        )
