"""
=========
BCIEditor
=========

"""

import os
import json
from typing import Literal, TypeVar

from PySide2.QtWidgets import QTextEdit, QCompleter
from PySide2.QtGui import QTextOption
from PySide2.QtCore import Qt

from .highlighters import PythonHighlighter, CSSHighlighter


AUTOCOMPLETER = TypeVar('Autocompleter')


########################################################################
class BCIEditor(QTextEdit):
    """Custom QTextEdit with autocompleter and linenumbers.

    Parameters
    ----------
    linenumber
        QTextEdit object that will be updated with linenumbers.
    extension
        To set the highlighter.
    """

    # ----------------------------------------------------------------------
    def __init__(self, linenumber: QTextEdit, extension: Literal['.py', '.css'] = '.py', *args, **kwargs):
        """"""
        super().__init__(*args, **kwargs)

        self.set_options()
        self.linenumber = linenumber

        if 'light' in os.environ.get('QTMATERIAL_THEME'):
            font_color = 'black'
        else:
            font_color = 'white'

        if json.loads(os.getenv('BCISTREAM_RASPAD')):
            font_size = 11
        else:
            font_size = 15

        self.setStyleSheet(f"""
        QTextEdit {{
        background-color: {os.environ.get('QTMATERIAL_SECONDARYDARKCOLOR')};
        color: {font_color};
        height: 18px;
        font-weight: normal;
        font-family: 'DejaVu Sans Mono';
        font-size: {font_size}px;
        line-height: {font_size}px;
        border: 1px solid {os.environ.get('QTMATERIAL_SECONDARYDARKCOLOR')};
        border-radius: 4px;
        height: 18px;
        padding: 0px;
        padding-top: 8px;
        }}
        """)

        if extension == '.py':
            PythonHighlighter(self.document())
        elif extension == '.css':
            CSSHighlighter(self.document())

        self.connect_()
        self.completer = None

    # ----------------------------------------------------------------------
    def connect_(self) -> None:
        """Update linenumber."""
        self.textChanged.connect(self.update_linenumber)

    # ----------------------------------------------------------------------
    def wheelEvent(self, evt):
        """Update the offset of the linenumber."""
        if self.linenumber:
            self.linenumber.wheelEvent(evt)
        return super().wheelEvent(evt)

    # ----------------------------------------------------------------------
    def update_linenumber(self) -> None:
        """Update linenumber."""
        lines = len(self.toPlainText().split('\n'))
        lines_ln = len(self.linenumber.toPlainText().split('\n'))

        if json.loads(os.getenv('BCISTREAM_RASPAD')):
            font_size = 11
        else:
            font_size = 15

        if lines != lines_ln:
            content = [f'{l}' for l in range(1, lines + 1)]
            self.linenumber.setHtml(
                f'<p style="text-align: right; line-height: {font_size}px; font-size: {font_size}px">{"<br>".join(content)}</p>')
        self.linenumber.verticalScrollBar().setValue(self.verticalScrollBar().value())

    # ----------------------------------------------------------------------
    def set_options(self) -> None:
        """Configure QTextEdit."""
        document = self.document()
        option = QTextOption()

        document.setDefaultTextOption(option)
        self.setAcceptRichText(False)
        self.setLineWrapMode(QTextEdit.LineWrapMode.NoWrap)

    # ----------------------------------------------------------------------
    def keyPressEvent(self, event):
        """Process key events."""

        completionPrefix = self.text_under_cursor()
        if self.completer and len(completionPrefix) < 3 and self.completer.popup().isVisible():
            self.completer.popup().hide()

        if event.key() in [Qt.Key_Tab, Qt.Key_Enter, Qt.Key_Enter - 1]:
            if self.completer and self.completer.popup().isVisible():
                self.completer.insertText.emit(
                    self.completer.last_highlighted)
                self.completer.popup().hide()
                return

        if event.key() == Qt.Key_Tab:
            tc = self.textCursor()
            tc.insertText(" " * 4)
            return

        # Manage {([ for seleceted text
        for text, *keys in (["({})", Qt.Key_ParenLeft, Qt.Key_ParenRight],
                            ["[{}]", Qt.Key_BracketLeft, Qt.Key_BracketRight],
                            ["{{{}}}", Qt.Key_BraceLeft, Qt.Key_BraceRight],
                            ):
            if event.key() in keys:
                tc = self.textCursor()
                start = tc.selectionStart()
                if selected := tc.selectedText():
                    tc.removeSelectedText()
                    tc.insertText(text.format(selected))
                    if event.key() == keys[0]:
                        tc.setPosition(start, tc.MoveAnchor)
                    self.setTextCursor(tc)
                    return

        # Manage quotation for selected text
        for text, *keys in (['"{}"', Qt.Key_QuoteDbl],
                            ["'{}'", Qt.Key_Apostrophe, ]
                            ):
            if event.key() in keys:
                tc = self.textCursor()
                start = tc.selectionStart()
                if selected := tc.selectedText():
                    tc.removeSelectedText()
                    tc.insertText(text.format(selected))
                    self.setTextCursor(tc)
                    return

        # Toggle comment
        if (event.key() == Qt.Key_Period) and bool(event.modifiers() & Qt.ControlModifier):
            tc = self.textCursor()
            # pos = tc.position()
            tc.select(tc.LineUnderCursor)
            line = tc.selectedText()

            if line.strip() and line.strip()[0] == '#':
                self._remove_symbol('# ')
            else:
                self._insert_symbol('# ')

        # Comment
        if (event.key() == Qt.Key_Slash) and bool(event.modifiers() & Qt.ControlModifier):
            return self._insert_symbol('# ')

        # Uncomment
        if (event.key() == Qt.Key_Question) and bool(event.modifiers() & Qt.ControlModifier):
            return self._remove_symbol('# ')

        # Unindent
        if (event.key() == Qt.Key_Less) and bool(event.modifiers() & Qt.ControlModifier):
            return self._remove_symbol('    ')

        # Indent
        if (event.key() == Qt.Key_Greater) and bool(event.modifiers() & Qt.ControlModifier):
            return self._insert_symbol('    ')

        # On enter
        if event.key() in [Qt.Key_Enter, Qt.Key_Enter - 1]:
            return self._on_enter()

        # On back space
        if event.key() == Qt.Key_Backspace:
            return self._on_back_space(event)

        super().keyPressEvent(event)

        if self.completer and event.text():
            completionPrefix = self.text_under_cursor()
            if len(completionPrefix) >= 3:
                self.show_completer(completionPrefix)
            elif self.completer.popup().isVisible():
                self.completer.popup().hide()

    # ----------------------------------------------------------------------
    def _on_back_space(self, event):
        """"""
        tc = self.textCursor()

        if tc.selectedText():
            return super().keyPressEvent(event)

        pos = tc.position()
        tc.movePosition(tc.StartOfLine, tc.KeepAnchor)
        previous = tc.selectedText()

        if previous[-4:] == '    ':
            if d := len(previous) % 4:
                previous = previous[:-d]
            else:
                previous = previous[:-4]
            tc.insertText(previous)
            return
        else:
            return super().keyPressEvent(event)

    # ----------------------------------------------------------------------
    def _on_enter(self):
        """"""
        tc = self.textCursor()
        pos = tc.position()
        tc.select(tc.LineUnderCursor)
        line = tc.selectedText()

        if line.isspace() or line == "":
            len_s = len(line)
        else:
            normal = line.replace(" ", "")
            len_s = line.find(normal[0])

        if line.strip().endswith(':'):
            len_s += 4

        tc.setPosition(pos)
        tc.insertText("\n" + " " * len_s)
        return

    # ----------------------------------------------------------------------
    def _remove_symbol(self, char='#'):
        """"""
        tc = self.textCursor()

        if tc.selectedText():
            start = tc.selectionStart()
            end = tc.selectionEnd()

            tc.setPosition(start, tc.MoveAnchor)
            tc.movePosition(tc.StartOfLine, tc.MoveAnchor)
            start = tc.position()

            tc.setPosition(end, tc.MoveAnchor)
            tc.movePosition(tc.EndOfLine, tc.MoveAnchor)
            end = tc.position()

            tc.setPosition(start, tc.MoveAnchor)
            tc.setPosition(end, tc.KeepAnchor)
            selected = tc.selectedText()
            tc.removeSelectedText()

            uncommented = []
            for line in selected.split('\u2029'):
                if line.strip():
                    start_ = line.find(line.replace(' ', '')[0])

                    if char.isspace():
                        uncommented.append(line.replace(f'{char}', '', 1))
                    else:
                        if line[start_:start_ + len(char)] == f'{char}':
                            uncommented.append(
                                line.replace(f'{char}', '', 1))
                        else:
                            tc.insertText(selected)
                            return

                    end -= len(char)
                else:
                    uncommented.append(line)

            if uncommented:
                tc.insertText('\u2029'.join(uncommented))
                tc.setPosition(start, tc.MoveAnchor)
                tc.setPosition(end, tc.KeepAnchor)
                self.setTextCursor(tc)
            else:
                tc.insertText(selected)

        else:
            tc.select(tc.LineUnderCursor)
            if line := tc.selectedText():
                tc.removeSelectedText()

                if char.isspace():
                    tc.insertText(line.replace(f'{char}', '', 1))
                else:
                    start = line.find(line.replace(' ', '')[0])
                    if line[start:start + len(char)] == f'{char}':
                        tc.insertText(line.replace(f'{char}', '', 1))
                    else:
                        tc.insertText(line)

    # ----------------------------------------------------------------------
    def _insert_symbol(self, char='#'):
        """"""
        tc = self.textCursor()

        if tc.selectedText():
            start = tc.selectionStart()
            end = tc.selectionEnd()

            tc.setPosition(start, tc.MoveAnchor)
            tc.movePosition(tc.StartOfLine, tc.MoveAnchor)
            start = tc.position()

            tc.setPosition(end, tc.MoveAnchor)
            tc.movePosition(tc.EndOfLine, tc.MoveAnchor)
            end = tc.position()

            tc.setPosition(start, tc.MoveAnchor)
            tc.setPosition(end, tc.KeepAnchor)
            selected = tc.selectedText()
            tc.removeSelectedText()

            commented = []
            for line in selected.split('\u2029'):
                if line.strip():
                    start_ = line.find(line.replace(' ', '')[0])
                    commented.append(
                        " " * start_ + f'{char}' + line[start_:])
                    end += len(char)
                else:
                    commented.append(line)
            tc.insertText('\u2029'.join(commented))
            tc.setPosition(start, tc.MoveAnchor)
            tc.setPosition(end, tc.KeepAnchor)
            self.setTextCursor(tc)

        else:
            tc.select(tc.LineUnderCursor)
            if line := tc.selectedText():
                if line.strip():
                    tc.removeSelectedText()
                    start = line.find(line.replace(' ', '')[0])
                    tc.insertText(" " * start + f'{char}' + line[start:])

    # ----------------------------------------------------------------------
    def set_completer(self, completer: AUTOCOMPLETER) -> None:
        """Update the autocompleter used."""
        if self.completer:
            self.disconnect(self.completer)
        if not completer:
            return

        completer.setWidget(self)
        completer.setCompletionMode(QCompleter.PopupCompletion)
        completer.setCaseSensitivity(Qt.CaseInsensitive)
        self.completer = completer
        self.completer.insertText.connect(self.insert_completion)

    # ----------------------------------------------------------------------
    def insert_completion(self, completion: str) -> None:
        """Process and inset the desired option."""
        tc = self.textCursor()
        self.text_under_cursor(tc)
        tc.removeSelectedText()
        pos = tc.position()

        if completion in self.completer.snippets:
            completion = self.completer.snippets[completion]

        extra = completion

        text_position = extra.find("[!]")
        extra = extra.replace("[!]", "")

        position_in_line = tc.positionInBlock()
        extra = extra.replace("\n", "\n" + " " * position_in_line)

        tc.insertText(extra)

        if text_position > 0:
            tc.setPosition(pos + text_position
                           + (4 * extra[:text_position].count('\n')))

        self.setTextCursor(tc)

    # ----------------------------------------------------------------------
    def text_under_cursor(self, tc=None) -> str:
        """Return de text under cursor."""

        if tc is None:
            tc = self.textCursor()

        # word like: cdc|
        tc.movePosition(tc.WordLeft, tc.KeepAnchor)

        # word like: cdc.|
        if tc.selectedText().startswith("."):
            tc.movePosition(tc.WordLeft, tc.KeepAnchor)

        # word like: cdc.pri|
        tc.movePosition(tc.WordLeft, tc.KeepAnchor)
        if tc.selectedText().startswith("."):
            tc.movePosition(tc.WordLeft, tc.KeepAnchor)
        else:
            tc.movePosition(tc.WordRight, tc.KeepAnchor)

        # tc.select(QTextCursor.WordUnderCursor)
        completionPrefix = tc.selectedText()

        if completionPrefix.endswith('.') and completionPrefix.count('.') > 1:
            index = completionPrefix.rfind(
                '.', 0, completionPrefix.rfind('.'))
            completionPrefix = completionPrefix[index + 1:]
            for _ in range(tc.selectedText().count('.') - 1):
                tc.movePosition(tc.WordRight, tc.KeepAnchor)
                tc.movePosition(tc.WordRight, tc.KeepAnchor)

        return completionPrefix

    # ----------------------------------------------------------------------
    def focusInEvent(self, event) -> None:
        """Keep the focus on the editor."""
        if self.completer:
            self.completer.setWidget(self)
        QTextEdit.focusInEvent(self, event)

    # ----------------------------------------------------------------------
    def show_completer(self, completion_prefix: str) -> None:
        """Show better options for current text."""
        self.completer.setCompletionPrefix(completion_prefix)
        popup = self.completer.popup()
        popup.setCurrentIndex(self.completer.completionModel().index(0, 0))
        cr = self.cursorRect()
        cr.setWidth(self.completer.popup().sizeHintForColumn(
            0) + self.completer.popup().verticalScrollBar().sizeHint().width() + 15)
        cr.setHeight(30)

        self.completer.complete(cr)
