import asyncio
import multiprocessing as mp
import webview

from lightweight_charts import abstract
from .util import parse_event_message


class CallbackAPI:
    def __init__(self, emit_queue):
        self.emit_q = emit_queue

    def callback(self, message: str):
        self.emit_q.put(message)


class PyWV:
    def __init__(self, q, start_ev, exit_ev, loaded, emit_queue, return_queue, html, debug,
                 width, height, x, y, screen, on_top, maximize, title):
        self.queue = q
        self.return_queue = return_queue
        self.exit = exit_ev
        self.callback_api = CallbackAPI(emit_queue)
        self.loaded: list = loaded
        self.html = html

        self.windows = []
        self.create_window(width, height, x, y, screen, on_top, maximize, title)

        start_ev.wait()
        webview.start(debug=debug)
        self.exit.set()

    def create_window(self, width, height, x, y, screen=None, on_top=False, maximize=False, title=''):
        screen = webview.screens[screen] if screen is not None else None
        if maximize:
            if screen is None:
                active_screen = webview.screens[0]
                width, height = active_screen.width, active_screen.height
            else:
                width, height = screen.width, screen.height
        self.windows.append(webview.create_window(
            title, html=self.html, js_api=self.callback_api,
            width=width, height=height, x=x, y=y, screen=screen,
            on_top=on_top,  background_color='#000000'))
        self.windows[-1].events.loaded += lambda: self.loop(self.loaded[len(self.windows)-1])

    def loop(self, loaded):
        loaded.set()
        while 1:
            i, arg = self.queue.get()
            if i == 'create_window':
                self.create_window(*arg)
            elif arg in ('show', 'hide'):
                getattr(self.windows[i], arg)()
            elif arg == 'exit':
                self.exit.set()
            else:
                try:
                    if '_~_~RETURN~_~_' in arg:
                        self.return_queue.put(self.windows[i].evaluate_js(arg[14:]))
                    else:
                        self.windows[i].evaluate_js(arg)
                except KeyError:
                    return


class Chart(abstract.AbstractChart):
    MAX_WINDOWS = 10
    _window_num = 0
    _main_window_handlers = None
    _exit, _start = (mp.Event() for _ in range(2))
    _q, _emit_q, _return_q = (mp.Queue() for _ in range(3))
    _loaded_list = [mp.Event() for _ in range(MAX_WINDOWS)]

    def __init__(self, width: int = 800, height: int = 600, x: int = None, y: int = None, title: str = '',
                 screen: int = None, on_top: bool = False, maximize: bool = False, debug: bool = False,
                 toolbox: bool = False, inner_width: float = 1.0, inner_height: float = 1.0,
                 scale_candles_only: bool = False):
        self._i = Chart._window_num
        self._loaded = Chart._loaded_list[self._i]
        abstract.Window._return_q = Chart._return_q
        Chart._window_num += 1
        self.is_alive = True

        window = abstract.Window(lambda s: self._q.put((self._i, s)), 'pywebview.api.callback')
        if self._i == 0:
            super().__init__(window, inner_width, inner_height, scale_candles_only, toolbox)
            Chart._main_window_handlers = self.win.handlers
            self._process = mp.Process(target=PyWV, args=(
                self._q, self._start, self._exit, Chart._loaded_list,
                self._emit_q, self._return_q, abstract.TEMPLATE, debug,
                width, height, x, y, screen, on_top, maximize, title
            ), daemon=True)
            self._process.start()
        else:
            window.handlers = Chart._main_window_handlers
            super().__init__(window, inner_width, inner_height, scale_candles_only, toolbox)
            self._q.put(('create_window', (width, height, x, y, screen, on_top, maximize, title)))

    def show(self, block: bool = False):
        """
        Shows the chart window.\n
        :param block: blocks execution until the chart is closed.
        """
        if not self.win.loaded:
            self._start.set()
            self._loaded.wait()
            self.win.on_js_load()
        else:
            self._q.put((self._i, 'show'))
        if block:
            asyncio.run(self.show_async(block=True))

    async def show_async(self, block=False):
        self.show(block=False)
        if not block:
            asyncio.create_task(self.show_async(block=True))
            return
        try:
            from lightweight_charts import polygon
            [asyncio.create_task(self.polygon.async_set(*args)) for args in polygon._set_on_load]
            while 1:
                while self._emit_q.empty() and not self._exit.is_set():
                    await asyncio.sleep(0.05)
                if self._exit.is_set():
                    self._exit.clear()
                    self.is_alive = False
                    self.exit()
                    return
                elif not self._emit_q.empty():
                    func, args = parse_event_message(self.win, self._emit_q.get())
                    await func(*args) if asyncio.iscoroutinefunction(func) else func(*args)
                    continue
        except KeyboardInterrupt:
            return

    def hide(self):
        """
        Hides the chart window.\n
        """
        self._q.put((self._i, 'hide'))

    def exit(self):
        """
        Exits and destroys the chart window.\n
        """
        self._q.put((self._i, 'exit'))
        self._exit.wait() if self.win.loaded else None
        self._process.terminate()

        Chart._main_window_handlers = None
        Chart._window_num = 0
        Chart._q = mp.Queue()
        Chart._exit.clear(), Chart._start.clear()
        self.is_alive = False
