from yta_validation.parameter import ParameterValidator
# TODO: These imports below must be optional
from yta_editor_pools.render_target.provider import RenderTargetProvider
from yta_editor_pools.render_target.context_manager import RenderTargetContextManager
from yta_editor_pools.texture.provider import TextureProvider
from yta_editor_pools.texture.context_manager import TextureContextManager


class GPUResources:
    """
    The resources of the context related to GPU.
    
    Here you have a list with the funcionalities you
    can interact with:
    - `.opengl_context` to get the context.
    - `.texture` to access to the `moderngl.Texture`
    API.
    - `.render_target` to access to the `RenderTarget`
    API.
    """

    def __init__(
        self,
        opengl_context: 'moderngl.Context',
    ):
        ParameterValidator.validate_mandatory_instance_of('opengl_context', opengl_context, 'Context')

        self.opengl_context: 'moderngl.Context' = opengl_context
        """
        The moderngl context we need to operate with GPU.
        """

        """
        Exposed API below
        """
        self.render_target: '_RenderTargetAccessor' = _RenderTargetAccessor(self._render_target_provider)
        """
        The functionality related to the `RenderTarget`
        instances in one click, by using an instance of its
        provider class.
        """
        self.texture: '_TextureAccessor' = _TextureAccessor(self._texture_provider)
        """
        The functionality related to the `moderngl.Texture`
        pool handling in one click, by using an instance of
        the texture provider class.
        """
        

"""
API exposer wrappers below
"""
class _RenderTargetAccessor:
    """
    *For internal use only*

    Wrapper to simplify the access to the functionality
    that the `RenderTargetProvider` is providing to us
    and limitate the part of its API we expose.
    """

    def __init__(
        self,
        opengl_context: 'moderngl.Context'
        # render_target_provider: 'RenderTargetProvider'
    ):
        self._opengl_context: 'moderngl.Context' = opengl_context
        """
        *For internal use only*

        The context itself.
        """
        self._render_target_provider: 'RenderTargetProvider' = RenderTargetProvider(self._opengl_context)
        """
        *For internal use only*

        The render target provider, able to handle the
        pool and acquire and release `RenderTarget`
        instances on demand.
        """
        self._render_target_context_manager: 'RenderTargetContextManager' = RenderTargetContextManager(self._render_target_provider)
        """
        *For internal use only*

        The render target context manager, able to
        acquire `RenderTarget` resources and release them
        inmediately and automatically after use.
        """
    
    def acquire_with_context(
        self,
        width: int = 1920,
        height: int = 1080,
        number_of_components: int = 4,
        dtype: str = 'f1'
    ) -> 'RenderTarget':
        """
        Acquire a temporary RenderTarget instance from the pool
        that will be used and then inmediately released back to
        the pool.

        Use within a context:
        - `with instance.acquire(...) as render_target:`
        """
        return self._render_target_context_manager.acquire(
            width = width,
            height = height,
            number_of_components = number_of_components,
            dtype = dtype
        )
    
    def ping_pong_with_context(
        self,
        width: int = 1920,
        height: int = 1080,
        number_of_components: int = 4,
        dtype: str = 'f1'
    ) -> 'RenderTarget':
        """
        Acquire 2 RenderTarget instances from the pool, yielded as
        a tuple, that will be used and then inmediately released
        back to the pool.

        Use within a context:
        - `with instance.ping_pong(...) as (rt1, rt2):`
        """
        return self._render_target_context_manager.ping_pong(
            width = width,
            height = height,
            number_of_components = number_of_components,
            dtype = dtype
        )
    
    def acquire_n_with_context(
        self,
        number_of_render_targets: int,
        width: int = 1920,
        height: int = 1080,
        number_of_components: int = 4,
        dtype: str = 'f1'
    ) -> 'RenderTarget':
        """
        Acquire number_of_render_targets RenderTarget instances from
        the pool, yielded as an array, that will be used and then
        inmediately released back to the pool.

        Use within a context:
        - `with instance.acquire_n(...) as render_targets:`
        """
        return self._render_target_context_manager.acquire_n(
            number_of_render_targets = number_of_render_targets,
            width = width,
            height = height,
            number_of_components = number_of_components,
            dtype = dtype
        )
    
    def acquire(
        self,
        width: int = 1920,
        height: int = 1080,
        number_of_components: int = 4,
        dtype: str = 'f1'
    ) -> 'RenderTarget':
        """
        Get a `RenderTarget` instance with the given `width`,
        `height`, `number_of_components` and `dtype` from the
        pool, that will mark it as in use.
        """
        return self._render_target_provider.acquire(
            width = width,
            height = height,
            number_of_components = number_of_components,
            dtype = dtype
        )
    
    def acquire_like(
        self,
        render_target: 'RenderTarget'
    ) -> 'RenderTarget':
        """
        Get the `RenderTarget` instance with the same
        properties than the `render_target` provided as
        parameter.
        """
        return self._render_target_provider.acquire_like(
            render_target = render_target
        )

    def release(
        self,
        render_target: 'RenderTarget'
    ) -> None:
        """
        Release the `render_target` provided, making it
        available again through the pool and marking it
        as free.
        """
        self._render_target_provider.release(render_target)

