import sys
from enum import IntEnum, IntFlag, auto
from typing import Dict, Iterable, List, Optional, Union

import axipy
from PySide2.QtWidgets import QInputDialog, QMessageBox

__all__: List[str] = [
    "DlgButtons",
    "DlgIcon",
    "show_message",
    "show_dialog",
    "prompt_string",
    "prompt_int",
    "prompt_float",
    "prompt_item",
]


class DlgButtons(IntFlag):
    """Кнопки диалога или сообщения."""

    NONE = auto()
    """Отсутствует."""
    OK = auto()
    """Подтверждение."""
    CANCEL = auto()
    """Отмена."""
    YES = auto()
    """Да"""
    NO = auto()
    """Нет."""
    RETRY = auto()
    """Повтор."""
    ABORT = auto()
    """Прервать."""
    IGNORE = auto()
    """Проигнорировать."""
    OK_CANCEL = OK | CANCEL
    """Комбинация OK и CANCEL."""
    YES_NO = YES | NO
    """Комбинация OK и CANCEL."""
    YES_NO_CANCEL = YES | NO | CANCEL
    """Комбинация YES, NO и CANCEL."""
    RETRY_CANCEL = RETRY | CANCEL
    """Комбинация RETRY и CANCEL."""
    ABORT_RETRY_IGNORE = ABORT | RETRY | IGNORE
    """Комбинация ABORT, RETRY и IGNORE."""


_buttons: Dict[DlgButtons, QMessageBox.StandardButton] = {
    DlgButtons.OK: QMessageBox.Ok,
    DlgButtons.CANCEL: QMessageBox.Cancel,
    DlgButtons.YES: QMessageBox.Yes,
    DlgButtons.NO: QMessageBox.No,
    DlgButtons.RETRY: QMessageBox.Retry,
    DlgButtons.ABORT: QMessageBox.Abort,
    DlgButtons.IGNORE: QMessageBox.Ignore,
}


def _to_qt_button(v: DlgButtons) -> QMessageBox.StandardButton:
    return _buttons[v] if v in _buttons else QMessageBox.NoButton


def _from_qt_button(v: QMessageBox.StandardButton) -> DlgButtons:
    for key, val in _buttons.items():
        if val == v:
            return key
    return DlgButtons.NONE


def _to_qt_buttons(dlg_buttons: DlgButtons) -> Union[QMessageBox.StandardButton, QMessageBox.StandardButtons]:
    res: Union[QMessageBox.StandardButton, QMessageBox.StandardButtons] = QMessageBox.NoButton
    for key, val in _buttons.items():
        if key in dlg_buttons:
            res |= val
    if DlgButtons.OK_CANCEL in dlg_buttons:
        res |= QMessageBox.Ok | QMessageBox.Cancel
    if DlgButtons.YES_NO in dlg_buttons:
        res |= QMessageBox.Yes | QMessageBox.No
    if DlgButtons.YES_NO_CANCEL in dlg_buttons:
        res |= QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel
    if DlgButtons.RETRY_CANCEL in dlg_buttons:
        res |= QMessageBox.Retry | QMessageBox.Cancel
    if DlgButtons.ABORT_RETRY_IGNORE in dlg_buttons:
        res |= QMessageBox.Abort | QMessageBox.Retry | QMessageBox.Ignore
    return res


class DlgIcon(IntEnum):
    """Стандартная иконка."""

    NONE = auto()
    """Отсутствует."""
    INFORMATION = auto()
    """Информация."""
    WARNING = auto()
    """Предупреждение."""
    ERROR = auto()
    """Ошибка."""
    QUESTION = auto()
    """Вопрос."""


def _to_qt_icon(v: DlgIcon) -> QMessageBox.Icon:
    if v == DlgIcon.INFORMATION:
        return QMessageBox.Information
    elif v == DlgIcon.WARNING:
        return QMessageBox.Warning
    elif v == DlgIcon.QUESTION:
        return QMessageBox.Question
    elif v == DlgIcon.ERROR:
        return QMessageBox.Critical
    return QMessageBox.NoIcon


def show_message(
    text: str,
    title: str = "",
    icon: DlgIcon = DlgIcon.NONE,
    details: str = "",
) -> None:
    """
    Показ сообщения.

    Args:
        text: Текст диалога
        title: Заголовок
        icon: Иконка
        details: Дополнительная информация в виде текста

    Пример::

        show_message('Сообщение', 'Заголовок', icon=DlgIcon.INFORMATION)
    """
    mbox = QMessageBox(axipy.view_manager.global_parent)
    if icon is not None and icon != DlgIcon.NONE:
        mbox.setIcon(_to_qt_icon(icon))
    if title:
        mbox.setWindowTitle(title)
    mbox.setText(text)
    if details is not None:
        mbox.setDetailedText(details)
    mbox.exec()


