"""
    This module contains the entry points to the manifest-edit python
    application.

    These are two, and must be invoked in order:

    1. a "setup" method, to prepare a processing pipeline by loading python
    plugins from disk based on a .yaml configuration file
    2. an "update" method, to process a manifest through the pipeline

    It must be noticed that the manifest is expected to be represented with the
    appropriate libfmp4.mpd.Manifest type.

    For details, please refere to the individual methods' documentation.

    All other methods are private and not supposed to be invoked from other
    modules.
"""

from manifest_edit import libfmp4
from .pipeline import Pipeline
from .context import Context

_comment_header = "Edited by Unified manifest-edit"
_comment_pipeline_preamble = ", using the following pipeline:\n"


def _addManifestEditComment(context, manifest, pipeline_config, verbose_manifest=True):
    manifest_type = type(manifest)
    if manifest_type == libfmp4.mpd.Manifest:
        if verbose_manifest:
            manifest.comment = _comment_header + _comment_pipeline_preamble + pipeline_config
        else:
            manifest.comment = "";
    elif manifest_type == libfmp4.hls.Manifest or manifest_type == libfmp4.hls.Playlist:
        if verbose_manifest:
            comment = _comment_header + _comment_pipeline_preamble + pipeline_config
            for tag in comment.splitlines():
                manifest.additionalTags.append(f"## {tag}")
    else:
        raise Exception(f"Cannot add comment to unknown manifest type {manifest_type}")


def setup(pipeline_filename):
    """
    This method prepares a processing pipeline for a specific manifest
    format ("mpd", "m3u8_main" or "m3u8_media are supported) by reading
    the pipeline configuration from a YAML configuration file.

    This method must be called at least once before invoking "update_manifest".

    Args:
        - pipeline_filename (str): the name of the .yaml Pipeline Configuration
          file.

    Returns:
        None.

    Raises:
        Exception: for unsupported extension.
        FileNotFoundError: for pipeline configuration file not found.
        yaml.YAMLError: for pipeline configuration file yaml syntax error.
        ImportError: if a plugin mentioned in the pipeline configuration file
            cannot be found/imported.
        SchemaError: for invalid plugin configuration.
    """

    Context.log_trace(f"Using libfmp4 = {libfmp4.version_string()}")

    # build_pipeline will raise in case the pipeline cannot be built.
    Context.pipeline = Pipeline.build_pipeline(pipeline_filename)

    Context.log_debug(
        f"Setup with pipeline filename {pipeline_filename} done."
    )


def update_manifest(context, manifest, storage={}, verbose_manifest=False):
    """
    This method processes the manifest through the active pipeline previously
    selected by the "setup" method.

    Args:
        - manifest (libfmp4.mpd.Manifest or libfmp4.hls.Manifest): The input
          manifest
        - storage: a dictionary transparently passed to the pipeline and that
          plugin will be able to access
        - verbose_manifest: a flag that controls whether comments will be added
          to manifests/playlists

    Returns:
        - the edited manifest.

    Raises:
        - NotImplementedError: if a "Plugin" class not respecting the required
          interface has been included in a pipeline.
        - Exception: in case of processing errors or unexpected manifest
          format.

    In details, this method performs:
        - dynamic identification of std:vector members (through the leading
          underscore convention used by libfmp4 python bindings and class
          reflection)
        - recursive copy of std:vector members to corresponding python list
          members
        - manifest process through pipeline
        - recursive copy-back of python list content to std:vector members
        - removal of the added python list from manifest

    """

    Context.log_trace("Pipeline processing...")
    # Actual pipeline processing
    Context.pipeline.run(input_manifest=manifest, storage=storage)

    # Finally, this will add a comment to the manifest with the specific
    #  yml description of the applied pipeline, mainly for troubleshooting
    #  or support purposes. Can be disabled to reduce verbosity.
    Context.log_trace(
        "Marking manifest as edited from manifest-edit. "
        f"Verbosity is {verbose_manifest}"
        )
    _addManifestEditComment(
        context,
        manifest,
        Context.pipeline.yaml_configuration,
        verbose_manifest=verbose_manifest
        )

    Context.log_trace("exiting update manifest")

    return manifest
