import pyperclip
import time
import string
from pynput import keyboard
from PyQt5 import QtWidgets, QtGui, QtCore

delay = 100
first_char_delay = 2000
shift_delay = 100
SETTINGS_FILE = 'settings.txt'


class SystemTrayIcon(QtWidgets.QSystemTrayIcon):

    def init(self, icon, parent=None):
        super().init(icon, parent)
        self.menu = QtWidgets.QMenu()
        self.settings_action = self.menu.addAction('Settings')
        self.exit_action = self.menu.addAction('Exit')
        self.setContextMenu(self.menu)
        self.settings_action.triggered.connect(self.open_settings)
        self.exit_action.triggered.connect(self.close_program)
        self.settings_dialog = None
        self.worker = Worker()
        self.worker.start()
        self.worker.clipboard_changed.connect(self.handle_clipboard_change)
        self.is_typing = False
        self.setup_keyboard_listener()

    def setup_keyboard_listener(self):

        def for_canonical(f):
            return lambda k: f(self.l.canonical(k))

        self.hotkeys = {
            frozenset([keyboard.Key.shift, keyboard.Key.alt, keyboard.Key.ctrl]): self.type_copied_text
        }
        self.current_keys = set()
        self.l = keyboard.Listener(
            on_press=for_canonical(self.press_key),
            on_release=for_canonical(self.release_key),
        )
        self.l.start()

    def press_key(self, key):
        try:
            if any([key in self.current_keys for key in self.hotkeys.keys()]):
                return

            self.current_keys.add(key)

            for hotkey in self.hotkeys.keys():

                if self.current_keys == hotkey:
                    self.hotkeyshotkey

        except Exception as e:
            print(str(e))
            pass

    def release_key(self, key):
        try:
            self.current_keys.remove(key)
        except KeyError as e:
            print(str(e))
            pass

    def type_copied_text(self):
        new_clipboard_content = pyperclip.paste().strip()
        if new_clipboard_content:
            time.sleep(first_char_delay / 1000)
            keyboard_controller = keyboard.Controller()
            shift_chars = string.ascii_uppercase + ''.join(
                c for c in string.printable if not c.isalnum()
            )
            for char in new_clipboard_content:
                if char in string.printable:
                    if char in shift_chars:
                        keyboard_controller.press(keyboard.Key.shift)
                        time.sleep(shift_delay / 1000)
                    keyboard_controller.press(char)
                    keyboard_controller.release(char)
                    if char in shift_chars:
                        time.sleep(shift_delay / 1000)
                        keyboard_controller.release(keyboard.Key.shift)
                    time.sleep(delay / 1000)

    def handle_clipboard_change(self, text):
        self.worker.clipboard_content = text

    def open_settings(self):
        if not self.settings_dialog:
            self.settings_dialog = SettingsDialog()
        self.settings_dialog.show()

    def close_program(self):
        self.l.stop()
        QtWidgets.QApplication.quit()


class Worker(QtCore.QThread):
    clipboard_changed = QtCore.pyqtSignal(str)

    def init(self, parent=None):
        super().init(parent)
        self.clipboard_content = pyperclip.paste()

    def run(self):
        while True:
            new_clipboard_content = pyperclip.paste()
            if new_clipboard_content != self.clipboard_content:
                self.clipboard_content = new_clipboard_content
                self.clipboard_changed.emit(new_clipboard_content)
            time.sleep(0.1)


class SettingsDialog(QtWidgets.QDialog):
    def init(self, parent=None):
        super().init(parent)
        self.setWindowTitle('Settings')
        self.setFixedSize(400, 300)
        self.delay_label = QtWidgets.QLabel('Delay between characters (ms):')
        self.delay_spinbox = QtWidgets.QSpinBox()
        self.delay_spinbox.setMinimum(0)
        self.delay_spinbox.setMaximum(10000)
        self.delay_spinbox.setValue(delay)
        self.first_char_delay_label = QtWidgets.QLabel('Delay before first character (ms):')
        self.first_char_delay_spinbox = QtWidgets.QSpinBox()
        self.first_char_delay_spinbox.setMinimum(0)
        self.first_char_delay_spinbox.setMaximum(10000)
        self.first_char_delay_spinbox.setValue(first_char_delay)
        self.shift_delay_label = QtWidgets.QLabel('Shift key delay (ms):')
        self.shift_delay_spinbox = QtWidgets.QSpinBox()
        self.shift_delay_spinbox.setMinimum(0)
        self.shift_delay_spinbox.setMaximum(1000)
        self.shift_delay_spinbox.setValue(shift_delay)
        self.save_button = QtWidgets.QPushButton('Save')
        self.cancel_button = QtWidgets.QPushButton('Cancel')
        self.save_button.clicked.connect(self.save_settings)
        self.cancel_button.clicked.connect(self.reject)
        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(self.delay_label)
        layout.addWidget(self.delay_spinbox)
        layout.addWidget(self.first_char_delay_label)
        layout.addWidget(self.first_char_delay_spinbox)
        layout.addWidget(self.shift_delay_label)
        layout.addWidget(self.shift_delay_spinbox)
        buttons_layout = QtWidgets.QHBoxLayout()
        buttons_layout.addWidget(self.save_button)
        buttons_layout.addWidget(self.cancel_button)
        layout.addLayout(buttons_layout)
        self.setLayout(layout)

    def save_settings(self):
        global delay, first_char_delay, shift_delay
        delay = self.delay_spinbox.value()
        first_char_delay = self.first_char_delay_spinbox.value()
        shift_delay = self.shift_delay_spinbox.value()
        with open(SETTINGS_FILE, 'w') as f:
            f.write(f"{delay}\n{first_char_delay}\n{shift_delay}")
        self.accept()


def main():
    app = QtWidgets.QApplication([])
    app.setQuitOnLastWindowClosed(False)
    tray_icon = SystemTrayIcon(QtGui.QIcon('icon.png'))
    tray_icon.show()
    try:
        with open(SETTINGS_FILE, 'r') as f:
            lines = f.read().splitlines()
            delay = int(lines[0])
            first_char_delay = int(lines[1])
            shift_delay = int(lines[2])
    except (FileNotFoundError, ValueError):
        pass
    app.exec_()


if __name__ == '__main__':
    main()