# Polychromatic is licensed under the GPLv3.
# Copyright (C) 2021-2024 Luke Horwell <code@horwell.me>
"""
This module controls the 'Background Tasks' window accessible from the menu bar.
"""

import time

from PyQt6.QtCore import QThread
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QDialog, QPushButton, QTreeWidget, QTreeWidgetItem

from .. import common, procpid
from ..base import PolychromaticBase
from . import shared


class ProcessViewer(PolychromaticBase):
    """
    The process viewer provides details into the procpid module implementation.
    Allows the user to view processes spawned by Polychromatic, stop them or
    restart them for diagnosis purposes.
    """
    def __init__(self, appdata):
        self.appdata = appdata
        self.widgets = shared.PolychromaticWidgets(appdata)

        # Session
        self.dialog_is_open = True

        # UI Controls
        self.dialog = shared.get_ui_widget(appdata, "procviewer", q_toplevel=QDialog)
        self.tree = self.dialog.findChild(QTreeWidget, "TasksTree")
        self.close = self.dialog.findChild(QPushButton, "Close")
        self.btn_refresh = self.dialog.findChild(QPushButton, "TasksRefresh")
        self.btn_reload = self.dialog.findChild(QPushButton, "TasksReloadAll")
        self.btn_stop = self.dialog.findChild(QPushButton, "TasksStopTask")

        # Set Dialog Button Icons
        if not self.appdata.system_qt_theme:
            self.close.setIcon(self.widgets.get_icon_qt("general", "close"))
            self.btn_refresh.setIcon(self.widgets.get_icon_qt("general", "refresh"))
            self.btn_reload.setIcon(self.widgets.get_icon_qt("general", "reset"))
            self.btn_stop.setIcon(self.widgets.get_icon_qt("general", "cancel"))

        # Connect signals when interacting with UI controls
        self.close.clicked.connect(self._close)
        self.tree.itemActivated.connect(self._change_item)
        self.tree.itemClicked.connect(self._change_item)
        self.btn_refresh.clicked.connect(self._refresh_list)
        self.btn_reload.clicked.connect(self._reload_all)
        self.btn_stop.clicked.connect(self._stop_task)

        # Showtime!
        self._refresh_list()
        self.tree.setColumnWidth(0, 250)
        self.tree.setColumnWidth(1, 300)
        self.tree.setColumnWidth(2, 50)
        self.dialog.exec()

    def _close(self):
        self.dialog_is_open = False
        self.dialog.deleteLater()

    def _change_item(self):
        self.btn_stop.setEnabled(True if self.tree.currentItem() else False)

    def _stop_task(self):
        """
        Gracefully interrupt the specified PID validating that it actually
        belongs to a Polychromatic process.
        """
        pid = int(self.tree.currentItem().text(2))
        component = self.tree.currentItem()._component

        self.dbg.stdout("Stopping process PID " + str(pid), self.dbg.action, 1)
        process = procpid.ProcessManager(component)
        process.stop()
        self._refresh_list()

    def _reload_all(self):
        """
        Stops all processes and initiates the autostart procedures.
        """
        set_cursor_busy = shared.TabData(self.appdata).set_cursor_busy
        set_cursor_normal = shared.TabData(self.appdata).set_cursor_normal


        class RebootThread(QThread):
            @staticmethod
            def run():
                # Gracefully stop using procpid module
                procmgr = procpid.ProcessManager("helper")
                components = procmgr._get_component_pid_list()
                for component in components:
                    process = procpid.ProcessManager(component)
                    self.dbg.stdout("Stopping process PID " + str(process._get_component_pid()), self.dbg.action, 1)
                    process.stop()

                # Before starting new processes, make sure old PIDs have ceased execution
                self.dbg.stdout("Checking all processes have interrupted...", self.dbg.action, 1)
                timeout = 3
                while len(procmgr._get_component_pid_list()) > 0 and timeout > 0:
                    time.sleep(1)
                    timeout = timeout - 1
                    self.dbg.stdout("Still waiting ({0}s timeout)".format(str(timeout)), self.dbg.debug, 1)

                # Reload all by using helper's --autostart
                self.dbg.stdout("Executing autostart...", self.dbg.action, 1)
                procmgr.start_component(["--autostart"])

                # Finished
                set_cursor_normal()
                if self.dialog_is_open:
                    self.btn_reload.setEnabled(True)
                    self.btn_reload.setText(self._("Restart All"))
                    time.sleep(1)
                    self._refresh_list()

                    # Auto refresh view again if tray applet is delayed
                    tray_delay = self.appdata.preferences["tray"]["autostart_delay"]
                    time.sleep(2 if tray_delay < 2 else tray_delay + 2)
                    self._refresh_list()

        self.dbg.stdout("Now reloading background processes...", self.dbg.warning, 1)
        self.btn_stop.setEnabled(False)
        self.btn_reload.setEnabled(False)
        self.btn_reload.setText(self._("Restarting..."))
        set_cursor_busy()

        self.tree.clear()
        item = QTreeWidgetItem()
        item.setText(0, self._("Please wait..."))
        item.setIcon(0, QIcon(common.get_icon("general", "exit")))
        item.setDisabled(True)
        self.tree.addTopLevelItem(item)

        self.thread = RebootThread()
        self.thread.start()

    def _refresh_list(self):
        procmgr = procpid.ProcessManager()
        components = procmgr._get_component_pid_list()
        device_list = self.middleman.get_devices()

        tree = self.dialog.findChild(QTreeWidget, "TasksTree")
        tree.clear()
        self.btn_stop.setEnabled(False)

        for component in components:
            procmgr = procpid.ProcessManager(component)
            pid = str(procmgr._get_component_pid())
            running = procmgr.is_another_instance_is_running()

            task = self._("Playing software effect").replace("[]", component)
            task_icon = common.get_icon("effects", "play")
            if component == "tray-applet":
                task = self._("Tray Applet")
                task_icon = common.get_icon("general", "tray-applet")
            elif not running:
                task = self._("No longer running")
                task_icon = common.get_icon("general", "cancel")

            # TODO: Refactor later
            running_device = None
            for device in device_list:
                if device.serial == component:
                    running_device = device

            item = QTreeWidgetItem()
            item.setText(0, task)
            item.setIcon(0, QIcon(task_icon))
            if running_device:
                item.setText(1, running_device.name)
                item.setIcon(1, QIcon(running_device.form_factor["icon"]))
            item.setText(2, pid)
            item._component = component

            if not running:
                item.setText(2, "-")
                item.setDisabled(True)

            tree.addTopLevelItem(item)
