import logging
import time
import cv2
import numpy as np
from polling2 import poll, TimeoutException
from screeninfo import get_monitors
import threading


logger = logging.getLogger(__name__)


def filter_displays(displays, width, height):
    return [disp for disp in displays if disp.width == width and disp.height == height]


class ExtendedModeDisplay:
    _window_name = "ExtendedDisplay"
    _display_timeout = 10 * 60  # 10 minutes in seconds

    """Class that renders an opencv window on the extended desktop"""

    def __init__(self, resolution, is_preview=False):
        self._start_time = time.time()
        displays = get_monitors()
        filtered_displays = filter_displays(displays, resolution[0], resolution[1])
        if len(filtered_displays) == 0:
            raise SystemError("Check HMD is connected and running in extended mode")
        if len(filtered_displays) > 1:
            raise SystemError("Multiple displays found with the same resolution")
        position = (filtered_displays[0].x, filtered_displays[0].y)

        cv2.namedWindow(self._window_name, cv2.WINDOW_NORMAL)

        def check_window_position():
            cv2.moveWindow(self._window_name, position[0], position[1])
            self.display_side_by_side(np.zeros(resolution))
            cv2.setWindowProperty(
                self._window_name, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN
            )
            cv2.moveWindow(self._window_name, position[0], position[1])
            cv2.resizeWindow(self._window_name, resolution[0], resolution[1])
            self.display_side_by_side(np.zeros(resolution))
            x, y, width, height = cv2.getWindowImageRect(self._window_name)
            logging.debug("Checking window position %s, %s", x, y)
            logging.debug("Checking window resolution %s, %s", width, height)
            return (x, y) == position and (width, height) == resolution

        try:
            poll(lambda: check_window_position(), step=0.1, timeout=5)
        except TimeoutException:
            if is_preview:
                return
            else:
                raise SystemError("Failed to set window position and resolution")

    def check_timeout(self):
        """Check if display timeout has been reached, must be called in main loop"""
        if threading.current_thread() != threading.main_thread():
            raise RuntimeError("check_timeout must be called in main thread")
        if time.time() - self._start_time > self._display_timeout:
            logger.info("Display timeout reached (30 minutes). Closing window.")
            self.close()
            raise TimeoutException("Display timeout reached (30 minutes)")

    def im_show(self, im):
        logger.debug("Displaying image")
        # Check if window still exists
        try:
            visible = cv2.getWindowProperty(self._window_name, cv2.WND_PROP_VISIBLE)
            if visible < 1:  # Window was closed
                raise TimeoutException("Window was closed")
        except cv2.error:  # Window doesn't exist
            raise TimeoutException("Window was closed")

        cv2.imshow(self._window_name, im)
        cv2.waitKey(1)

    def display_side_by_side(self, left, right=None):
        if right is not None:
            self.im_show(cv2.hconcat([left, right]))
        else:
            self.im_show(cv2.hconcat([left, left]))

    def close(self):
        logger.debug("Closing extended display")
        try:
            cv2.destroyWindow(self._window_name)
        except cv2.error:
            pass  # Window might already be closed
