"""
I don't think we need this...
"""
from yta_validation import PythonValidator
from dataclasses import dataclass
from typing import Union

import numpy as np


@dataclass(frozen = True)
class FrameFormatDescriptor:
    """
    Dataclass to identify and simplify the way we
    handle the different formats.
    """

    name: str
    """
    The name of the format.
    """
    number_of_channels: int
    """
    The amount of channels included.
    """
    numpy_dtype: np.dtype
    """
    The raw numpy 'dtype' format.
    """
    av_format: str
    """
    The name of the format for the PyAV library.
    """
    gl_components: Union[int, None]
    """
    The number of components when as a moderngl
    texture, that can be `None` if it needs to be
    converted.
    """


FRAME_FORMATS = {
    'rgba8': FrameFormatDescriptor(
        name = 'rgba8',
        number_of_channels = 4,
        numpy_dtype = np.uint8,
        av_format = 'rgba',
        gl_components = 4,
    ),
    'rgb8': FrameFormatDescriptor(
        name = 'rgb8',
        number_of_channels = 3,
        numpy_dtype = np.uint8,
        av_format = 'rgb24',
        gl_components = 3,
    ),
    'gray8': FrameFormatDescriptor(
        name = 'gray8',
        number_of_channels = 1,
        numpy_dtype = np.uint8,
        av_format = 'gray',
        gl_components = 1,
    ),
    'yuv420p': FrameFormatDescriptor(
        name = 'yuv420p',
        number_of_channels = 3,
        numpy_dtype = np.uint8,
        av_format = 'yuv420p',
        gl_components = None,  # Need to transform to upload
    ),
}

class FrameFormatDetector:
    """
    Class to simplify the way we detect the format
    of a video frame.
    """

    # TODO: Set the 'av' library requirement
    @staticmethod
    def _from_pyav_frame(
        frame: 'av.VideoFrame'
    ) -> FrameFormatDescriptor:
        """
        *For internal use only*

        Detect the format descriptor from the pyav
        video frame `frame` provided.

        This will detect it by checking the format name
        of the `frame` provided.
        """
        import av

        # TODO: Validate the 'frame' is a 'av.VideoFrame' (?)
        format = frame.format.name

        for frame_format_descriptor in FRAME_FORMATS.values():
            if frame_format_descriptor.av_format == format:
                return frame_format_descriptor

        raise Exception(f'The "{format}" is not supported.')
    
    # TODO: Set the 'numpy' library requirement
    @staticmethod
    def _from_numpy_frame(
        frame: 'np.ndarray'
    ) -> FrameFormatDescriptor:
        """
        *For internal use only*

        Detect the format descriptor from the numpy
        video frame `frame` provided.

        This will detect it by checking the number of
        `channels` and the `dtype` in the `frame`
        provided.
        """
        import numpy as np

        # TODO: Validate the 'frame' is a 'np.ndarray' frame (?)
        number_of_channels = (
            1
            if frame.ndim == 2 else
            frame.shape[2]
        )
        dtype = np.dtype(frame.dtype)

        for frame_format_descriptor in FRAME_FORMATS.values():
            if (
                frame_format_descriptor.number_of_channels == number_of_channels and
                frame_format_descriptor.numpy_dtype == dtype
            ):
                return frame_format_descriptor
            
        raise Exception(f'The numpy ndarray with {str(number_of_channels)} channels and dtype={str(dtype)} is not supported.')
    
    # TODO: Set the 'moderngl' library requirement
    @staticmethod
    def _from_moderngl_frame(
        frame: 'moderngl.Texture'
    ) -> FrameFormatDescriptor:
        """
        *For internal use only*

        Detect the format descriptor from the moderngl
        video frame `frame` provided.

        This will detect it by checking the number of
        `components` existing in the `frame` provided.
        """
        import moderngl

        number_of_components = frame.components

        for desc in FRAME_FORMATS.values():
            if desc.gl_components == number_of_components:
                return desc

        raise Exception(f'The moderngl texture with {str(number_of_components)} components is not supported')
    
    # TODO: Set the 'av' library requirement
    # TODO: Set the 'numpy' library requirement
    # TODO: Set the 'moderngl' library requirement
    @staticmethod
    def from_frame(
        frame: Union['np.ndarray', 'av.VideoFrame', 'moderngl.Texture']
    ) -> Union[FrameFormatDescriptor, None]:
        """
        Detect the format descriptor from the video
        frame `frame` provided, that can be one of
        these:
        - `np.ndarray`
        - `av.VideoFrame`
        - `moderngl.Texture`
        """
        return (
            FrameFormatDetector._from_pyav_frame(frame)
            if PythonValidator.is_instance_of(frame, 'VideoFrame') else
            FrameFormatDetector._from_numpy_frame(frame)
            if PythonValidator.is_numpy_array(frame) else
            FrameFormatDetector._from_moderngl_frame(frame)
            if PythonValidator.is_instance_of(frame, 'Texture') else
            None
        )