from cvpubsubs.window_sub.winctrl import WinCtrl
import numpy as np

if False:
    from typing import Union


def global_cv_display_callback(frame,  # type: np.ndarray
                               cam_id  # type: Union[int, str]
                               ):
    from cvpubsubs.window_sub import SubscriberWindows
    """Default callback for sending frames to the global frame dictionary.

    :param frame: The video or image frame
    :type frame: np.ndarray
    :param cam_id: The video or image source
    :type cam_id: Union[int, str]
    """
    SubscriberWindows.frame_dict[str(cam_id) + "frame"] = frame


class function_display_callback(object):  # NOSONAR
    def __init__(self, display_function, finish_function=None):
        """Used for running arbitrary functions on pixels.

        >>> import random
        >>> from cvpubsubs.webcam_pub import VideoHandlerThread
        >>> img = np.zeros((300, 300, 3))
        >>> def fun(array, coords, finished):
        ...     r,g,b = random.random()/20.0, random.random()/20.0, random.random()/20.0
        ...     array[coords[0:2]] = (array[coords[0:2]] + [r,g,b])%1.0
        >>> VideoHandlerThread(video_source=img, callbacks=function_display_callback(fun)).display()

        :param display_function: a function to run on the input image.
        :param finish_function: a function to run on the input image when the other function finishes.
        """
        self.looping = True
        self.first_call = True

        def _run_finisher(self, frame, finished, *args, **kwargs):
            if not callable(finish_function):
                WinCtrl.quit()
            else:
                finished = finish_function(frame, Ellipsis, finished, *args, **kwargs)
                if finished:
                    WinCtrl.quit()

        def _display_internal(self, frame, *args, **kwargs):
            finished = True
            if self.first_call:
                # return to display initial frame
                self.first_call = False
                return
            if self.looping:
                it = np.nditer(frame, flags=['multi_index'])
                while not it.finished:
                    x, y, c = it.multi_index
                    finished = display_function(frame, (x, y, c), finished, *args, **kwargs)
                    it.iternext()
            if finished:
                self.looping = False
                _run_finisher(self, frame, finished, *args, **kwargs)

        self.inner_function = _display_internal

    def __call__(self, *args, **kwargs):
        return self.inner_function(self, *args, **kwargs)
