# -*- coding: utf-8 -*-
import inspect
from prompt_toolkit import PromptSession
from prompt_toolkit.filters import IsDone, Always
from prompt_toolkit.layout import (
    FormattedTextControl,
    Layout,
    HSplit,
    ConditionalContainer,
    Window,
)
from prompt_toolkit.validation import Validator, ValidationError
from typing import Optional, Any, List, Text, Dict, Union, Callable, Tuple

from questionary.constants import (
    SELECTED_POINTER,
    INDICATOR_SELECTED,
    INDICATOR_UNSELECTED,
)


class Choice(object):
    """One choice in a select, rawselect or checkbox."""

    def __init__(
        self,
        title: Text,
        value: Optional[Any] = None,
        disabled: Optional[Text] = None,
        checked: bool = False,
        shortcut_key: Optional[Text] = None,
    ) -> None:
        """Create a new choice.

        Args:
            title: Text shown in the selection list.

            value: Value returned, when the choice is selected.

            disabled: If set, the choice can not be selected by the user. The
                      provided text is used to explain, why the selection is
                      disabled.

            checked: Preselect this choice when displaying the options.

            shortcut_key: Key shortcut used to select this item.
        """

        self.disabled = disabled
        self.title = title
        self.checked = checked

        if value is not None:
            self.value = value
        elif isinstance(title, list):
            self.value = "".join([token[1] for token in title])
        else:
            self.value = title

        if shortcut_key is not None:
            self.shortcut_key = str(shortcut_key)
        else:
            self.shortcut_key = None

    @staticmethod
    def build(c: Union[Text, "Choice", Dict[Text, Any]]) -> "Choice":
        """Create a choice object from different representations."""

        if isinstance(c, Choice):
            return c
        elif isinstance(c, str):
            return Choice(c, c)
        else:
            return Choice(
                c.get("name"),
                c.get("value"),
                c.get("disabled", None),
                c.get("checked"),
                c.get("key"),
            )


class Separator(Choice):
    """Used to space/separate choices group."""

    default_separator = "-" * 15

    def __init__(self, line: Optional[Text] = None):
        """Create a separator in a list.

        Args:
            line: Text to be displayed in the list, by default uses `---`.
        """

        self.line = line or self.default_separator
        super(Separator, self).__init__(self.line, None, "-")


