#!/usr/bin/python3
import evdev
import os
import xml.etree.ElementTree as ET
from PyQt5 import QtWidgets, QtCore, QtGui, QtDBus, uic

try:
    from ..ui import config
    from ..__id__ import ID
except ImportError:
    from __id__ import ID

UDEV_FILE = "/etc/udev/rules.d/99-gamepads.rules"
LOCAL_DIR = os.path.dirname(os.path.realpath(__file__))


class InputMonitor(QtCore.QObject):
    hotkey = QtCore.pyqtSignal(str)
    status = QtCore.pyqtSignal(str, str)
    online = QtCore.pyqtSignal(bool)

    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.lastKeys = []
        self.device = ""
        self.holdTimer = QtCore.QTimer(interval=200, singleShot=True)
        self.holdTimer.timeout.connect(self._sendKeys)
        self.statusTimer = QtCore.QTimer(interval=100)
        self.statusTimer.timeout.connect(self._status)

    def _sendKeys(self):
        keys = None
        try:
            dev = evdev.InputDevice(self.device)
            keys = dev.active_keys()
            if keys:
                keys = str(keys)[1:-1]
                self.hotkey.emit(keys)
        except OSError:
            pass

    def _status(self):
        try:
            dev = evdev.InputDevice(self.device)
            keys = dev.active_keys()
            if self.lastKeys != keys:
                self.lastKeys = keys
                if keys:
                    self.holdTimer.start()
            online = True
        except OSError:
            online = False

        if not self.device:
            msg = "No device selected"
            color = ""
        elif not online:
            msg = "Selected device is not connected or has no symlink"
            color = "red"
        else:
            msg = "Monitoring..."
            color = "green"

        self.online.emit(online)
        self.status.emit(msg, color)


