"""
This module is recent, used with `FrameBuffer`
and working properly, a bit more strict than
others you can find in the project and maybe
should be removed.
"""
from yta_programming.decorators.requires_dependency import requires_dependency
from yta_editor_pools.texture.provider import TextureProvider


@requires_dependency('numpy', 'yta_editor_frame', 'numpy')
def _texture_to_numpy_frame(
    texture: 'moderngl.Texture',
    size: tuple[int, int],
    do_flip_texture: bool = True,
    do_force_contiguous: bool = False
):
    """
    *For internal use only*

    Create a `np.ndarray` from the `texture` provided
    and return it.

    This method is only meant to be used within
    the `FrameBuffer` class.

    This frame is not forced to be contiguous.
    """
    import numpy as np

    data = texture.read()
    """
    We are always using 'np.uint8' because we are also
    forcing 'f1' textures dtype.
    """
    frame = np.frombuffer(data, dtype = np.uint8)
    # The format is (height, width, number_of_components)
    frame = frame.reshape(size[1], size[0], texture.components)

    # We need to flip it depending on our moderngl shaders
    frame = (
        np.flipud(frame)
        if do_flip_texture else
        frame
    )

    # We force it as contiguous
    frame = (
        np.ascontiguousarray(frame)
        if do_force_contiguous else
        frame
    )

    # # We always force 'RGBA'
    # frame = _ensure_rgba_numpy_array(frame)

    return frame

@requires_dependency('numpy', 'yta_editor_frame', 'numpy')
def _ensure_rgba_numpy_array(
    ndarray: 'np.ndarray',
    do_force_contiguous: bool = False
) -> 'np.ndarray':
    """
    *For internal use only*

    Transform the `ndarray` provided to make sure
    that it is representing a 'rgba' frame,
    compatible with textures like the following one:
    - `dtype = uint8`
    - `shape = (H, W, 4)`
    - `layout = RGBA`

    This method is only meant to be used within
    the `FrameBuffer` class.

    This array is forced to be contiguous if the
    `do_force_contiguous` parameter is `True`, that
    must be set as `True` when we are about to use
    that frame to build a texture o to write with
    the pyav library.
    """
    import numpy as np

    if ndarray.ndim == 2:
        ndarray = np.repeat(ndarray[:, :, None], 3, axis = 2)

    if ndarray.shape[2] == 3:
        alpha = np.full((ndarray.shape[0], ndarray.shape[1], 1), 255, dtype = ndarray.dtype)
        ndarray = np.concatenate((ndarray, alpha), axis = 2)

    if ndarray.shape[2] != 4:
        raise Exception('The format of this numpy array is not accepted.')
    
    ndarray = (
        np.ascontiguousarray(ndarray)
        if do_force_contiguous else
        ndarray
    )

    return ndarray

@requires_dependency('numpy', 'yta_editor_frame', 'numpy')
@requires_dependency('moderngl', 'yta_editor_frame', 'moderngl')
def _numpy_frame_to_texture(
    numpy_frame: 'np.ndarray',
    moderngl_context: 'moderngl.Context',
    size: tuple[int, int],
    do_flip_texture: bool = True
) -> 'moderngl.Texture':
    """
    *For internal use only*

    Create a `moderngl.Texture` from the given
    `numpy_frame` and return it.

    This method is only meant to be used within
    the `FrameBuffer` class.

    Please, remember to release the texture when
    finished as it is being extracted from the
    pool.
    """
    import moderngl
    import numpy as np

    numpy_frame = (
        np.flipud(numpy_frame)
        if do_flip_texture else
        numpy_frame
    )

    number_of_components = (
        numpy_frame.shape[2]
        if numpy_frame.ndim == 3 else
        1
    )

    # We force contiguous for moderngl
    numpy_frame = np.ascontiguousarray(numpy_frame)

    """
    We are always using 'np.uint8' because we are also
    forcing 'f1' textures dtype.
    """
    # TODO: Sometimes we can receive a 'float32', why (?)
    # TODO: Transform dtype to texture dtype with utils
    dtype = (
        'f1'
        if numpy_frame.dtype.name == 'uint8' else
        'f4'
        if numpy_frame.dtype.name == 'float32' else
        # This is not ok, but by now...
        'f1'
    )

    # We need to release it this one when finished
    texture = TextureProvider(moderngl_context).acquire(
        width = size[0],
        height = size[1],
        number_of_components = number_of_components,
        dtype = dtype
    )
    texture.write(numpy_frame)

    # texture = moderngl_context.texture(
    #     size = size,
    #     components = number_of_components,
    #     data = numpy_frame.tobytes(),
    #     dtype = dtype
    # )

    # Default filter
    texture.filter = (moderngl.NEAREST, moderngl.NEAREST)

    return texture