# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.global_settings import SequenceTypes
from .utils.doc import doc_name, doc_idname, doc_brief, doc_description


class POWER_SEQUENCER_OT_select_related_strips(bpy.types.Operator):
    """
    *brief* Find and select all strips related to the selection


    Find and select effects related to the selection, but also inputs of selected effects.
    This helps to then copy or duplicate strips with all attached effects.
    """

    doc = {
        "name": doc_name(__qualname__),
        "demo": "",
        "description": doc_description(__doc__),
        "shortcuts": [],
        "keymap": "Sequencer",
    }
    bl_idname = doc_idname(__qualname__)
    bl_label = doc["name"]
    bl_description = doc_brief(doc["description"])
    bl_options = {"REGISTER", "UNDO"}

    find_all: bpy.props.BoolProperty(
        name="Find All",
        description=(
            "Find all related strips recursively so that you can copy the selection"
            " without getting an error from Blender"
        ),
        default=True,
    )

    @classmethod
    def poll(cls, context):
        return context.selected_sequences

    def execute(self, context):
        if self.find_all:
            related_strips = set()
            for s in context.selected_sequences:
                self.find_neighbours_recursive(related_strips, s, context)
        else:
            related_strips = []
            # Only select direct neighbours and attached effects
            effects = [s for s in context.sequences if s.type in SequenceTypes.EFFECT]
            found_effects = self.find_related_effects(context.selected_sequences, effects)
            related_strips.extend(found_effects)
            while len(found_effects) > 0:
                found_effects = self.find_related_effects(found_effects, effects)
                related_strips.extend(found_effects)
        for s in related_strips:
            s.select = True
        return {"FINISHED"}

    def find_neighbours_recursive(self, visited, strip, context):
        """
        Performs a depth first search traversal to the graph of strips, to find
        all related strips.
        Args:
        - visited: A set with all the strips that have been visited.
        - strip: The strip to start the search from.
        """
        visited.add(strip)
        neighbours = self.find_neighbours(strip, context)
        for s in neighbours:
            if s not in visited:
                self.find_neighbours_recursive(visited, s, context)

    def find_neighbours(self, strip, context):
        """
        Strips and their effect strips define a graph, where each node is a
        strip and edges are their connections. It finds all the neighbours of a
        strip in the graph, and *sometimes neighbours of neighbours and so on*.
        *In order to find the neighbours of a strip the
        bpy.ops.transform.seq_slide operator is used, and usually finds many
        levels of neighbours, but always finds the first level, which is needed,
        the other levels are redundant, but are included for brevity reasons.
        Args:
        - strip: The strip to find all its neighbours.
        Returns: A list with all the neighbours of the strip and sometimes
                 neighbours of neighbours and so on.
        """
        # Respects initial selection
        init_selected_strips = [s for s in context.selected_sequences]

        neighbours = []
        bpy.ops.sequencer.select_all(action="DESELECT")
        strip.select = True
        bpy.ops.transform.seq_slide(value=(0, 0))
        strip.select = False
        for s in context.selected_sequences:
            neighbours.append(s)

        try:
            neighbours.append(strip.input_1)
            neighbours.append(strip.input_2)
        except Exception:
            pass

        bpy.ops.sequencer.select_all(action="DESELECT")
        for s in init_selected_strips:
            s.select = True

        return neighbours

    def find_related_effects(self, sequences, effects):
        found = []
        for s in sequences:
            for e in effects:
                try:
                    if e.input_1 == s:
                        found.append(e)
                except Exception:
                    continue
                try:
                    if e.input_2 == s:
                        found.append(e)
                except Exception:
                    continue
        return found
