from yta_editor.utils import VideoUtils
from yta_validation.parameter import ParameterValidator
from av.video.frame import VideoFrame
from av.audio.frame import AudioFrame
from typing import Union

import numpy as np


IS_FROM_GAP_METADATA = 'is_from_gap'
"""
Metadata key to indicate if the frame
has been generated by a gap and should
be ignored when trying to combine with
others.
"""
IS_SILENT_METADATA = 'is_silent'
"""
Metadata key to indicate if the frame is a
silent frame or not, being able to handle it
properly when we need to combine it.
"""

class _FrameWrappedBase:
    """
    Class to wrap video and audio frames from
    the pyav library but to support a metadata
    field to inject some information we need
    when processing and combining them.
    """

    @property
    def frame(
        self
    ) -> VideoFrame:
        """
        Get the original pyav VideoFrame.
        """
        return self._frame

    @property
    def is_from_gap(
        self
    ) -> bool:
        """
        Flag to indicate if the frame comes from
        an empty part or not, that will be done
        by checking the 'is_from_gap' 
        attribute in the metadata.

        This will return True only if the value
        is set and it is set as 'True'.
        """
        return self._is_metadata_boolean_set(IS_FROM_GAP_METADATA)
    
    @property
    def is_silent(
        self
    ) -> bool:
        """
        Flag to indicate if the frame that comes is
        actually an empty frame or not, which could
        be useful when we need to combine it with 
        others, and let us avoid reading the content
        to detect if it is silent or not.

        This metadata is valid for audio frames.

        This will return `True` only if the value is
        set and its value is `'True'`.
        """
        return self._is_metadata_boolean_set(IS_SILENT_METADATA)

    def __init__(
        self,
        frame: Union[VideoFrame, AudioFrame],
        metadata: dict = {}
    ):
        ParameterValidator.validate_mandatory_instance_of('frame', frame, [VideoFrame, AudioFrame])
        ParameterValidator.validate_mandatory_dict('metadata', metadata)

        self._frame: Union[VideoFrame, AudioFrame] = frame
        """
        The VideoFrame or AudioFrame pyav instance.
        """
        self.metadata: dict = metadata or {}
        """
        The metadata we want to include with the
        frame.
        """

    def __getattr__(
        self,
        name
    ):
        return getattr(self._frame, name)

    def __setattr__(
        self,
        name,
        value
    ):
        super().__setattr__(name, value)
        #setattr(self._frame, name, value)
        # if name in ('_frame', 'metadata'):
        #     super().__setattr__(name, value)
        # else:
        #     setattr(self._frame, name, value)

    def __repr__(
        self
    ):
        cname = self.__class__.__name__

        return f'<{cname} metadata={self.metadata} frame={self._frame!r}>'
    
    def set_is_from_gap(
        self,
        value: bool = True
    ) -> None:
        """
        Add the metadata information to indicate
        that this is a frame that comes from an 
        empty part.
        """
        return self._set_metadata_boolean(
            attribute_name = IS_FROM_GAP_METADATA,
            value = value
        )

    def set_is_silent(
        self,
        value: bool = True
    ) -> None:
        """
        Add the metadata information to indicate that this
        is a silent frame, including the `is_silent` 
        metadata.
        """
        return self._set_metadata_boolean(
            attribute_name = IS_SILENT_METADATA,
            value = value
        )

    def _is_metadata_boolean_set(
        self,
        attribute_name: str
    ) -> bool:
        """
        *For internal use only*

        Method to simplify the way we check if the boolean
        metadata `attribute_name` is set or not. A metadata
        boolean parameter must be stored as string `'True`'
        to be considered valid.
        """
        is_parameter = self.metadata.get(attribute_name, None)

        return (
            is_parameter == 'True'
            if is_parameter is not None else
            is_parameter
        )
    
    def _set_metadata_boolean(
        self,
        attribute_name: str,
        value: bool = True
    ) -> None:
        """
        Set the `attribute_name` metadata boolean parameter
        `value` provided (that will be stored as a str).
        """
        self.metadata[attribute_name] = (
            'True'
            if value else
            'False'
        )
    
class VideoFrameWrapped(_FrameWrappedBase):
    """
    Class to wrap video frames from the pyav
    library but to support a metadata field
    to inject some information we need when
    processing and combining them.
    """

    @property
    def has_alpha_layer(
        self
    ) -> bool:
        """
        Flag to indicate if the frame has an alpha
        layer or not, that is defined by an 'a' in
        its format name.
        """
        return VideoUtils.video.alpha.videoframe_has_alpha_layer(self._frame)
    
    @property
    def has_alpha_pixels(
        self
    ) -> bool:
        """
        Flag to indicate if the frame comes with
        at least one pixel that has some kind of
        transparency and has to be considered for
        the alpha blending process because of that.
        """
        return (
            self.has_alpha_layer and
            VideoUtils.video.alpha.numpy_videoframe_has_transparent_pixels(self.as_rgba_numpy)
        )
    
    @property
    def size(
        self
    ) -> tuple[int, int]:
        """
        The size of the frame expressed as (width,
        height).
        """
        return (self.frame.width, self.frame.height)

    @property
    def as_rgb_numpy(
        self
    ) -> 'np.ndarray':
        """
        Get the video frame but as a numpy array with
        the 'rgb24' format, that doesn't include the
        alpha channel.

        The 'rgb24' format is perfect for video
        edition including not the alpha channel.
        """
        return self.frame.to_ndarray(format = 'rgb24')
    
    @property
    def as_rgba_numpy(
        self
    ) -> 'np.ndarray':
        """
        Get the video frame but as a numpy array with
        the 'rgba' format, that includes the alpha
        channel.

        The 'rgba' format is perfect for video edition
        including the alpha channel.
        """
        return self.frame.to_ndarray(format = 'rgba')

    def __init__(
        self,
        frame: VideoFrame,
        metadata: dict = {},
        is_from_gap: Union[bool, None] = None,
    ):
        ParameterValidator.validate_mandatory_instance_of('frame', frame, VideoFrame)

        super().__init__(
            frame = frame,
            metadata = metadata
        )

        if is_from_gap is not None:
            self.set_is_from_gap(is_from_gap)
        
# TODO: Maybe move? But careful with
# cyclic import issues
class AudioFrameWrapped(_FrameWrappedBase):
    """
    Class to wrap audio frames from the pyav
    library but to support a metadata field
    to inject some information we need when
    processing and combining them.
    """

    @property
    def copy(
        self
    ) -> 'AudioFrameWrapped':
        """
        A copy of this same instance.
        """
        return AudioFrameWrapped(
            # TODO: Do I need to hard-copy this '_frame' (?)
            frame = self._frame,
            metadata = self.metadata
        )

    def __init__(
        self,
        frame: AudioFrame,
        metadata: dict = {},
        is_from_gap: bool = False,
        is_silent: bool = False
    ):
        ParameterValidator.validate_mandatory_instance_of('frame', frame, AudioFrame)

        super().__init__(
            frame = frame,
            metadata = metadata
        )

        if is_from_gap:
            self.set_is_from_gap()

        if is_silent:
            self.set_is_silent()