import asyncio
from concurrent.futures import Future
from typing import Dict

from lsprotocol.types import (
    PROGRESS,
    WINDOW_WORK_DONE_PROGRESS_CREATE,
    ProgressParams,
    ProgressToken,
    WorkDoneProgressBegin,
    WorkDoneProgressEnd,
    WorkDoneProgressReport,
    WorkDoneProgressCreateParams,
)
from pygls.protocol import LanguageServerProtocol


class Progress:
    """A class for working with client's progress bar.

    Attributes:
        _lsp(LanguageServerProtocol): Language server protocol instance
        tokens(dict): Holds futures for work done progress tokens that are
            already registered. These futures will be cancelled if the client
            sends a cancel work done process notification.
    """

    def __init__(self, lsp: LanguageServerProtocol) -> None:
        self._lsp = lsp

        self.tokens: Dict[ProgressToken, Future] = {}

    def _check_token_registered(self, token: ProgressToken) -> None:
        if token in self.tokens:
            raise Exception("Token is already registered!")

    def _register_token(self, token: ProgressToken) -> None:
        self.tokens[token] = Future()

    def create(self, token: ProgressToken, callback=None) -> Future:
        """Create a server initiated work done progress."""
        self._check_token_registered(token)

        def on_created(*args, **kwargs):
            self._register_token(token)
            if callback is not None:
                callback(*args, **kwargs)

        return self._lsp.send_request(
            WINDOW_WORK_DONE_PROGRESS_CREATE,
            WorkDoneProgressCreateParams(token=token),
            on_created,
        )

    async def create_async(self, token: ProgressToken) -> asyncio.Future:
        """Create a server initiated work done progress."""
        self._check_token_registered(token)

        result = await self._lsp.send_request_async(
            WINDOW_WORK_DONE_PROGRESS_CREATE,
            WorkDoneProgressCreateParams(token=token),
        )
        self._register_token(token)
        return result

    def begin(self, token: ProgressToken, value: WorkDoneProgressBegin) -> None:
        """Notify beginning of work."""
        # Register cancellation future for the case of client initiated progress
        self.tokens.setdefault(token, Future())

        return self._lsp.notify(PROGRESS, ProgressParams(token=token, value=value))

    def report(self, token: ProgressToken, value: WorkDoneProgressReport) -> None:
        """Notify progress of work."""
        self._lsp.notify(PROGRESS, ProgressParams(token=token, value=value))

    def end(self, token: ProgressToken, value: WorkDoneProgressEnd) -> None:
        """Notify end of work."""
        self._lsp.notify(PROGRESS, ProgressParams(token=token, value=value))
