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

import os.path
from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
from typing import Optional, cast

from cura.CuraApplication import CuraApplication  # Creating QML objects and managing packages.

from UM.Extension import Extension  # We are implementing the main object of an extension here.
from UM.PluginRegistry import PluginRegistry  # To find out where we are stored (the proper way).

from .RemotePackageList import RemotePackageList  # To register this type with QML.
from .LocalPackageList import LocalPackageList  # To register this type with QML.


class Marketplace(Extension, QObject):
    """
    The main managing object for the Marketplace plug-in.
    """
    def __init__(self, parent: Optional[QObject] = None) -> None:
        QObject.__init__(self, parent)
        Extension.__init__(self)
        self._window: Optional["QObject"] = None  # If the window has been loaded yet, it'll be cached in here.
        self._plugin_registry: Optional[PluginRegistry] = None
        self._package_manager = CuraApplication.getInstance().getPackageManager()

        self._material_package_list: Optional[RemotePackageList] = None
        self._plugin_package_list: Optional[RemotePackageList] = None

        # Not entirely the cleanest code, since the localPackage list also checks the server if there are updates
        # Since that in turn will trigger notifications to be shown, we do need to construct it here and make sure
        # that it checks for updates...
        preferences = CuraApplication.getInstance().getPreferences()
        preferences.addPreference("info/automatic_plugin_update_check", True)
        self._local_package_list = LocalPackageList(self)
        if preferences.getValue("info/automatic_plugin_update_check"):
            self._local_package_list.checkForUpdates(self._package_manager.local_packages)

        self._package_manager.installedPackagesChanged.connect(self.checkIfRestartNeeded)

        self._tab_shown: int = 0
        self._restart_needed = False

    def getTabShown(self) -> int:
        return self._tab_shown

    def setTabShown(self, tab_shown: int) -> None:
        if tab_shown != self._tab_shown:
            self._tab_shown = tab_shown
            self.tabShownChanged.emit()

    tabShownChanged = pyqtSignal()
    tabShown = pyqtProperty(int, fget=getTabShown, fset=setTabShown, notify=tabShownChanged)

    @pyqtProperty(QObject, constant=True)
    def MaterialPackageList(self):
        if self._material_package_list is None:
            self._material_package_list = RemotePackageList()
            self._material_package_list.packageTypeFilter = "material"

        return self._material_package_list

    @pyqtProperty(QObject, constant=True)
    def PluginPackageList(self):
        if self._plugin_package_list is None:
            self._plugin_package_list = RemotePackageList()
            self._plugin_package_list.packageTypeFilter = "plugin"
        return self._plugin_package_list

    @pyqtProperty(QObject, constant=True)
    def LocalPackageList(self):
        return self._local_package_list

    @pyqtSlot()
    def show(self) -> None:
        """
        Opens the window of the Marketplace.

        If the window hadn't been loaded yet into Qt, it will be created lazily.
        """
        if self._window is None:
            self._plugin_registry = PluginRegistry.getInstance()
            self._plugin_registry.pluginsEnabledOrDisabledChanged.connect(self.checkIfRestartNeeded)
            plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
            if plugin_path is None:
                plugin_path = os.path.dirname(__file__)
            path = os.path.join(plugin_path, "resources", "qml", "Marketplace.qml")
            self._window = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
        if self._window is None:  # Still None? Failed to load the QML then.
            return
        if not self._window.isVisible():
            self.setTabShown(0)
        self._window.show()
        self._window.requestActivate()  # Bring window into focus, if it was already open in the background.

    @pyqtSlot()
    def setVisibleTabToMaterials(self) -> None:
        """
        Set the tab shown to the remote materials one.
        Not implemented in a more generic way because it needs the ability to be called with 'callExtensionMethod'.
        """
        self.setTabShown(1)

    def checkIfRestartNeeded(self) -> None:
        if self._package_manager.hasPackagesToRemoveOrInstall or \
                cast(PluginRegistry, self._plugin_registry).getCurrentSessionActivationChangedPlugins():
            self._restart_needed = True
        else:
            self._restart_needed = False
        self.showRestartNotificationChanged.emit()

    showRestartNotificationChanged = pyqtSignal()

    @pyqtProperty(bool, notify=showRestartNotificationChanged)
    def showRestartNotification(self) -> bool:
        return self._restart_needed
