from schema import SchemaError
from .base_plugin import BasePlugin
from manifest_edit.context import Context

class ManifestIteratorPlugin(BasePlugin):
    """
    The ManifestIterator Plugin is a specialization of BasePlugin providing
    a different and more complex config member which, instead of returing
    a simple dictionary, behaves as a generator by yielding parts of a
    manifest that a plugin may need to modify.

    This base class must be used to implement plugins who want to leverage
    the "select and edit" concept as explained in the user documentation, where
    a part of the .yaml pipeline configuration file is just dedicated to
    select parts of a manifest and the rest is for the plugin-specific config.

    The "config" member is based on a ManifestIterator object, which must be
    specified at creation time, that can be specialized in either of the two
    supported manifest format .mpd or .m3u8 (Main only). Depending on the
    kind of manifest iterator, the .yaml syntax for element selection will
    be different.

    """

    def __init__(self, ManifestIteratorType):
        self.ManifestIteratorType = ManifestIteratorType

    # This is an override of the BasePlugin method
    def validate_config(self):
        # This is necessary to validate the configuration coming from the yml
        #  file once at loading time.
        # If the configuration is empty, it means this plugin will receive
        #  its configuration at runtime in the "storage" argument of the mpd
        #  method, so there is nothing to check.
        if self._config:
            self.manifest_iterator = self.ManifestIteratorType(
                self._config, plugin_config_schema=self.schema()
            )
            return self.manifest_iterator.is_valid()
        else:
            return True

    # This is an override of the BasePlugin method
    def config(self, manifest, storage):
        # The "config" method of a ManifestIteratorPlugin Type will use
        # either an iterator coming from ManifestIterator or will iterate
        # on the config passed from the previous plugin and intended to be
        # used by this plugin.
        if self._config is not None:
            # This means the yml config was valid and we have to use the
            #  internal ManifestIterator
            yield from self.manifest_iterator(manifest)
        else:
            # Plugins will pass a configuration down the pipeline by putting it
            #  into a {<plugin name> : {<config>}} dictionary. The split is
            #  there to remove the path. They would use the name of the plugin
            #  that must receive the configuration.
            for (plugin_config, element) in storage.get(self._name.split(".")[-1], []):
                try:
                    validated_config = self.schema().validate(plugin_config)
                    yield (validated_config, element)
                except SchemaError as e:
                    Context.log_error(
                        f'Invalid plugin configuration for {self._name.split(".")[-1]}!\n'
                        f'Got {e}'
                    )
                    yield StopIteration
