import logging
import typing
from threading import Thread
from typing import Callable

import cv2

from fishy.engine.common.window import WindowClient
from fishy.gui.funcs import GUIFuncsMock
from fishy.helper.helper import print_exc

if typing.TYPE_CHECKING:
    from fishy.gui import GUI


class IEngine:

    def __init__(self, gui_ref: 'Callable[[], GUI]'):
        self.get_gui = gui_ref
        # 0 - off, 1 - running, 2 - quitting
        self.state = 0
        self.window = None
        self.thread = None
        self.name = "default"

    @property
    def gui(self):
        if self.get_gui is None:
            return GUIFuncsMock()

        return self.get_gui().funcs

    @property
    def start(self):
        return self.state == 1

    def toggle_start(self):
        if self.state == 0:
            self.turn_on()
        else:
            self.turn_off()

    def turn_on(self):
        self.state = 1
        self.thread = Thread(target=self._crash_safe)
        self.thread.start()

    def join(self):
        if self.thread:
            logging.debug(f"waiting for {self.name} engine")
            self.thread.join()

    def turn_off(self):
        """
        this method only signals the thread to close using start flag,
        its the responsibility of the thread to shut turn itself off
        """
        if self.state == 1:
            logging.debug(f"sending turn off signal to {self.name} engine")
            self.state = 2
        else:
            logging.debug(f"{self.name} engine already signaled to turn off ")
            # todo: implement force turn off on repeated calls

    # noinspection PyBroadException
    def _crash_safe(self):
        logging.debug(f"starting {self.name} engine thread")
        self.window = WindowClient()
        self.gui.bot_started(True)
        try:
            self.run()
        except Exception:
            logging.error(f"Unhandled exception occurred while running {self.name} engine")
            print_exc()
        self.state = 0
        self.gui.bot_started(False)
        self.window.destroy()
        logging.debug(f"{self.name} engine thread safely exiting")

    def run(self):
        raise NotImplementedError
