"""
Key binding handlers for displaying completions.
"""
import asyncio
import math
from typing import TYPE_CHECKING, List

from prot.prompt.application.run_in_terminal import in_terminal
from prot.prompt.completion import (
    CompleteEvent,
    Completion,
    get_common_complete_suffix,
)
from prot.prompt.formatted_text import StyleAndTextTuples
from prot.prompt.key_binding.key_bindings import KeyBindings
from prot.prompt.key_binding.key_processor import KeyPressEvent
from prot.prompt.keys import Keys
from prot.prompt.utils import get_cwidth

if TYPE_CHECKING:
    from prot.prompt.application import Application
    from prot.prompt.shortcuts import PromptSession

__all__ = [
    "generate_completions",
    "display_completions_like_readline",
]

E = KeyPressEvent


def generate_completions(event: E) -> None:
    r"""
    Tab-completion: where the first tab completes the common suffix and the
    second tab lists all the completions.
    """
    b = event.current_buffer

    # When already navigating through completions, select the next one.
    if b.complete_state:
        b.complete_next()
    else:
        b.start_completion(insert_common_part=True)


def display_completions_like_readline(event: E) -> None:
    """
    Key binding handler for readline-style tab completion.
    This is meant to be as similar as possible to the way how readline displays
    completions.

    Generate the completions immediately (blocking) and display them above the
    prompt in columns.

    Usage::

        # Call this handler when 'Tab' has been pressed.
        key_bindings.add(Keys.ControlI)(display_completions_like_readline)
    """
    # Request completions.
    b = event.current_buffer
    if b.completer is None:
        return
    complete_event = CompleteEvent(completion_requested=True)
    completions = list(b.completer.get_completions(b.document, complete_event))

    # Calculate the common suffix.
    common_suffix = get_common_complete_suffix(b.document, completions)

    # One completion: insert it.
    if len(completions) == 1:
        b.delete_before_cursor(-completions[0].start_position)
        b.insert_text(completions[0].text)
    # Multiple completions with common part.
    elif common_suffix:
        b.insert_text(common_suffix)
    # Otherwise: display all completions.
    elif completions:
        _display_completions_like_readline(event.app, completions)


def _display_completions_like_readline(
    app: "Application", completions: List[Completion]
) -> "asyncio.Task[None]":
    """
    Display the list of completions in columns above the prompt.
    This will ask for a confirmation if there are too many completions to fit
    on a single page and provide a paginator to walk through them.
    """
    from prot.prompt.shortcuts.prompt import create_confirm_session
    from prot.prompt.formatted_text import to_formatted_text

    # Get terminal dimensions.
    term_size = app.output.get_size()
    term_width = term_size.columns
    term_height = term_size.rows

    # Calculate amount of required columns/rows for displaying the
    # completions. (Keep in mind that completions are displayed
    # alphabetically column-wise.)
    max_compl_width = min(
        term_width, max(get_cwidth(c.display_text) for c in completions) + 1
    )
    column_count = max(1, term_width // max_compl_width)
    completions_per_page = column_count * (term_height - 1)
    page_count = int(math.ceil(len(completions) / float(completions_per_page)))
    # Note: math.ceil can return float on Python2.

    def display(page: int) -> None:
        # Display completions.
        page_completions = completions[
            page * completions_per_page : (page + 1) * completions_per_page
        ]

        page_row_count = int(math.ceil(len(page_completions) / float(column_count)))
        page_columns = [
            page_completions[i * page_row_count : (i + 1) * page_row_count]
            for i in range(column_count)
        ]

        result: StyleAndTextTuples = []

        for r in range(page_row_count):
            for c in range(column_count):
                try:
                    completion = page_columns[c][r]
                    style = "class:readline-like-completions.completion " + (
                        completion.style or ""
                    )

                    result.extend(to_formatted_text(completion.display, style=style))

                    # Add padding.
                    padding = max_compl_width - get_cwidth(completion.display_text)
                    result.append((completion.style, " " * padding,))
                except IndexError:
                    pass
            result.append(("", "\n"))

        app.print_text(to_formatted_text(result, "class:readline-like-completions"))

    # User interaction through an application generator function.
    async def run_compl() -> None:
        " Coroutine. "
        async with in_terminal(render_cli_done=True):
            if len(completions) > completions_per_page:
                # Ask confirmation if it doesn't fit on the screen.
                confirm = await create_confirm_session(
                    "Display all {} possibilities?".format(len(completions)),
                ).prompt_async()

                if confirm:
                    # Display pages.
                    for page in range(page_count):
                        display(page)

                        if page != page_count - 1:
                            # Display --MORE-- and go to the next page.
                            show_more = await _create_more_session(
                                "--MORE--"
                            ).prompt_async()

                            if not show_more:
                                return
                else:
                    app.output.flush()
            else:
                # Display all completions.
                display(0)

    return app.create_background_task(run_compl())


def _create_more_session(message: str = "--MORE--") -> "PromptSession":
    """
    Create a `PromptSession` object for displaying the "--MORE--".
    """
    from prot.prompt.shortcuts import PromptSession

    bindings = KeyBindings()

    @bindings.add(" ")
    @bindings.add("y")
    @bindings.add("Y")
    @bindings.add(Keys.ControlJ)
    @bindings.add(Keys.ControlM)
    @bindings.add(Keys.ControlI)  # Tab.
    def _yes(event: E) -> None:
        event.app.exit(result=True)

    @bindings.add("n")
    @bindings.add("N")
    @bindings.add("q")
    @bindings.add("Q")
    @bindings.add(Keys.ControlC)
    def _no(event: E) -> None:
        event.app.exit(result=False)

    @bindings.add(Keys.Any)
    def _ignore(event: E) -> None:
        " Disable inserting of text. "

    return PromptSession(message, key_bindings=bindings, erase_when_done=True)
