"""
Module to transform one resource into another
one to be able to do CPU -> GPU, GPU -> CPU,
etc.
"""
from yta_editor_frame.frame.resource.identifier import FrameResourceIdentifier
from yta_editor_frame.frame.resource.cpu_frame_resource import CPUNumpyArrayFrameResource
from yta_editor_frame.frame.resource.gpu_frame_resource import GPUTextureFrameResource
from yta_editor_frame.frame.resource.av_video_frame_resource import AvVideoFrameResource
from yta_programming.decorators.requires_dependency import requires_dependency
from typing import Union


FORMAT = 'rgba'

class FrameResourceConverter:
    """
    Class to simplify the way we transform one
    frame resource into another one.
    """

    @requires_dependency('numpy', 'yta_editor_frame', 'numpy')
    def to_numpy(
        resource: Union[CPUNumpyArrayFrameResource, GPUTextureFrameResource]
    ) -> CPUNumpyArrayFrameResource:
        """
        Transform the `resource` provided into a GPU
        `CPUNumpyArrayFrameResource` that holds a
        `np.ndarray`.
        """
        if FrameResourceIdentifier.is_cpu(resource):
            # [1] `np.ndarray` -> `np.ndarray`
            return resource
        
        frame = None
        if FrameResourceIdentifier.is_cpu_av_videoframe(resource):
            # [2] `av.VideoFrame` -> `np.ndarray`
            import numpy as np

            # TODO: av to numpy
            numpy_array = resource.frame.to_ndarray(format = FORMAT)

            # Flipping it would be unexpected, so we don't
            # frame = (
            #     np.ascontiguousarray(frame)
            #     if do_force_contiguous else
            #     frame
            # )

            # self._numpy_ndarray = frame
            # self._autodetect_transparency()
            frame = numpy_array
        else:
            # [3] `moderngl.Texture` -> `np.ndarray`
            # [4] `render_target` -> `np.ndarray`
            from yta_editor_frame.utils import _texture_to_numpy_frame, _ensure_rgba_numpy_array

            # [3] `moderngl.Texture` -> `np.ndarray`
            texture = resource.frame
            if FrameResourceIdentifier.is_gpu_render_target(resource):
                # [4] `render_target` -> `np.ndarray`
                # TODO: What do we do with the render target? Should
                # this conversion happen? I think it shouldn't...
                texture = texture.texture

            # `moderngl.Texture` -> `np.ndarray`
            numpy_frame = _texture_to_numpy_frame(
                texture = texture,
                size = (resource.width, resource.height),
                # TODO: What do we do with the flip (?)
                do_flip_texture = True,
                # do_flip_texture = do_flip_texture,
                do_force_contiguous = False
            )

            # 2. Force 'rgba' format
            frame = _ensure_rgba_numpy_array(
                ndarray = numpy_frame,
                # TODO: What do we do with the flip (?)
                # do_force_contiguous = do_force_contiguous
                do_force_contiguous = False
            )

            # self._numpy_ndarray = frame
            # self._autodetect_transparency()
            # TODO: Do we need to release the `render_target`
            # or `moderngl.Texture` to the pool (?)

        return CPUNumpyArrayFrameResource(frame)
        
    @requires_dependency('numpy', 'yta_editor_frame', 'numpy')
    def to_texture(
        resource: 'FrameResource',
        moderngl_context: 'moderngl.Context'
    ) -> GPUTextureFrameResource:
        """
        Transform the `resource` provided into a GPU
        `GPUTextureFrameResource` that holds a
        `moderngl.Texture`.
        """
        if FrameResourceIdentifier.is_gpu(resource):
            # [1] `moderngl.Texture` -> `moderngl.Texture`
            return resource

        frame = None
        if FrameResourceIdentifier.is_gpu_render_target(resource):
            # [2] `render_target` -> `moderngl.Texture`
            # TODO: What do we do with the render target? Should
            # this conversion happen? I think it shouldn't...
            frame = resource.frame.texture
        elif (
            FrameResourceIdentifier.is_cpu(resource) or
            FrameResourceIdentifier.is_cpu_av_videoframe(resource)
        ):
            from yta_editor_frame.utils import _ensure_rgba_numpy_array, _numpy_frame_to_texture
            # [3] `np.ndarray` -> `moderngl.Texture`
            # [4] `av.VideoFrame` -> `moderngl.Texture`

            numpy_array = resource.frame
            if FrameResourceIdentifier.is_cpu_av_videoframe(resource):
                # [4] `av.VideoFrame` -> `moderngl.Texture`
                numpy_array = numpy_array.to_ndarray(format = FORMAT)

            # [3] `np.ndarray` -> `moderngl.Texture`
            # 1. Force 'rgba' format
            numpy_array = _ensure_rgba_numpy_array(
                ndarray = numpy_array,
                do_force_contiguous = False
            )

            # 2. `np.ndarray` -> `moderngl.Texture`
            frame = _numpy_frame_to_texture(
                numpy_frame = numpy_array,
                moderngl_context = moderngl_context,
                size = (resource.width, resource.height),
                # TODO: What do we do with the flip (?)
                do_flip_texture = True
                # do_flip_texture = do_flip_texture
            )

            # We preserve the information about the alpha
            # channel that we had when numpy array

        return GPUTextureFrameResource(frame)
    
    @requires_dependency('av', 'yta_editor_frame', 'av')
    def to_videoframe(
        resource: 'FrameResource'
    ) -> AvVideoFrameResource:
        """
        Transform the `resource` provided into a GPU
        `AvVideoFrameResource` that holds a
        `av.VideoFrame`.
        """
        import av
        
        if FrameResourceIdentifier.is_cpu_av_videoframe(resource):
            # [1] `av.VideoFrame` -> `av.VideoFrame`
            return resource
        
        frame = None
        if FrameResourceIdentifier.is_cpu(resource):
            # [2] `np.ndarray` -> `av.VideoFrame`
            from yta_editor_frame.utils import _ensure_rgba_numpy_array
            # 1. Force 'rgba' format
            numpy_array = _ensure_rgba_numpy_array(
                ndarray = resource.frame,
                do_force_contiguous = True
            )

            # 2. `np.ndarray` -> `av.VideoFrame`
            frame = av.VideoFrame.from_ndarray(numpy_array, format = FORMAT)

            # We preserve the information about the alpha
            # channel that we had when numpy array
        else:
            # [3] `moderngl.Texture` -> `av.VideoFrame`
            # [4] `render_target` -> `av.VideoFrame`
            from yta_editor_frame.utils import _texture_to_numpy_frame

            frame = resource.frame
            if FrameResourceIdentifier.is_gpu_render_target(resource):
                # [4] `render_target` -> `av.VideoFrame`
                frame = frame.texture

            # [3] `moderngl.Texture` -> `av.VideoFrame`
            # 1. `moderngl.Texture` -> `np.ndarray`
            numpy_array = _texture_to_numpy_frame(
                texture = frame,
                size = (resource.width, resource.height),
                # TODO: What do we do with the flip (?)
                do_flip_texture = True,
                # do_flip_texture = do_flip_texture,
                do_force_contiguous = True
            )

            # As we obtained it as numpy array, preserve it and
            # the information about the alpha channel from it
            # self._numpy_ndarray = frame
            # self._autodetect_transparency()

            # 2. `np.ndarray` -> `av.VideoFrame`
            frame = av.VideoFrame.from_ndarray(numpy_array, format = FORMAT)
            # TODO: Do we need to release the `render_target`
            # or `moderngl.Texture` to the pool (?)
        
        return AvVideoFrameResource(frame)
