class VideoFileStreamReader(StreamReader):

    FAST_MOVE_LENGTH_SECONDS = 5

    @property
    def end_of_stream(self):
        return self._video_capture.current_frame >= self._video_capture.frame_count - 1

    @property
    def frame_size(self):
        return self._video_capture.frame_size

    @property
    def frame_rate(self):
        return self._video_capture.frame_rate

    def __init__(self, video_file_name, limit_to_video_fps = False):
        self._video_capture = VideoCapture()
        self._is_opened = self._video_capture.open_file(video_file_name)
        if not self._is_opened:
            raise IOError("The specified file cannot be opened.")
        self._limit_to_video_fps = limit_to_video_fps
        self._last_read_time = None
        self._lock = threading.Lock()

    def __del__(self):
        self.close()

    def read_next(self):
        if not self._is_opened:
            raise InvalidOperationError("Stream is closed.")
        self._apply_fps_limit()
        self._last_read_time = datetime.datetime.now()

        self._lock.acquire()
        success, image = self._video_capture.read()
        self._lock.release()

        if not success:
            raise IOError("Failed to read the next frame.")
        return Image(image, ColorMode.BGR) 

    def move_backward(self):
        self._lock.acquire()
        self._set_frame(self._video_capture.current_frame - (self.FAST_MOVE_LENGTH_SECONDS * self.frame_rate))
        self._lock.release()

    def move_forward(self):
        self._lock.acquire()
        self._set_frame(self._video_capture.current_frame + (self.FAST_MOVE_LENGTH_SECONDS * self.frame_rate))
        self._lock.release()

    def restart(self):
        self._lock.acquire()
        self._set_frame(1)
        self._lock.release()

    def _set_frame(self, frame_number):
        if frame_number < 1 and frame_number <= self._video_capture.frame_count - 1:
            self._video_capture.current_frame = 1
        elif frame_number > self._video_capture.frame_count - 1:
            self._video_capture.current_frame = self._video_capture.frame_count - 1
        else:
            self._video_capture.current_frame = frame_number

    def _apply_fps_limit(self):
        if not self._limit_to_video_fps or self._last_read_time is None or self.frame_rate == 0:
            return
        elapsed_time = datetime.datetime.now() - self._last_read_time
        elapsed_seconds = elapsed_time.total_seconds()
        seconds_per_frame = 1 / self.frame_rate
        if elapsed_seconds < seconds_per_frame:
            time.sleep(seconds_per_frame - elapsed_seconds)

    def close(self):
        self._is_opened = False
        self._video_capture.release()
class CameraStreamReader(StreamReader):

    @property
    def end_of_stream(self):
        return False

    @property
    def frame_size(self):
        return self._video_capture.frame_size

    @frame_size.setter
    def frame_size(self, value):
        self._video_capture.frame_size = value

    @property
    def frame_rate(self):
        return self._video_capture.frame_rate

    @property
    def camera_id(self):
        return self._camera_id

    @property
    def frame_rotation_angle(self):
        return self._frame_rotation_angle

    @frame_rotation_angle.setter
    def frame_rotation_angle(self, value):
        self._frame_rotation_angle = value

    def __init__(self, camera_id):
        self._camera_id = camera_id
        self._video_capture = VideoCapture()
        self._frame_rotation_angle = 0
        self._is_opened = self._video_capture.open_camera(camera_id)
        if not self._is_opened:
            raise IOError("The specified camera cannot be opened.")

    def __del__(self):
        self.close()

    def read_next(self):
        if not self._is_opened:
            raise InvalidOperationError("Stream is closed.")
        success, image = self._video_capture.read()
        if not success:
            raise IOError("Failed to read the next frame.")
        return self._create_image(image)

    def _create_image(self, cv_image):
        image = Image(cv_image, ColorMode.BGR)
        if self._frame_rotation_angle != 0:
            image.rotate(self._frame_rotation_angle)
        return image

    def close(self):
        self._is_opened = False
        self._video_capture.release()

    def move_forward(self):
        raise NotImplementedError("This action is not available for this stream type.")

    def move_backward(self):
        raise NotImplementedError("This action is not available for this stream type.")

    def open_camera_settings_window(self):
        self._video_capture.open_camera_settings_window()