#!/usr/bin/env python3
import asyncio
import time
from collections.abc import Generator
from multiprocessing import Manager
from queue import Empty, Queue
from typing import Callable

from nicegui import app, background_tasks, run, ui


class Worker:

    def __init__(self) -> None:
        self._queue: Queue
        self.progress: float = 0.0
        self.is_running: bool = False

        app.on_startup(self._create_queue)

    async def run(self, func: Callable[..., Generator[float, None, None]]) -> None:
        background_tasks.create(run.cpu_bound(self._run_generator, func, self._queue))
        background_tasks.create(self._consume_queue())

    @staticmethod
    def _run_generator(func: Callable[..., Generator[float, None, None]], queue: Queue) -> None:
        for progress in func():
            queue.put({'progress': progress})
        queue.put({'progress': 1.0})

    def _create_queue(self) -> None:
        self._queue = Manager().Queue()

    async def _consume_queue(self) -> None:
        self.is_running = True
        self.progress = 0.0
        while self.progress < 1.0:
            try:
                msg = self._queue.get_nowait()
            except Empty:
                await asyncio.sleep(0.1)
                continue
            self.progress = msg['progress']
        self.is_running = False


def heavy_computation() -> Generator[float, None, None]:
    n = 50
    for i in range(n):
        time.sleep(0.1)
        yield i / n


worker = Worker()


@ui.page('/')
def main_page():
    ui.button('compute', on_click=lambda: worker.run(heavy_computation))
    ui.linear_progress().props('instant-feedback') \
        .bind_value_from(worker, 'progress') \
        .bind_visibility_from(worker, 'is_running')


ui.run()