class _TextureAccessor:
    """
    *For internal use only*

    Wrapper to simplify the access to the functionality
    that the `TextureProvider` is providing to us and
    limitate the part of its API we expose.
    """

    def __init__(
        self,
        opengl_context: 'moderngl.Context'
    ):
        self._opengl_context: 'moderngl.Context' = opengl_context
        """
        *For internal use only*

        The context itself.
        """
        self._texture_provider: 'TextureProvider' = TextureProvider(self._opengl_context)
        """
        *For internal use only*

        The texture provider, able to handle the pool and
        acquire and release `moderngl.Texture` instances
        on demand.
        """
        self._texture_context_manager: 'TextureContextManager' = TextureContextManager(self._texture_provider)
        """
        *For internal use only*

        The texture context manager, able to acquire
        `moderngl.Texture` resources and release them
        inmediately and automatically after use.
        """
    
    def acquire_with_context(
        self,
        width: int = 1920,
        height: int = 1080,
        number_of_components: int = 4,
        dtype: str = 'f1'
    ) -> 'moderngl.Texture':
        """
        Acquire a temporary `moderngl.Texture` instance from the pool
        that will be used and then inmediately released back to the
        pool.

        Use within a context:

        - `with instance.acquire(...) as texture:`
        """
        return self._texture_context_manager.acquire(
            width = width,
            height = height,
            number_of_components = number_of_components,
            dtype = dtype
        )
    
    def ping_pong_with_context(
        self,
        width: int = 1920,
        height: int = 1080,
        number_of_components: int = 4,
        dtype: str = 'f1'
    ) -> 'moderngl.Texture':
        """
        Acquire 2 `moderngl.Texture` instances from the pool, yielded as
        a tuple, that will be used and then inmediately released back
        to the pool.

        Use within a context:

        - `with instance.ping_pong(...) as (tex1, tex2):`
        """
        return self._texture_context_manager.ping_pong(
            width = width,
            height = height,
            number_of_components = number_of_components,
            dtype = dtype
        )
    
    def acquire_n(
        self,
        number_of_textures: int,
        width: int = 1920,
        height: int = 1080,
        number_of_components: int = 4,
        dtype: str = 'f1'
    ) -> 'moderngl.Texture':
        """
        Acquire `number_of_textures` `moderngl.Texture` instances from
        the pool, yielded as an array, that will be used and then
        inmediately released back to the pool.

        Use within a context:

        - `with instance.acquire_n(...) as textures:`
        """
        return self._texture_context_manager.acquire_n(
            number_of_textures = number_of_textures,
            width = width,
            height = height,
            number_of_components = number_of_components,
            dtype = dtype
        )
    
    def acquire(
        self,
        width: int = 1920,
        height: int = 1080,
        number_of_components: int = 4,
        dtype: str = 'f1'
    ) -> 'moderngl.Texture':
        """
        Get a `moderngl.Texture` instance with the given `width`,
        `height`, `number_of_components` and `dtype` from the
        pool.
        """
        return self._texture_provider.acquire(
            width = width,
            height = height,
            number_of_components = number_of_components,
            dtype = dtype
        )
    
    def acquire_like(
        self,
        texture: 'moderngl.Texture'
    ) -> 'moderngl.Texture':
        """
        Get the `moderngl.Texture` instance with the same
        properties than the `texture` provided as
        parameter.
        """
        return self._texture_provider.acquire_like(
            texture = texture
        )

    def release(
        self,
        texture: 'moderngl.Texture'
    ) -> None:
        """
        Release the `texture` provided, making it
        available again through the pool.
        """
        self._texture_provider.release(texture)