from yta_editor.transformations.effects.abstract import Effect
from yta_editor.utils.transition_calculator import frame_to_uint8_ndarray
from yta_editor_time.evaluation_context import EvaluationContext
# from av.video import VideoFrame
from typing import Union
from abc import abstractmethod

import numpy as np


class VideoEffect(Effect):
    """
    An effect for a video input.

    This class is just to be able to identify the
    hierarchy and which effect is designed for 
    the video.
    """

    @abstractmethod
    def _apply(
        self,
        # TODO: Set the type
        frame: any,
        output_size: Union[tuple[int, int], None],
        evaluation_context: EvaluationContext
    # TODO: Set the type
    ) -> any:
        """
        *For internal use only*

        The internal method to apply the effect on the `frame`
        provided.
        """
        pass

    def apply(
        self,
        # TODO: Set the type
        frame: any,
        output_size: Union[tuple[int, int], None],
        evaluation_context: EvaluationContext
    # TODO: Set the type
    ) -> any:
        """
        Apply the effect to the given `frame` at the `t` time
        moment provided.
        """
        frame_processed = self._apply(
            frame = frame,
            output_size = output_size,
            evaluation_context = evaluation_context
        )

        frame_processed = frame_to_uint8_ndarray(
            frame = frame_processed,
            do_include_alpha = True
        )

        # TODO: Why do we force it to be a numpy ndarray (?)
        # The effect can be done using GPU

        # if PythonValidator.is_instance_of(frame_processed, 'Texture'):
        #     from yta_editor_common.frame.helper import FrameHelper
        #     frame_processed = FrameHelper.texture_to_ndarray(frame_processed)[0]

        # frame_processed = TextureUtils.numpy_to_uint8(frame_processed)

        # The output is forced to be 'uint8'
        return frame_processed

class VideoEffects:
    """
    A set of effects to apply in a video input.
    """

    # TODO: This is not as simple as a list, it must be
    # managed with the specific nodes we designed that
    # are able to combine and are time-concerned.

    def __init__(
        self,
        effects: list[VideoEffect] = []
    ):
        self.effects: list[VideoEffect] = effects
        """
        The list of the effects to apply in the video.
        """

    def apply(
        self,
        frame: Union['np.ndarray', 'Texture'],
        output_size: Union[tuple[int, int], None],
        evaluation_context: EvaluationContext
    # TODO: Set the type
    ) -> Union['np.ndarray', 'Texture']:
        """
        Apply all the effects that should be applied at the
        given `t` time moment to the provided audio `frame`.
        """
        # width, height = frame.width, frame.height
        # Frame here: 'ndarray' (CPU) or 'Texture' (GPU)

        if len(self.effects) > 0:
            # TODO: Only if 'frame' is 'av.VideoFrame'
            # processed_frame = frame.to_ndarray(format = 'rgba')
            processed_frame = frame

            for effect in self.effects:
                # TODO: The effect has to be a real input (numpy or texture)
                processed_frame = effect.apply(
                    frame = processed_frame,
                    output_size = output_size,
                    evaluation_context = evaluation_context
                )

            # TODO: Only if texture:
            # data = processed_frame.read()
            # data = np.frombuffer(data, dtype = np.uint8)
            # # The shape has to be the expected by the timeline
            # # TODO: Do I need the 'output_size' here? It should be
            # # defined before the 'transform' processing...
            # # data = data.reshape(self.output_size[1], self.output_size[0], 4)
            # data = data.reshape(height, width, 4)
            # # OpenGL uses textures flipped, so...
            # data = np.flip(data, axis = 0)
            # # TODO: I'm removing the alpha channel here...
            # # processed_frame = data[:, :, :3]

            # If numpy
            # data = processed_frame.astype(np.uint8)
            
            # frame = VideoFrame.from_ndarray(
            #     # TODO: Force this with the 'yta-numpy' utils to 'np.uint8'
            #     array = data,
            #     #format = 'rgb24'
            #     format = 'rgba'
            # )

            return processed_frame

        return frame