def show_dialog(
    text: str,
    title: str = "",
    buttons: DlgButtons = DlgButtons.OK,
    icon: DlgIcon = DlgIcon.NONE,
    default_button: Optional[DlgButtons] = None,
    details: str = "",
) -> DlgButtons:
    """
    Отображает диалоговое окно с различными опциями и возвращает информацию о кнопке,
    нажатой пользователем.

    Args:
        text: Текст диалога
        title: Заголовок
        buttons: Перечень стандартных кнопок диалога,
        icon: Иконка
        default_button: Кнопка, выбранная по умолчанию. Должна присутствовать в `buttons`
        details: Дополнительная информация в виде текста

    Return:
        Возвращает выбор пользователя в диалоге

    Пример::

        res = show_dialog('Подтвердить действие?', 'Заголовок',
                    icon=DlgIcon.QUESTION,
                    default_button = DlgButtons.CANCEL,
                    buttons = DlgButtons.YES_NO_CANCEL)
        if res == DlgButtons.YES:
            print('Yes')
    """
    mbox = QMessageBox(axipy.view_manager.global_parent)
    if icon is not None and icon != DlgIcon.NONE:
        mbox.setIcon(_to_qt_icon(icon))
    if title:
        mbox.setWindowTitle(title)
    mbox.setText(text)
    mbox.setStandardButtons(_to_qt_buttons(buttons))
    if default_button is not None:
        mbox.setDefaultButton(_to_qt_button(default_button))
    if details is not None:
        mbox.setDetailedText(details)
    return _from_qt_button(mbox.exec())


def prompt_string(
    text: str,
    title: str = "",
    multiline: bool = False,
    value: str = "",
) -> Optional[str]:
    """
    Диалог запроса строкового значения.

    Args:
        text: Текст диалога
        title: Заголовок
        multiline: Поддержка задания многострочного текста
        value: Значение по умолчанию

    Пример::

        res = prompt_string('Введите текст:', multiline=True)
    """
    if multiline:
        res, ok = QInputDialog.getMultiLineText(axipy.view_manager.global_parent, title, text, text=value)
    else:
        res, ok = QInputDialog.getText(axipy.view_manager.global_parent, title, text, text=value)
    if ok:
        return res
    else:
        return None


def prompt_int(
    text: str,
    title: str = "",
    value: int = 0,
    min_value: int = -2147483647,
    max_value: int = 2147483647,
    step: int = 1,
) -> Optional[int]:
    """
    Диалог запроса целого значения.

    Args:
        text: Текст диалога
        title: Заголовок
        value: Значение по умолчанию
        min_value: Минимально возможное значение при задании
        max_value: Максимально возможное значение при задании
        step: Шаг изменения значения посредством мыши

    Пример::

        value = prompt_int('Введите значение (0..100):', min_value=0, max_value=100)
    """
    res, ok = QInputDialog.getInt(axipy.view_manager.global_parent, title, text, value, min_value, max_value, step)
    if ok:
        return res
    return None


def prompt_float(
    text: str,
    title: str = "",
    value: float = 0.0,
    min_value: float = -sys.float_info.max,
    max_value: float = sys.float_info.max,
    decimals: int = 2,
    step: float = 1,
) -> Optional[float]:
    """
    Диалог запроса вещественного значения.

    Args:
        text: Текст диалога
        title: Заголовок
        value: Значение по умолчанию
        min_value: Минимально возможное значение при задании
        max_value: Максимально возможное значение при задании
        decimals: Цифр после запятой
        step: Шаг изменения значения посредством мыши

    Пример::

        value = prompt_float('Введите значение (0..100):', min_value=0, max_value=100)
    """
    d = QInputDialog(axipy.view_manager.global_parent)
    if title is not None:
        d.setWindowTitle(title)
    d.setLabelText(text)
    d.setDoubleMinimum(min_value)
    d.setDoubleMaximum(max_value)
    d.setDoubleValue(value)
    d.setDoubleDecimals(decimals)
    d.setDoubleStep(step)
    res = d.exec()
    if res == QInputDialog.Accepted:
        return d.doubleValue()
    return None


def prompt_item(
    text: str,
    title: str = "",
    items: Optional[Iterable[str]] = None,
    value: Union[int, str] = 0,
    editable: bool = False,
) -> Optional[str]:
    """
    Диалог выбора значения из выпадающего списка.

    Args:
        text: Текст диалога.
        title: Заголовок.
        items: Последовательность значений или итератор.
        value: Значение по умолчанию или его индекс.
        editable: Допустимо ли редактирование текущего значения.

    Return:
        Выбранное значение или пустая строка.

    Пример::

        res = prompt_item('Варианты для выбора:', items = ("один", "два", "три"), value = 'два')
    """
    idx: int = 0
    items_list: List[str]

    if items is None:
        items_list = []
    else:
        items_list = list(items)
        if isinstance(value, str):
            if value in items_list:
                idx = items_list.index(value)
        else:
            idx = value

    v, ok = QInputDialog.getItem(axipy.view_manager.global_parent, title, text, items_list, idx, editable)
    if ok:
        return v
    return None
