"""
Code commented because it is not a good
implementation as we are very limited in
the way we combine the outputs when
finished. I keep it like this until we
find a better way to handle it.
"""
# from yta_editor_nodes.timeline import TimelineNode
# from yta_editor_nodes.timeline.serial import SerialTimelineNode
# from yta_editor_nodes.utils import validate_render_target
# from yta_editor_time.evaluation_context import EvaluationContext
# from abc import ABC, abstractmethod
# from typing import Union


# class _ParallelTimelineNodeAbstract(TimelineNode, ABC):
#     """
#     The abstract class of the parallel node.

#     This class is limited and needs at least 2 nodes
#     to be valid.
#     """

#     @property
#     def is_gpu_available(
#         self
#     ) -> bool:
#         """
#         Boolean flag to indicate if at least one of the
#         `nodes` of this instance is a GPU node (have a
#         GPU node processor associated).
#         """
#         if not hasattr(self, '_is_cpu_available'):
#             self._is_gpu_available = False

#             for node in self.nodes:
#                 if node.is_gpu_available:
#                     self._is_gpu_available = True
#                     break
            
#         return self._is_gpu_available
    
#     @property
#     def is_cpu_available(
#         self
#     ) -> bool:
#         """
#         Boolean flag to indicate if at least one of the
#         `nodes` of this instance is a CPU node (have a
#         CPU node processor associated).
#         """
#         if not hasattr(self, '_is_cpu_available'):
#             self._is_cpu_available = False

#             for node in self.nodes:
#                 if node.is_cpu_available:
#                     self._is_cpu_available = True
#                     break
            
#         return self._is_cpu_available

#     def __init__(
#         self,
#         # TODO: Put the correct class
#         # TODO: Maybe I need the 'node' in the TimelineNode class
#         # TODO: Maybe I need a shortcut to 'is_gpu_available'...
#         name: str,
#         nodes: list[SerialTimelineNode],
#         opengl_context: 'moderngl.Context'
#     ):
#         if len(nodes) < 2:
#             raise Exception('A ParallelTimelineNode needs 2 or more nodes to be created.')
        
#         self.nodes: list[SerialTimelineNode] = nodes
#         """
#         The list of nodes that will be executed in parallel
#         to obtain the outputs that will be combined.
#         """
#         self._opengl_context: 'moderngl.Context' = opengl_context
#         """
#         The context to be able to use GPU.
#         """

#         super().__init__(
#             name = name
#         )

#     def _resolve_specification(
#         self,
#         evaluation_context: EvaluationContext
#     ) -> 'TimelineNode':
#         """
#         *For internal use only*

#         Resolve the specification of this node by using the
#         `evaluation_context` provided, to be able to know
#         when the node is active and when we should process
#         the input.

#         This method must be called each time we modify
#         something that affects to the `duration` or other
#         field that makes the specifications resolved be
#         different.
#         """
#         for node in self.nodes:
#             node._resolve_specification(evaluation_context)

#         return self

#     def is_active_at(
#         self,
#         evaluation_context: EvaluationContext
#     ) -> bool:
#         """
#         Flag to indicate if the `evaluation_context`
#         provided is in the range of this TimedNode instance, 
#         which means that it is in between the limits of this
#         `TimelineNode` instance.

#         The formula:
#         - `start_frame <= evaluation_context.frame_index < end_frame`
#         """
#         if not self.is_enabled:
#             return False
        
#         for node in self.nodes:
#             if node.is_active_at(evaluation_context):
#                 return True
            
#         return False

#     def process(
#         self,
#         render_target: Union['RenderTarget', None],
#         inputs: dict[str, Union['np.ndarray', 'moderngl.Texture']],
#         evaluation_context: EvaluationContext,
#         do_use_gpu: bool = True,
#         **kwargs
#     ) -> Union['np.ndarray', 'moderngl.Texture']:
#         """
#         Process the `inputs` provided for the
#         `evaluation_context` given, and combine the
#         outputs of the different internal nodes and
#         return them as output.
#         """
#         validate_render_target(
#             render_target = render_target,
#             do_use_gpu = do_use_gpu
#         )

#         active_nodes = [
#             node for node in self.nodes
#             if node.is_active_at(evaluation_context)
#         ]

#         # This shouldn't happen as we know that the parallel
#         # node itself is not active, but just in case...
#         if not active_nodes:
#             # TODO: What do we do with the different inputs? We
#             # should return only one from the dict...
#             return inputs

#         if len(active_nodes) == 1:
#             return active_nodes[0].process(
#                 render_target = render_target,
#                 inputs = inputs,
#                 evaluation_context = evaluation_context,
#                 do_use_gpu = do_use_gpu,
#                 **kwargs
#             )
        
#         # For more than one node we need more RenderTarget instances
#         from yta_video_opengl.render_target.provider import RenderTargetProvider
#         # TODO: We need render_targets from the pool enough
#         # to be able to operate all the nodes
#         render_targets = [
#             RenderTargetProvider.get(self._opengl_context).acquire_like(render_target)
#             for _ in range(len(active_nodes))
#         ]
#         outputs = []
#         # TODO: But we don't need a RenderTarget if the GPU
#         # will not be used
#         for i, node in enumerate(active_nodes):
#             outputs.append(
#                 node.process(
#                     render_target = render_targets[i],
#                     inputs = inputs,
#                     evaluation_context = evaluation_context,
#                     do_use_gpu = do_use_gpu,
#                     **kwargs
#                 )
#             )

#         return self._combine_outputs(outputs)

#         # Old format below
#         outputs = [
#             node.process(
#                 inputs = inputs,
#                 evaluation_context = evaluation_context,
#                 output_size = output_size,
#                 do_use_gpu = do_use_gpu,
#                 **kwargs
#             )
#             for node in active_nodes
#         ]

#         return self._combine_outputs(outputs)
    
#     @abstractmethod
#     def _combine_outputs(
#         self,
#         outputs: list[Union['np.ndarray', 'moderngl.Texture']]
#     ) -> Union['np.ndarray', 'moderngl.Texture']:
#         """
#         Combine the different `outputs` into a single one.

#         This class must be overwritten by every specific
#         class implementation.
#         """
#         # TODO: This must be according to the type
#         pass