#     Dodo - A graphical, hackable email client based on notmuch
#     Copyright (C) 2021 - Aleks Kissinger
#
# This file is part of Dodo
#
# Dodo is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Dodo is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Dodo. If not, see <https://www.gnu.org/licenses/>.

from __future__ import annotations
import subprocess
from typing import Dict, List, Tuple, Optional, Callable, Any
from PyQt6.QtWidgets import *
from PyQt6.QtGui import QKeyEvent
from PyQt6 import QtCore

from . import app
from . import util
from . import keymap
from . import search
from . import thread

class CommandBar(QLineEdit):
    """A command bar that appears on the bottom of the screen when searching
    or tagging."""

    def __init__(self, a: app.Dodo, label: QLabel, parent: QWidget):
        super().__init__(parent)
        self.app = a
        self.label = label
        self.mode = ''
        self.history: Dict[str, Tuple[int, List[str]]] = {}
        self.callback: Optional[Callable[[str], Any]] = None

        self.completer = self._get_completer()

    def _get_completer(self):
        """Prepare the completer for tags."""
        r = subprocess.run(['notmuch', 'search', '--output=tags', '*'], stdout=subprocess.PIPE)
        tags = r.stdout.decode('utf-8').splitlines()
        completer = QCompleter(tags, self)
        completer.setCaseSensitivity(QtCore.Qt.CaseSensitivity.CaseInsensitive)
        completer.setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
        completer.setFilterMode(QtCore.Qt.MatchFlag.MatchContains)
        completer.setModelSorting(QCompleter.ModelSorting.UnsortedModel)
        completer.setWidget(self)
        completer.activated.connect(self.handleCompletion)

        self.textChanged.connect(self.handleTextChanged)
        return completer

    def handleTextChanged(self, text: str):
        """Open suggestion dialog if a matching tag is present."""
        prefix = text.rsplit(sep=" ", maxsplit=1)[-1]
        if len(prefix) == 0:
            return
        elif prefix[0] in ["+", "-"]:
            prefix = prefix[1:]
        elif prefix[:4] == "tag:":
            prefix = prefix[4:]
        else:
            prefix = ""
        if len(prefix) > 0:
            self.completer.setCompletionPrefix(prefix)

            popup = self.completer.popup()
            popup.setCurrentIndex(self.completer.completionModel().index(0, 0))
            self.completer.complete()

    def handleCompletion(self, text):
        """Use the choosen tag."""
        prefix = self.completer.completionPrefix()
        self.setText(self.text()[:-len(prefix)] + text + " ")

    def open(self, mode: str, callback: Callable[[str], Any]) -> None:
        """Open the command bar and give it focus

        This method sets the `command_area` QWidget (which contains the command bar and
        its label) to be visible, and sets the `command_label` to be equal to `mode`.

        :param mode: a string used to set the label next to the command bar and
                     keep a unique command history (e.g. 'search' history should be
                     different from 'tag' history).
        :param callback: a function called with the user input to run the command
        """

        self.mode = mode
        self.callback = callback
        self.label.setText(mode)

        p = self.parent()
        if isinstance(p, QWidget): p.setVisible(True)

        self.setFocus()

    def close_bar(self) -> None:
        """Clear the command and close

        Call this method by itself to cancel the command and close the bar. Note we use
        `close_bar` to avoid a name clash with QWidget.close."""

        if self.mode in self.history:
            _, h = self.history[self.mode]
            self.history[self.mode] = (len(h), h)

        self.setText('')
        p = self.parent()
        if isinstance(p, QWidget): p.setVisible(False)

        w = self.app.tabs.currentWidget()
        if w: w.setFocus()

    def accept(self) -> None:
        """Apply the command typed into the command bar and close

        After the command has been applied, this method saves the command to the command
        history associated with the current mode, then calls :func:`close_bar` to clear
        the command and close the command bar."""

        if self.completer.popup().isVisible():
            return

        if self.callback:
            self.callback(self.text())

        if self.mode in self.history:
            pos, h = self.history[self.mode]
            h.append(self.text())
            self.history[self.mode] = (pos + 1, h)
        else:
            self.history[self.mode] = (1, [self.text()])

        self.close_bar()

    def history_previous(self) -> None:
        """Cycle to the previous command in the command history

        Note a separate history is kept for each mode."""

        if self.mode in self.history:
            pos, h = self.history[self.mode]
            if len(h) != 0:
                pos = max(pos - 1, 0)
                self.history[self.mode] = (pos, h)
                self.setText(h[pos])

    def history_next(self) -> None:
        """Cycle to the next command in the command history

        Note a separate history is kept for each mode."""

        if self.mode in self.history:
            pos, h = self.history[self.mode]
            if len(h) != 0:
                pos = min(pos + 1, len(h) - 1)
                self.history[self.mode] = (pos, h)
                self.setText(h[pos])

    def keyPressEvent(self, e: QKeyEvent) -> None:
        """Process keyboard input while the command bar is in focus.

        Translate the key event into a string with :func:`~dodo.util.key_string`
        and check if it is in :func:`~dodo.keymap.command_bar_keymap`. If it is,
        fire the associated function. Otherwise, pass the event on to the text
        box.

        Note: Key chords are NOT supported in the command bar.
        """
        if self.completer.popup().isVisible() and e.key() in [
            QtCore.Qt.Key.Key_Enter,
            QtCore.Qt.Key.Key_Return,
            QtCore.Qt.Key.Key_Up,
            QtCore.Qt.Key.Key_Down,
            QtCore.Qt.Key.Key_Tab,
            QtCore.Qt.Key.Key_Backtab,
            QtCore.Qt.Key.Key_Escape,
        ]:
            # ingnore keymaps if the popup is shown!
            e.ignore()
            return

        k = util.key_string(e)
        if k in keymap.command_bar_keymap:
            keymap.command_bar_keymap[k][1](self)
        else:
            super().keyPressEvent(e)