class Main(QtWidgets.QMainWindow):
    disconnect = QtCore.pyqtSignal(str)

    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.preferences = parent.preferences
        self.process = QtCore.QProcess()
        self._uiInit()

        self.worker = InputMonitor(self)
        self.workerThread = QtCore.QThread()
        self.worker.moveToThread(self.workerThread)
        self.worker.hotkey.connect(self.hotkeyUpdate)
        self.worker.status.connect(self.statusUpdate)
        self.worker.online.connect(self.ui.hotkeyLine.setEnabled)
        self.workerThread.start()

        if not self.preferences.db["devices"]:
            self.show()

    def hideEvent(self, event):
        self.ui.hotkeyLine.clear()
        self.worker.statusTimer.stop()
        self.worker.holdTimer.stop()
        self.worker.device = ""

    def showEvent(self, event):
        self.ui.statusBar.setStyleSheet("QStatusBar::item { border: 0 }")
        self.ui.statusRightLabel.clear()
        self.worker.statusTimer.start()
        self._settingsLoad()

    def _cancel(self):
        self.hide()

    def _devGet(self):
        service = "org.bluez"
        root = "/org/bluez"
        interface = "org.freedesktop.DBus.Properties"
        bus = QtDBus.QDBusConnection.systemBus()
        nodes = self._devGetNodes()

        # Fill the tree with all trusted bluetooth devices
        self.ui.devicesTree.clear()
        for hci in nodes:
            for node in nodes[hci]:
                path = f"{root}/{hci}/{node}"
                connection = QtDBus.QDBusInterface(service, path, interface, bus)
                name = connection.call("Get", "org.bluez.Device1", "Name").arguments()[0]
                address = connection.call("Get", "org.bluez.Device1", "Address").arguments()[0]
                basename = os.path.basename(path)
                symlink = f"/dev/input/{basename}"
                if path in self.preferences.db["devices"]:
                    hotkey = self.preferences.db["devices"][path]["hotkey"]
                else:
                    hotkey = ""
                yield name, address, hotkey, path, symlink

    def _devGetNodes(self):
        service = "org.bluez"
        path = "/org/bluez"
        interface = "org.freedesktop.DBus.Introspectable"
        bus = QtDBus.QDBusConnection.systemBus()
        nodes = {}
        if bus.interface().isServiceRegistered(service).value():
            connection = QtDBus.QDBusInterface(service, path, interface, bus)
            hciNodes = ET.fromstring(connection.call("Introspect").arguments()[0])
            for node in hciNodes.iter("node"):
                if node.attrib:
                    hci = node.attrib["name"]
                    connection = QtDBus.QDBusInterface(service, f"{path}/{hci}", interface, bus)
                    devNodes = ET.fromstring(connection.call("Introspect").arguments()[0])
                    nodes[hci] = []
                    for device in devNodes.iter("node"):
                        if device.attrib:
                            nodes[hci].append(device.attrib["name"])
        return nodes

    def _devLoad(self):
        for dev in self._devGet():
            name, address, hotkey, path, symlink = dev
            item = QtWidgets.QTreeWidgetItem()
            item.setText(0, name)
            item.setText(1, address)
            item.setText(2, hotkey)
            item.setText(3, path)
            item.setText(4, symlink)
            if path not in self.preferences.db["devices"]:
                item.setCheckState(0, QtCore.Qt.Unchecked)
            self.ui.devicesTree.addTopLevelItem(item)

    def _devSave(self):
        self.preferences.db["devices"] = {}
        for item in range(self.ui.devicesTree.topLevelItemCount()):
            item = self.ui.devicesTree.topLevelItem(item)
            if bool(item.checkState(0)):
                hotkey = item.text(2)
                path = item.text(3)
                symlink = item.text(4)
                self.preferences.db["devices"][path] = {"hotkey": hotkey, "symlink": symlink}

    def _devSort(self):
        # Put selected devices in the last saved order, on top of the list
        self.ui.devicesTree.sortItems(0, QtCore.Qt.AscendingOrder)
        for i, path in enumerate(self.preferences.db["devices"]):
            for item in range(self.ui.devicesTree.topLevelItemCount()):
                item = self.ui.devicesTree.topLevelItem(item)
                if path == item.text(3):
                    index = self.ui.devicesTree.indexOfTopLevelItem(item)
                    item = self.ui.devicesTree.takeTopLevelItem(index)
                    item.setCheckState(0, QtCore.Qt.Checked)
                    self.ui.devicesTree.insertTopLevelItem(i, item)

    def _disconnectAll(self):
        for item in range(self.ui.devicesTree.topLevelItemCount()):
            item = self.ui.devicesTree.topLevelItem(item)
            path = item.text(3)
            self.disconnect.emit(path)

    def _itemChanged(self, item, column):
        if bool(item.checkState(0)):
            self.ui.devicesTree.setCurrentItem(item)

    def _ok(self):
        self._settingsSave()
        self.hide()

    def _symlinksSave(self):
        rules = self._symlinksGetRules()
        script = f"/tmp/{ID}_rules.sh"
        with open(script, 'w') as f:
            f.write(f"echo '# {script}'\n")
            f.write(f"echo -e 'Overwriting {UDEV_FILE}...\\n'\n")
            f.write("sudo -s <<EOF\n")
            f.write(f"echo '# {UDEV_FILE}\n' > {UDEV_FILE}\n")
            for rule in rules.splitlines():
                f.write(f"echo '{rule}'>> {UDEV_FILE}\n")
            f.write("udevadm control --reload-rules\n")
            f.write("EOF\n")

        os.chmod(script, 0o775)
        self.process.start("xterm -e " + script)
        self.process.write(b"exit\n")
        self.process.waitForFinished(-1)
        self._disconnectAll()
        os.remove(script)

    def _symlinksGetRules(self):
        rules = ""
        for item in range(self.ui.devicesTree.topLevelItemCount()):
            item = self.ui.devicesTree.topLevelItem(item)
            if bool(item.checkState(0)):
                symlink = item.text(4)
                name = os.path.basename(symlink)
                mac = name[4:].replace("_", ":").lower()
                rule = f'SUBSYSTEM=="input", ENV{{ID_INPUT_JOYSTICK}}=="1", ATTRS{{uniq}}=="{mac}", SYMLINK+="input/{name}"'
                rules += f"{rule}\n"
        return rules

    def _selectionChanged(self, current, previous):
        if previous:
            self.ui.devicesTree.itemChanged.disconnect()
            self._setBold(previous, False)
            self.ui.devicesTree.itemChanged.connect(self._itemChanged)

        if current:
            name = current.text(0)
            mac = current.text(1)
            hotkey = current.text(2)
            dev = current.text(4)
            self.worker.device = dev
            self.ui.hotkeyLine.setText(hotkey)
            self.ui.statusRightLabel.setText(f"{name}, {mac}")
            self._setBold(current, True)

    def _setBold(self, item, state):
        font = item.font(0)
        for col in range(self.ui.devicesTree.columnCount()):
            font.setBold(state)
            item.setFont(col, font)

    def _settingsLoad(self):
        self._devLoad()
        self._devSort()
        self.ui.disconnectBox.setChecked(self.preferences.db["behavior"]["disconnect on exit"])
        self.ui.execBeforeLine.setText(self.preferences.db["behavior"]["before frontend"])
        self.ui.execAfterLine.setText(self.preferences.db["behavior"]["after frontend"])
        self.ui.holdDelayBox.setValue(self.preferences.db["behavior"]["hold delay"])
        self.ui.frontendLine.setText(self.preferences.db["process"]["frontend"])
        self.ui.emulatorsLine.setText(",".join(self.preferences.db["process"]["emulators"]))

    def _settingsSave(self):
        self._devSave()
        self.preferences.db["behavior"]["disconnect on exit"] = self.ui.disconnectBox.isChecked()
        self.preferences.db["behavior"]["before frontend"] = self.ui.execBeforeLine.text()
        self.preferences.db["behavior"]["after frontend"] = self.ui.execAfterLine.text()
        self.preferences.db["behavior"]["hold delay"] = self.ui.holdDelayBox.value()
        self.preferences.db["process"]["frontend"] = self.ui.frontendLine.text()
        self.preferences.db["process"]["emulators"] = self.ui.emulatorsLine.text().split(",")
        self.preferences.save()

    def _uiInit(self):
        try:
            self.ui = config.Ui_MainWindow()
            self.ui.setupUi(self)
        except NameError:
            self.ui = uic.loadUi(f"{LOCAL_DIR}/../ui/config.ui", self)

        self.ui.statusRightLabel = QtWidgets.QLabel()
        self.ui.statusBar = QtWidgets.QStatusBar()
        self.ui.statusBar.addPermanentWidget(self.ui.statusRightLabel)
        self.ui.statusBar.setStyleSheet("QStatusBar::item { border: 0 }")
        self.setStatusBar(self.ui.statusBar)

        self.ui.buttonBox.button(QtWidgets.QDialogButtonBox.Cancel).clicked.connect(self._cancel)
        self.ui.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect(self._ok)
        self.ui.udevButton.clicked.connect(self._symlinksSave)

        self.ui.devicesTree.header().setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
        self.ui.devicesTree.currentItemChanged.connect(self._selectionChanged)
        self.ui.devicesTree.itemChanged.connect(self._itemChanged)

    @QtCore.pyqtSlot(str)
    def hotkeyUpdate(self, keys):
        self.ui.hotkeyLine.setText(keys)
        current = self.ui.devicesTree.currentItem()
        if current:
            current.setText(2, keys)

    @QtCore.pyqtSlot(str, str)
    def statusUpdate(self, msg, color):
        if color == "green":
            bg = QtGui.QColor("#C6EFCE")
            fg = QtGui.QColor("#004000")
        elif color == "red":
            bg = QtGui.QColor("#FFC7CE")
            fg = QtGui.QColor("#9C0006")

        if color:
            palette = self.ui.statusBar.palette()
            palette.setColor(QtGui.QPalette.Window, bg)
            palette.setColor(QtGui.QPalette.WindowText, fg)
            self.ui.statusBar.setPalette(palette)
        self.ui.statusBar.showMessage(msg)