class InquirerControl(FormattedTextControl):
    SHORTCUT_KEYS = [
        "1",
        "2",
        "3",
        "4",
        "5",
        "6",
        "7",
        "8",
        "9",
        "0",
        "a",
        "b",
        "c",
        "d",
        "e",
        "f",
        "g",
        "h",
        "i",
        "j",
        "k",
        "l",
        "m",
        "n",
        "o",
        "p",
        "q",
        "r",
        "s",
        "t",
        "u",
        "v",
        "w",
        "x",
        "y",
        "z",
    ]

    def __init__(
        self,
        choices: List[Union[Text, Choice, Dict[Text, Any]]],
        default: Optional[Any] = None,
        use_indicator: bool = True,
        use_shortcuts: bool = False,
        use_pointer: bool = True,
        **kwargs
    ):

        self.use_indicator = use_indicator
        self.use_shortcuts = use_shortcuts
        self.use_pointer = use_pointer
        self.default = default

        self.pointed_at = None
        self.is_answered = False
        self.choices = []
        self.selected_options = []

        self._init_choices(choices)
        self._assign_shortcut_keys()

        super(InquirerControl, self).__init__(self._get_choice_tokens, **kwargs)

    def _is_selected(self, choice):
        return (
            choice.checked or choice.value == self.default and self.default is not None
        ) and not choice.disabled

    def _assign_shortcut_keys(self):
        available_shortcuts = self.SHORTCUT_KEYS[:]

        # first, make sure we do not double assign a shortcut
        for c in self.choices:
            if c.shortcut_key is not None:
                if c.shortcut_key in available_shortcuts:
                    available_shortcuts.remove(c.shortcut_key)
                else:
                    raise ValueError(
                        "Invalid shortcut '{}'"
                        "for choice '{}'. Shortcuts "
                        "should be single characters or numbers. "
                        "Make sure that all your shortcuts are "
                        "unique.".format(c.shortcut_key, c.title)
                    )

        shortcut_idx = 0
        for c in self.choices:
            if c.shortcut_key is None and not c.disabled:
                c.shortcut_key = available_shortcuts[shortcut_idx]
                shortcut_idx += 1

            if shortcut_idx == len(available_shortcuts):
                break  # fail gracefully if we run out of shortcuts

    def _init_choices(self, choices):
        # helper to convert from question format to internal format
        self.choices = []

        for i, c in enumerate(choices):
            choice = Choice.build(c)

            if self._is_selected(choice):
                self.selected_options.append(choice.value)

            if self.pointed_at is None and not choice.disabled:
                # find the first (available) choice
                self.pointed_at = i

            self.choices.append(choice)

    @property
    def choice_count(self):
        return len(self.choices)

    def _get_choice_tokens(self):
        tokens = []

        def append(index, choice):
            # use value to check if option has been selected
            selected = choice.value in self.selected_options

            if index == self.pointed_at:
                if self.use_pointer:
                    tokens.append(("class:pointer", " {} ".format(SELECTED_POINTER)))
                else:
                    tokens.append(("class:text", "   "))

                tokens.append(("[SetCursorPosition]", ""))
            else:
                tokens.append(("class:text", "   "))

            if isinstance(choice, Separator):
                tokens.append(("class:separator", "{}".format(choice.title)))
            elif choice.disabled:  # disabled
                if isinstance(choice.title, list):
                    tokens.append(
                        ("class:selected" if selected else "class:disabled", "- ")
                    )
                    tokens.extend(choice.title)
                else:
                    tokens.append(
                        (
                            "class:selected" if selected else "class:disabled",
                            "- {}".format(choice.title),
                        )
                    )

                tokens.append(
                    (
                        "class:selected" if selected else "class:disabled",
                        "{}".format(
                            ""
                            if isinstance(choice.disabled, bool)
                            else " ({})".format(choice.disabled)
                        ),
                    )
                )
            else:
                if self.use_shortcuts and choice.shortcut_key is not None:
                    shortcut = "{}) ".format(choice.shortcut_key)
                else:
                    shortcut = ""

                if selected:
                    if self.use_indicator:
                        indicator = INDICATOR_SELECTED + " "
                    else:
                        indicator = ""

                    tokens.append(("class:selected", "{}".format(indicator)))
                else:
                    if self.use_indicator:
                        indicator = INDICATOR_UNSELECTED + " "
                    else:
                        indicator = ""

                    tokens.append(("class:text", "{}".format(indicator)))

                if isinstance(choice.title, list):
                    tokens.extend(choice.title)
                elif selected:
                    tokens.append(
                        ("class:selected", "{}{}".format(shortcut, choice.title))
                    )
                elif index == self.pointed_at:
                    tokens.append(
                        ("class:highlighted", "{}{}".format(shortcut, choice.title))
                    )
                else:
                    tokens.append(("class:text", "{}{}".format(shortcut, choice.title)))

            tokens.append(("", "\n"))

        # prepare the select choices
        for i, c in enumerate(self.choices):
            append(i, c)

        if self.use_shortcuts:
            tokens.append(
                (
                    "class:text",
                    "  Answer: {}" "".format(self.get_pointed_at().shortcut_key),
                )
            )
        else:
            tokens.pop()  # Remove last newline.
        return tokens

    def is_selection_a_separator(self):
        selected = self.choices[self.pointed_at]
        return isinstance(selected, Separator)

    def is_selection_disabled(self):
        return self.choices[self.pointed_at].disabled

    def is_selection_valid(self):
        return not self.is_selection_disabled() and not self.is_selection_a_separator()

    def select_previous(self):
        self.pointed_at = (self.pointed_at - 1) % self.choice_count

    def select_next(self):
        self.pointed_at = (self.pointed_at + 1) % self.choice_count

    def get_pointed_at(self):
        return self.choices[self.pointed_at]

    def get_selected_values(self):
        # get values not labels
        return [
            c
            for c in self.choices
            if (not isinstance(c, Separator) and c.value in self.selected_options)
        ]


def build_validator(validate: Any) -> Optional[Validator]:
    if validate:
        if inspect.isclass(validate) and issubclass(validate, Validator):
            return validate()
        elif isinstance(validate, Validator):
            return validate
        elif callable(validate):

            class _InputValidator(Validator):
                def validate(self, document):
                    verdict = validate(document.text)
                    if verdict is not True:
                        if verdict is False:
                            verdict = "invalid input"
                        raise ValidationError(
                            message=verdict, cursor_position=len(document.text)
                        )

            return _InputValidator()
    return None


def _fix_unecessary_blank_lines(ps: PromptSession) -> None:
    """This is a fix for additional empty lines added by prompt toolkit.

    This assumes the layout of the default session doesn't change, if it
    does, this needs an update."""

    default_container = ps.layout.container

    default_buffer_window = (
        default_container.get_children()[0].content.get_children()[1].content
    )

    assert isinstance(default_buffer_window, Window)
    # this forces the main window to stay as small as possible, avoiding
    # empty lines in selections
    default_buffer_window.dont_extend_height = Always()


def create_inquirer_layout(
    ic: InquirerControl,
    get_prompt_tokens: Callable[[], List[Tuple[Text, Text]]],
    **kwargs
) -> Layout:
    """Create a layout combining question and inquirer selection."""

    ps = PromptSession(get_prompt_tokens, reserve_space_for_menu=0, **kwargs)

    _fix_unecessary_blank_lines(ps)

    return Layout(
        HSplit(
            [ps.layout.container, ConditionalContainer(Window(ic), filter=~IsDone())]
        )
    )