# Copyright (c) 2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
# Modified by BigRep GmbH 2024

# The MachineListModel is used to display the connected printers in the interface. Both the abstract machines and all
# online cloud connected printers are represented within this ListModel. Additional information such as the number of
# connected printers for each printer type is gathered.

from typing import Optional, List, cast

from PyQt6.QtCore import Qt, QTimer, QObject, pyqtSlot, pyqtProperty, pyqtSignal
from PyQt6.QtQml import QQmlEngine

from UM.Qt.ListModel import ListModel
from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.Interfaces import ContainerInterface
from UM.i18n import i18nCatalog
from UM.Util import parseBool

from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
from cura.Settings.GlobalStack import GlobalStack

class MachineTypeListModelA(ListModel):
    NameRole = Qt.ItemDataRole.UserRole + 1
    IdRole = Qt.ItemDataRole.UserRole + 2
    MachineType = Qt.ItemDataRole.UserRole + 3
    HasRemoteConnectionRole = Qt.ItemDataRole.UserRole + 4
    MetaDataRole = Qt.ItemDataRole.UserRole + 5
    IsOnlineRole = Qt.ItemDataRole.UserRole + 6
    MachineCountRole = Qt.ItemDataRole.UserRole + 7
    IsAbstractMachineRole = Qt.ItemDataRole.UserRole + 8
    ComponentTypeRole = Qt.ItemDataRole.UserRole + 9
    IsNetworkedMachineRole = Qt.ItemDataRole.UserRole + 10
    MachineIconPathRole = Qt.ItemDataRole.UserRole + 11

    def __init__(self, parent = None):
        super().__init__(parent)
        QQmlEngine.setObjectOwnership(self, QQmlEngine.ObjectOwnership.CppOwnership)
    
        self.addRoleName(self.NameRole, "name")
        self.addRoleName(self.IdRole, "id")
        self.addRoleName(self.MachineType, "machineType")
        self.addRoleName(self.HasRemoteConnectionRole, "hasRemoteConnection")
        self.addRoleName(self.MetaDataRole, "metadata")
        self.addRoleName(self.IsOnlineRole, "isOnline")
        self.addRoleName(self.MachineCountRole, "machineCount")
        self.addRoleName(self.IsAbstractMachineRole, "isAbstractMachine")
        self.addRoleName(self.ComponentTypeRole, "componentType")
        self.addRoleName(self.IsNetworkedMachineRole, "isNetworked")
        self.addRoleName(self.MachineIconPathRole, "iconName")

    def addItem(self, container_stack: ContainerStack, is_online: bool, machine_count: int = 0) -> None:
        if parseBool(container_stack.getMetaDataEntry("hidden", False)):
            return
        
        item = {
            "componentType": "MACHINE",
            "name": container_stack.getName() if container_stack.getName() != "" else container_stack.getMetaData().get("serial_number", ""),
            "id": container_stack.getId(),
            "machineType": container_stack.definition.name,
            "metadata": container_stack.getMetaData().copy(),
            "isOnline": is_online,
            "isAbstractMachine": parseBool(container_stack.getMetaDataEntry("is_abstract_machine", False)),
            "iconName": container_stack.getMetaDataEntry("icon", None),
            "isNetworked": cast(GlobalStack, container_stack).hasNetworkedConnection() if isinstance(container_stack, GlobalStack) else False,
            "machineCount": machine_count,
            "catergory": "connected" if is_online else "other",
        }

        self.appendItem(item)

class MachineTypeListModel(ListModel):

    def __init__(self, parent: Optional[QObject] = None, listenToChanges: bool = True) -> None:
        super().__init__(parent)
        QQmlEngine.setObjectOwnership(self, QQmlEngine.ObjectOwnership.CppOwnership)

        self.addRoleName(Qt.ItemDataRole.UserRole + 1, "machineType")
        self.addRoleName(Qt.ItemDataRole.UserRole + 2, "iconName")
        self.addRoleName(Qt.ItemDataRole.UserRole + 3, "machines")

        self._update()

        self._show_cloud_printers = False

        self._catalog = i18nCatalog("cura")

        self._change_timer = QTimer()
        self._change_timer.setInterval(200)
        self._change_timer.setSingleShot(True)
        self._change_timer.timeout.connect(self._update)

        if listenToChanges:
            CuraContainerRegistry.getInstance().containerAdded.connect(self._onContainerChanged)
            CuraContainerRegistry.getInstance().containerMetaDataChanged.connect(self._onContainerChanged)
            CuraContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChanged)
            self._updateDelayed()

    showCloudPrintersChanged = pyqtSignal(bool)

    @pyqtProperty(bool, notify=showCloudPrintersChanged)
    def showCloudPrinters(self) -> bool:
        return self._show_cloud_printers

    @pyqtSlot(bool) 
    def setShowCloudPrinters(self, show_cloud_printers: bool) -> None:
        self._show_cloud_printers = show_cloud_printers
        self._updateDelayed()
        self.showCloudPrintersChanged.emit(show_cloud_printers)

    def _onContainerChanged(self, container: ContainerInterface) -> None:
        """Handler for container added/removed events from registry"""

        # We only need to update when the added / removed container GlobalStack
        if isinstance(container, GlobalStack):
            self._updateDelayed()

    def _updateDelayed(self) -> None:
        self._change_timer.start()

    def _getMachineStacks(self) -> List[ContainerStack]:
        return CuraContainerRegistry.getInstance().findContainerStacks(type = "machine")

    def _getAbstractMachineStacks(self) -> List[ContainerStack]:
        return CuraContainerRegistry.getInstance().findContainerStacks(is_abstract_machine = "True")

    def set_machines_filter(self, machines_filter: Optional[List[GlobalStack]]) -> None:
        self._machines_filter = machines_filter
        self._update()

    def _update(self) -> None:
        self.clear()

        machine_stacks = self._getMachineStacks()

        machine_stacks_dict = {machine_stack.definition.name: [other for other in machine_stacks if other.definition.id == machine_stack.definition.id] 
               for machine_stack in machine_stacks}
        
        machine_type_item_list = []
        
        for machine_type, machine_stacks_of_type in machine_stacks_dict.items():

            machine_type_item = {
                "machineType": machine_type,
                "iconName": machine_stacks_of_type[0].getMetaDataEntry("icon", None),
                "machines": MachineTypeListModelA()
            }

            machine_stacks_of_type_sorted = sorted(machine_stacks_of_type, key=lambda item: item.name)
            for machine_stack_of_type in machine_stacks_of_type_sorted:
                machine_type_item["machines"].addItem(machine_stack_of_type, False)
            
            machine_type_item_list.append(machine_type_item)

        # Sort brand by name
        machine_type_item_list = sorted(machine_type_item_list, key = lambda x: x["machineType"].upper())
        self.setItems(machine_type_item_list)