import time
from concurrent.futures import ThreadPoolExecutor
from contextlib import contextmanager

import numpy as np
import panel as pn
import param
from asyncio import wrap_future

class ProgressExtMod(pn.viewable.Viewer):
    """A custom component for easy progress reporting"""

    completed = param.Integer(default=0)
    bar_color = param.String(default="info")
    num_tasks = param.Integer(default=100, bounds=(1, None))

    # @param.depends('completed', 'num_tasks')
    @property
    def value(self) -> int:
        """Returns the progress value

        Returns:
            int: The progress value
        """
        return int(100 * (self.completed / self.num_tasks))

    def reset(self):
        """Resets the value and message"""
        # Please note the order matters as the Widgets updates two times. One for each change
        self.completed = 0

    def __panel__(self):
        return self.view

    @param.depends("completed", "bar_color")
    def view(self):
        """View the widget
        Returns:
            pn.viewable.Viewable: Add this to your app to see the progress reported
        """
        if self.value:
            return pn.widgets.Progress(
                active=True, value=self.value, align="center", sizing_mode="stretch_width"
            )
        return None

    @contextmanager
    def increment(self):
        """Increments the value
        
        Can be used as context manager or decorator
        
        Yields:
            None: Nothing is yielded
        """
        self.completed += 1
        yield
        if self.completed == self.num_tasks:
            self.reset()

executor = ThreadPoolExecutor(max_workers=2)  # pylint: disable=consider-using-with
progress = ProgressExtMod()


class AsyncComponent(pn.viewable.Viewer):
    """A component that demonstrates how to run a Blocking Background task asynchronously
    in Panel"""

    select = param.Selector(objects=range(10))
    slider = param.Number(2, bounds=(0, 10))
    
    run_blocking_task = param.Event(label="RUN")
    result = param.Number(0)
    view = param.Parameter()

    def __init__(self, **params):
        super().__init__(**params)

        self._layout = pn.Column(
            pn.pane.Markdown("## Blocking Task Running in Background"),
            pn.Param(
                self,
                parameters=["run_blocking_task", "result"],
                widgets={"result": {"disabled": True}, "run_blocking_task": {"button_type": "primary"}},
                show_name=False,
            ),
            progress,
            pn.pane.Markdown("## Other, Non-Blocked Tasks"),
            pn.Param(
                self,
                parameters=["select", "slider"],
                widgets={"text": {"disabled": True}},
                show_name=False,
            ),
            self.text
        )

    def __panel__(self):
        return self._layout

    @param.depends("slider", "select")
    def text(self):
        if self.select:
            select = self.select
        else:
            select = 0
        return f"{select} + {self.slider} = {select + self.slider}"

    @pn.depends("run_blocking_task", watch=True)
    async def _run_blocking_tasks(self, num_tasks=10):
        """Runs background tasks num_tasks times"""
        num_tasks = 20
        progress.num_tasks = num_tasks
        for _ in range(num_tasks):
            future = executor.submit(self._run_blocking_task)
            result = await wrap_future(future)
            self._update(result)

    @progress.increment()
    def _update(self, number):
        self.result += number

    @staticmethod
    def _run_blocking_task():
        time.sleep(np.random.randint(1, 2))
        return 5

if __name__.startswith("bokeh"):
    pn.extension(template="fast")
    pn.pane.Markdown(__doc__).servable()
    AsyncComponent().servable()  # pylint: disable=no-value-for-parameter