# -*- coding: utf-8 -*-

import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QDesktopWidget
from PyQt5.QtWidgets import QWidget, QTextEdit, QVBoxLayout, QMenu
from PyQt5.QtGui import QTextCursor
from PyQt5.QtCore import Qt


class Console(QTextEdit):
    def __init__(self, parent=None):
        super(Console, self).__init__(parent)

        self.promptText = '> '

        self.command = ''
        self.commandPosition = len(self.promptText)

        self.history = []
        self.historyPosition = -1
        self.editingCommand = ''

        self.bufferText = ''

        self.insertPlainText(self.promptText)

    def printConsole(self, text):
        self.insertPlainText(text)
        cursor = self.textCursor()
        self.commandPosition = cursor.position()

    def executeCommand(self, command):
        self.insertPlainText(command)
        return True

    def keyPressEvent(self, e):
        def isPrintable(key):
            printable = [
                Qt.Key_Space,
                Qt.Key_Exclam,
                Qt.Key_QuoteDbl,
                Qt.Key_NumberSign,
                Qt.Key_Dollar,
                Qt.Key_Percent,
                Qt.Key_Ampersand,
                Qt.Key_Apostrophe,
                Qt.Key_ParenLeft,
                Qt.Key_ParenRight,
                Qt.Key_Asterisk,
                Qt.Key_Plus,
                Qt.Key_Comma,
                Qt.Key_Minus,
                Qt.Key_Period,
                Qt.Key_Slash,
                Qt.Key_0,
                Qt.Key_1,
                Qt.Key_2,
                Qt.Key_3,
                Qt.Key_4,
                Qt.Key_5,
                Qt.Key_6,
                Qt.Key_7,
                Qt.Key_8,
                Qt.Key_9,
                Qt.Key_Colon,
                Qt.Key_Semicolon,
                Qt.Key_Less,
                Qt.Key_Equal,
                Qt.Key_Greater,
                Qt.Key_Question,
                Qt.Key_At,
                Qt.Key_A,
                Qt.Key_B,
                Qt.Key_C,
                Qt.Key_D,
                Qt.Key_E,
                Qt.Key_F,
                Qt.Key_G,
                Qt.Key_H,
                Qt.Key_I,
                Qt.Key_J,
                Qt.Key_K,
                Qt.Key_L,
                Qt.Key_M,
                Qt.Key_N,
                Qt.Key_O,
                Qt.Key_P,
                Qt.Key_Q,
                Qt.Key_R,
                Qt.Key_S,
                Qt.Key_T,
                Qt.Key_U,
                Qt.Key_V,
                Qt.Key_W,
                Qt.Key_X,
                Qt.Key_Y,
                Qt.Key_Z,
                Qt.Key_BracketLeft,
                Qt.Key_Backslash,
                Qt.Key_BracketRight,
                Qt.Key_AsciiCircum,
                Qt.Key_Underscore,
                Qt.Key_QuoteLeft,
                Qt.Key_BraceLeft,
                Qt.Key_Bar,
                Qt.Key_BraceRight,
                Qt.Key_AsciiTilde,
            ]

            if key in printable:
                return True
            else:
                return False

        def moveToPosition(pos, keepAnchor = False):
            if keepAnchor:
                m = QTextCursor.KeepAnchor
            else:
                m = QTextCursor.MoveAnchor

            cursor.setPosition(pos, m)
            self.setTextCursor(cursor)

        def moveToStartOfLine(keepAnchor = False):
            moveToPosition(self.commandPosition, keepAnchor)

        def moveToEndOfLine(keepAnchor = False):
            if keepAnchor:
                m = QTextCursor.KeepAnchor
            else:
                m = QTextCursor.MoveAnchor

            cursor.movePosition(QTextCursor.EndOfLine, m)
            self.setTextCursor(cursor)

        def moveToEnd():
            cursor.movePosition(QTextCursor.End)
            self.setTextCursor(cursor)

        def deleteToStartOfLine():
            moveToStartOfLine(True)
            self.bufferText = cursor.selectedText()
            cursor.removeSelectedText()

        def deleteToEndOfLine():
            moveToEndOfLine(True)
            self.bufferText = cursor.selectedText()
            cursor.removeSelectedText()

        def deleteLine():
            moveToStartOfLine()
            moveToEndOfLine(True)
            cursor.removeSelectedText()

        def deletePreviousWord():
            pos = cursor.position()

            moveToStartOfLine(True)

            i = cursor.selectedText().rstrip().rfind(' ')
            i = len(cursor.selectedText()) - i - 1
            if i < 0:
                i = 0

            moveToPosition(pos - i)
            moveToPosition(pos, True)
            self.bufferText = cursor.selectedText()
            cursor.removeSelectedText()

        def pasteBufferText():
            self.insertPlainText(self.bufferText)

        def deleteChar():
            cursor.deleteChar()

        def deletePreviousChar():
            if cursor.position() != self.commandPosition:
                cursor.deletePreviousChar()

        def moveRight():
            self.moveCursor(QTextCursor.Right)

        def moveLeft():
            if cursor.position() != self.commandPosition:
                self.moveCursor(QTextCursor.Left)

        def newCommand():
            moveToEnd()
            self.insertPlainText('\n')
            self.insertPlainText(self.promptText)
            self.commandPosition = cursor.position()
            self.editingCommand = ''

        def getCommand():
            moveToStartOfLine()
            moveToEndOfLine(True)
            command = cursor.selectedText()
            command = command.strip()
            moveToEnd()
            return command

        def addCommandHistory(command):
            if self.historyPosition < 0 or command != self.history[-1]:
                self.history.append(command)
                self.historyPosition = len(self.history)

        def enterCommand():
            command = getCommand()

            finished = True

            if command != '':
                addCommandHistory(command)
                self.insertPlainText('\n')
                finished = self.executeCommand(command)

            if finished:
                newCommand()

        def replaceCommand(command):
            deleteLine()
            self.insertPlainText(command)

        def previousCommand():
            if self.historyPosition < 0:
                return

            if self.historyPosition == len(self.history):
                command = getCommand()
                self.editingCommand = command
                self.historyPosition -= 1
            elif self.historyPosition > 0:
                self.historyPosition -= 1

            command = self.history[self.historyPosition]

            replaceCommand(command)

        def nextCommand():
            if self.historyPosition < 0:
                return

            if self.historyPosition < len(self.history):
                self.historyPosition += 1

            if self.historyPosition == len(self.history):
                command = self.editingCommand
            else:
                command = self.history[self.historyPosition]

            replaceCommand(command)

        cursor = self.textCursor()
        if cursor.position() < self.commandPosition:
            moveToEnd()

        mod = e.modifiers()
        control = mod & Qt.ControlModifier

        if isPrintable(e.key()):
            if control:
                if e.key() == Qt.Key_A:
                    moveToStartOfLine()

                elif e.key() == Qt.Key_B:
                    moveLeft()

                elif e.key() == Qt.Key_C:
                    newCommand()

                elif e.key() == Qt.Key_D:
                    deleteChar()

                elif e.key() == Qt.Key_E:
                    moveToEndOfLine()

                elif e.key() == Qt.Key_F:
                    moveRight()

                elif e.key() == Qt.Key_H:
                    deletePreviousChar()

                elif e.key() == Qt.Key_J:
                    enterCommand()

                elif e.key() == Qt.Key_K:
                    deleteToEndOfLine()

                elif e.key() == Qt.Key_M:
                    enterCommand()

                elif e.key() == Qt.Key_N:
                    nextCommand()

                elif e.key() == Qt.Key_P:
                    previousCommand()

                elif e.key() == Qt.Key_U:
                    deleteToStartOfLine()

                elif e.key() == Qt.Key_W:
                    deletePreviousWord()

                elif e.key() == Qt.Key_Y:
                    pasteBufferText()

            else:
                self.insertPlainText(e.text())

        elif e.key() == Qt.Key_Delete:
            deleteChar()

        elif e.key() == Qt.Key_Backspace:
            deletePreviousChar()

        elif e.key() == Qt.Key_Right:
            moveRight()

        elif e.key() == Qt.Key_Left:
            moveLeft()

        elif e.key() == Qt.Key_Up:
            previousCommand()

        elif e.key() == Qt.Key_Down:
            nextCommand()

        elif e.key() in [Qt.Key_Return, Qt.Key_Enter]:
            enterCommand()

    def contextMenuEvent(self, e):
        menu = QMenu(self)
        aCopy = menu.addAction('Copy')
        aPaste = menu.addAction('Paste')

        action = menu.exec_(self.mapToGlobal(e.pos()))

        if action == aCopy:
            self.copy()
        elif action == aPaste:
            cursor = self.textCursor()
            if cursor.position() < self.commandPosition:
                self.moveCursor(QTextCursor.End)
            self.paste()


if __name__ == '__main__':
    class Main(QMainWindow):
        def __init__(self):
            super().__init__()
            
            console = Console(self)
            
            self.setCentralWidget(console)

            self.setWindowTitle('console')
            self.resize(320, 240)
            self.centerOnScreen()
            self.show()

        def centerOnScreen(self):
            res = QDesktopWidget().screenGeometry()
            self.move((res.width()/2) - (self.frameSize().width()/2),
                      (res.height()/2) - (self.frameSize().height()/2))

    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

