from manifest_edit.plugin.mpd import BasePlugin
from manifest_edit import libfmp4
from schema import Schema, Optional


class Plugin(BasePlugin):
    '''
    service_description_add plugin.

    Implements the basic functionalities of a Service Description as
    required by the Low Latency use case and as supported by libfmp4
    as in https://docs.unified-streaming.com/documentation/live/
    dvb-dash-low-latency-mode.html.

    Complete support of the ServiceDescription element is not possible
    until libfmp4 will not support it.

    Example of configuration:

    .. code-block:: yaml

        # A list is necessary, there could be more than one SD
        - id: "1"
        Scope:
            # There can be more than one scope
            - schemeIdUri: "urn:dvb:dash:lowlatency:scope:2019"
            value: '' #optional
            id: '' #optional
        Latency:
            min: ''
            max: ''
            target: ''
            referenceId: ''
        PlaybackRate:
            # There can be more than one playback rate
            - min: '' #etsi real
            max: '' #etsi real

    '''

    _name = __name__

    _keys = [
        'id',
        'Scope',
        'Latency',
        'PlaybackRate'
    ]

    def schema(self):
        return Schema(
            [
                {
                    Optional(self._keys[0]): str,
                    self._keys[1]: [
                        {
                            'schemeIdUri': str,
                            Optional('value'): str,
                            Optional('id'): str
                        }
                    ],
                    Optional(self._keys[2]):
                    {
                        Optional('min'): int,
                        Optional('max'): int,
                        Optional('target'): int,
                        Optional('referenceId'): int,
                    },
                    Optional(self._keys[3]):
                    [
                        {
                            # EtsiReal types can only be created from a string
                            # in libfmp4
                            Optional('min'): str,
                            Optional('max'): str,
                        }
                    ]
                }
            ]
        )

    def _sd_add_id(self, sd, service_description_config):
        # Create an id. If not present in the config, do nothing. The
        # ServiceDescription constructor has already put
        # a sensible default
        try:
            sd.id = service_description_config[self._keys[0]]
        except KeyError:
            # No big deal, this is an optional field and can be absent
            #  from the config
            pass

    def _sd_add_scopes(self, sd, service_description_config):
        # Create a Scope list. This is mandatory. I know it's there or
        # schema would not validate the config
        scopes_config = service_description_config[self._keys[1]]
        for scope_config in scopes_config:
            scope = libfmp4.mpd.Descriptor()

            scope.schemeIdUri = scope_config['schemeIdUri']

            try:
                scope.value = scope_config['value']
            except KeyError:
                # No big deal, this is an optional field and can be absent
                #  from the config
                pass

            try:
                scope.id = scope_config['id']
            except KeyError:
                # No big deal, this is an optional field and can be absent
                #  from the config
                pass

            sd.scopes.append(scope)


    def _sd_add_latency(self, sd, service_description_config):
        # Create an (optional in libfmp4) latency
        latency_config = service_description_config.get(self._keys[2], None)
        if latency_config:
            latency = libfmp4.mpd.Latency()

            try:
                latency.optMin = latency_config['min']
            except KeyError:
                # No big deal, this is an optional field and can be absent
                #  from the config
                pass

            try:
                latency.optMax = latency_config['max']
            except KeyError:
                # No big deal, this is an optional field and can be absent
                #  from the config
                pass

            try:
                latency.optTarget = latency_config['target']
            except KeyError:
                # No big deal, this is an optional field and can be absent
                #  from the config
                pass

            try:
                latency.optReferenceId = latency_config['referenceId']
            except KeyError:
                # No big deal, this is an optional field and can be absent
                #  from the config
                pass

            sd.optLatency = latency

    def _sd_add_playback_rates(self, sd, service_description_config):
        # Create a playback rate list (not optional in libfmp4)
        pb_rates_config = service_description_config.get(self._keys[3], [])
        if pb_rates_config:
            for pb_rate_config in pb_rates_config:
                playback_rate = libfmp4.mpd.PlaybackRate()

                try:
                    optMin = libfmp4.EtsiReal(pb_rate_config['min'])
                    playback_rate.optMin = optMin
                except KeyError:
                    # No big deal, this is an optional field and can be absent
                    #  from the config
                    pass

                try:
                    optMax = libfmp4.EtsiReal(pb_rate_config['max'])
                    playback_rate.optMax = optMax
                except KeyError:
                    # No big deal, this is an optional field and can be absent
                    #  from the config
                    pass

                sd.playbackRates.append(playback_rate)

    def _supplemental_property_add(self, manifest, storage):

        for service_description_config in self.config(storage):

            sd = libfmp4.mpd.ServiceDescription()

            self._sd_add_id(sd, service_description_config)
            self._sd_add_scopes(sd, service_description_config)
            self._sd_add_latency(sd, service_description_config)
            self._sd_add_playback_rates(sd, service_description_config)

            manifest.serviceDescriptions.append(sd)

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