from manifest_edit.plugin.mpd import ManifestIteratorPlugin
from manifest_edit.context import Context
from manifest_edit import libfmp4
from schema import Schema, Optional, Or


class Plugin(ManifestIteratorPlugin):
    """
    descriptor add plugin.

    The purpose of this plugin is to add a generic descriptor to a DASH
    manifest, in a specific position.

    The position is determined by the usual mechanisms implemented by
    ManifestIterator.

    The descriptor is specified by providing:

    - name: "type"
    - schemeIdUri: "schemeIdUri_content"
    - value: "value_content" (optional)
    - id: "id_content" (optional)

    This will generate the following descriptor:

    ..code-block: xml

      < type
        schemeIdUri="schemeIdUri_content"
        value="value_content"
        id="id_content" />

    "type" must be one of the supported descriptors, that is one of:

    - EssentialProperty
    - SupplementalProperty
    - Accessibility
    - Roles
    - AudioChannelConfiguration
    - UTCTiming

    If the optional "value" or "id" are not provided, the xml will not report
    the attribute at all.
    """

    _name = __name__

    _keys = ["name", "schemeIdUri", "value", "id", "force"]

    # This is just the specific ["how" config] for this plugin
    def schema(self):
        return Schema(
            {
                self._keys[0]: Or(
                    "EssentialProperty",
                    "SupplementalProperty",
                    "Accessibility",
                    "Role",
                    "AudioChannelConfiguration",
                    "UTCTiming",
                ),
                self._keys[1]: str,
                Optional(self._keys[2], default=""): str,
                Optional(self._keys[3], default=""): str,
                Optional(self._keys[4], default=False): bool,
            }
        )

    # maps a descriptor name to the corresponding member in libfmp4. We
    # will use this to infer if the provided configuration is legal.
    # i.e.:
    # the standard affirms that UTCTiming descriptors can only be present
    # in an MPD element. In libfmp4, accordingly, the fmp4::mpd::manifest_t
    # data type has an "utcTimings" member, storing "UTCTiming" instances.
    # If an attempt is done to add an "UTCTiming" descriptor to, say, an
    # adaptation set, an error must be thrown. This can be detected in python
    # by checking if a libfmp4.mpd.AdaptationSet type has an "utcTimings"
    # attribute. So, basically if libfpm4 doesn't have a "place" to store the
    # descriptor, then you must have done something wrong in the config.
    descriptor_rules = {
        "EssentialProperty": "essentialProperties",
        "SupplementalProperty": "supplementalProperties",
        "Accessibility": "accessibilities",
        "Role": "roles",
        "AudioChannelConfiguration": "audioChannelConfigurations",
        "UTCTiming": "utcTimings", 
    }

    def _checkIfExists(self, descriptor_config, element):
        descriptors = getattr(element, self.descriptor_rules[descriptor_config["name"]])
        if descriptor_config["force"] == False and any(
            [
                descriptor.schemeIdUri == descriptor_config["schemeIdUri"]
                and descriptor.value == descriptor_config["value"]
                for descriptor in descriptors
            ]
        ):
            return True
        else:
            return False

    def addDescriptor(self, manifest, storage):
        for descriptor_config, element in self.config(manifest, storage):
            # Get the name of the descriptor, check if the selected element
            # has the correct attribute to store it, otherwise we just
            # assume that the configuration is wrong
            if hasattr(element, self.descriptor_rules[descriptor_config["name"]]):
                # OK, it makes sens to add the descriptor in this point of
                # the manifest.
                # However, we will going to add it only if an identical one
                # (based on schemeIdUri, Value) does not exists already
                Context.log_trace(
                    f'Trying to add {descriptor_config["name"]} to {type(element)}'
                )
                if not self._checkIfExists(descriptor_config, element):
                    getattr(
                        element, self.descriptor_rules[descriptor_config["name"]]
                    ).append(
                        libfmp4.mpd.Descriptor(
                            schemeIdUri=descriptor_config[self._keys[1]],
                            value=descriptor_config[self._keys[2]],
                            id=descriptor_config[self._keys[3]],
                        )
                    )
                else:
                    Context.log_error(
                        f"A descriptor with the same SchemeIdUri and Value than the "
                        f"one you are trying to add exists already in the manifest! "
                        f'See {descriptor_config["name"]} elements in {type(element)} '
                    )
            else:
                Context.log_error(
                    f'You cannot add a {descriptor_config["name"]} to an {type(element)}!'
                )

    def process(self, manifest, storage):
        self.addDescriptor(manifest, storage)
