# Copyright 2021 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
"""Pigweed Console progress bar task state."""

from contextvars import ContextVar
import copy
from dataclasses import dataclass, field
import signal
from typing import Dict, Optional, Union

from prompt_toolkit.application import get_app_or_none
from prompt_toolkit.shortcuts import ProgressBar
from prompt_toolkit.shortcuts.progress_bar import formatters

from pw_console.progress_bar.progress_bar_impl import (
    IterationsPerSecondIfNotHidden,
    ProgressBarImpl,
    TextIfNotHidden,
    TimeLeftIfNotHidden,
)
from pw_console.progress_bar.progress_bar_task_counter import (
    ProgressBarTaskCounter,
)
from pw_console.style import generate_styles

CUSTOM_FORMATTERS = [
    formatters.Label(suffix=': '),
    formatters.Rainbow(
        formatters.Bar(start='|Pigw', end='|', sym_a='e', sym_b='d!', sym_c=' ')
    ),
    formatters.Text(' '),
    formatters.Progress(),
    formatters.Text(' ['),
    formatters.Percentage(),
    formatters.Text('] in '),
    formatters.TimeElapsed(),
    TextIfNotHidden(' ('),
    IterationsPerSecondIfNotHidden(),
    TextIfNotHidden('/s, eta: '),
    TimeLeftIfNotHidden(),
    TextIfNotHidden(')'),
]


def prompt_toolkit_app_running() -> bool:
    existing_app = get_app_or_none()
    if existing_app:
        return True
    return False


@dataclass
class ProgressBarState:
    """Pigweed Console wide state for all repl progress bars.

    An instance of this class is intended to be a global variable."""

    tasks: Dict[str, ProgressBarTaskCounter] = field(default_factory=dict)
    instance: Optional[Union[ProgressBar, ProgressBarImpl]] = None

    def _install_sigint_handler(self) -> None:
        """Add ctrl-c handling if not running inside pw_console"""

        def handle_sigint(_signum, _frame):
            # Shut down the ProgressBar prompt_toolkit application
            prog_bar = self.instance
            if prog_bar is not None and hasattr(prog_bar, '__exit__'):
                prog_bar.__exit__()  # pylint: disable=unnecessary-dunder-call
            raise KeyboardInterrupt

        signal.signal(signal.SIGINT, handle_sigint)

    def startup_progress_bar_impl(self):
        prog_bar = self.instance
        if not prog_bar:
            if prompt_toolkit_app_running():
                prog_bar = ProgressBarImpl(
                    style=get_app_or_none().style, formatters=CUSTOM_FORMATTERS
                )
            else:
                self._install_sigint_handler()
                prog_bar = ProgressBar(
                    style=generate_styles(), formatters=CUSTOM_FORMATTERS
                )
                # Start the ProgressBar prompt_toolkit application in a separate
                # thread.
                prog_bar.__enter__()  # pylint: disable=unnecessary-dunder-call
            self.instance = prog_bar
        return self.instance

    def cleanup_finished_tasks(self) -> None:
        for task_name in copy.copy(list(self.tasks.keys())):
            task = self.tasks[task_name]
            if task.completed or task.canceled:
                ptc = task.prompt_toolkit_counter
                self.tasks.pop(task_name, None)
                if (
                    self.instance
                    and self.instance.counters
                    and ptc in self.instance.counters
                ):
                    self.instance.counters.remove(ptc)

    @property
    def all_tasks_complete(self) -> bool:
        tasks_complete = [
            task.completed or task.canceled
            for _task_name, task in self.tasks.items()
        ]
        self.cleanup_finished_tasks()
        return all(tasks_complete)

    def cancel_all_tasks(self):
        self.tasks = {}
        if self.instance is not None:
            self.instance.counters = []

    def get_container(self):
        prog_bar = self.instance
        if prog_bar is not None and hasattr(prog_bar, '__pt_container__'):
            return prog_bar.__pt_container__()
        return None


TASKS_CONTEXTVAR = ContextVar(
    'pw_console_progress_bar_tasks', default=ProgressBarState()
)
