""" OpenCV Backend RTSP Client """

import cv2
from io import BytesIO
from PIL import Image

from threading import Thread

class Client:
    """ Maintain live RTSP feed without buffering. """
    _stream = None

    def __init__(self, rtsp_server_uri, verbose = False):
        """
            rtsp_server_uri: the path to an RTSP server. should start with "rtsp://"
            verbose: print log or not
        """
        self.rtsp_server_uri = rtsp_server_uri
        self._verbose = verbose

        self._bg = False
        self.open()

    def __enter__(self,*args,**kwargs):
        """ Returns the object which later will have __exit__ called.
            This relationship creates a context manager. """
        return self

    def __exit__(self, type=None, value=None, traceback=None):
        """ Together with __enter__, allows support for `with-` clauses. """
        self.close()

    def open(self):
        if self.isOpened():
            return
        self._stream = cv2.VideoCapture(self.rtsp_server_uri)
        if self._verbose:
            print("Connected to video source {}.".format(self.rtsp_server_uri))
        self._bg = True
        t = Thread(target=self._update, args=())
        t.daemon = True
        t.start()
        return self

    def close(self):
        """ signal background thread to stop. release CV stream """
        self._bg = False
        self._stream.release()
        if self._verbose:
            print("Disconnected from {}".format(self.rtsp_server_uri))

    def isOpened(self):
        """ return true if stream is opened and being read, else ensure closed """
        try:
            return (self._stream is not None) and self._stream.isOpened() and self._bg
        except:
            self.close()
            return False

    def _update(self):
        while self.isOpened():
            (grabbed, frame) = self._stream.read()
            if not grabbed:
                self._bg = False
            else:
                self._queue = frame

    def read(self,raw=False):
        """ Retrieve most recent frame and convert to PIL. Return unconverted with raw=True. """
        try:
            if raw:
                return self._queue
            else:
                return Image.fromarray(cv2.cvtColor(self._queue, cv2.COLOR_BGR2RGB))
        except:
            return None

    def preview(self):
        """ Blocking function. Opens OpenCV window to display stream. """
        win_name = 'RTSP'
        cv2.namedWindow(win_name, cv2.WINDOW_AUTOSIZE)
        cv2.moveWindow(win_name,20,20)
        while(self.isOpened()):
            cv2.imshow(win_name,self.read(raw=True))
            if cv2.waitKey(25) & 0xFF == ord('q'):
                break
        cv2.waitKey()
        cv2.destroyAllWindows()
        cv2.waitKey()

class PicamVideoFeed:

    def __init__(self):
        import picamera
        self.cam = picamera.PiCamera()

    def preview(self,*args,**kwargs):
        """ Blocking function. Opens OpenCV window to display stream. """
        self.cam.start_preview(*args,**kwargs)

    def open(self):
        pass

    def isOpened(self):
        return True

    def read(self):
        """https://picamera.readthedocs.io/en/release-1.13/recipes1.html#capturing-to-a-pil-image"""
        stream = BytesIO()
        self.cam.capture(stream, format='png')
        # "Rewind" the stream to the beginning so we can read its content
        stream.seek(0)
        return Image.open(stream)

    def close(self):
        pass

    def stop(self):
        pass

class WebcamVideoFeed:
    def __init__(self, source_id, verbose = False):
        """
            source_id: the id of a camera interface. Should be an integer
            verbose: print log or not
        """
        self._cam_id = source_id
        self._verbose = verbose
        self.open()

    def __enter__(self,*args,**kwargs):
        """ Returns the object which later will have __exit__ called.
            This relationship creates a context manager. """
        return self

    def __exit__(self, type=None, value=None, traceback=None):
        """ Together with __enter__, allows support for `with-` clauses. """
        self.close()

    def open(self):
        if self.isOpened():
            return

        self._stream = cv2.VideoCapture(self._cam_id)

        if self._verbose:
            if self.isOpened():
                print("Connected to video source {}.".format(self._cam_id))
            else:
                print("Failed to connect to source {}.".format(self._cam_id))
                return

    def close(self):
        if self.isOpened():
            self._stream.release()

    def isOpened(self):
        try:
            return self._stream is not None and self._stream.isOpened()
        except:
            return False

    def read(self):
        (grabbed, frame) = self._stream.read()
        return Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))

    def preview(self):
        """ Blocking function. Opens OpenCV window to display stream. """
        win_name = 'Camera'
        cv2.namedWindow(win_name, cv2.WINDOW_AUTOSIZE)
        cv2.moveWindow(win_name,20,20)
        self.open()
        while(self.isOpened()):
            cv2.imshow(win_name,self._stream.read()[1])
            if cv2.waitKey(25) & 0xFF == ord('q'):
                break
        cv2.waitKey()
        cv2.destroyAllWindows()
        cv2.waitKey()