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

import collections

from PyQt5.QtCore import pyqtProperty, pyqtSignal, Qt

import UM.Logger
import UM.Qt
from UM.Application import Application
from UM.Settings.ContainerRegistry import ContainerRegistry
import os

from UM.i18n import i18nCatalog


class QualitySettingsModel(UM.Qt.ListModel.ListModel):
    KeyRole = Qt.UserRole + 1
    LabelRole = Qt.UserRole + 2
    UnitRole = Qt.UserRole + 3
    ProfileValueRole = Qt.UserRole + 4
    ProfileValueSourceRole = Qt.UserRole + 5
    UserValueRole = Qt.UserRole + 6
    CategoryRole = Qt.UserRole + 7

    def __init__(self, parent = None):
        super().__init__(parent = parent)

        self._container_registry = ContainerRegistry.getInstance()

        self._extruder_id = None
        self._extruder_definition_id = None
        self._quality_id = None
        self._material_id = None
        self._i18n_catalog = None

        self.addRoleName(self.KeyRole, "key")
        self.addRoleName(self.LabelRole, "label")
        self.addRoleName(self.UnitRole, "unit")
        self.addRoleName(self.ProfileValueRole, "profile_value")
        self.addRoleName(self.ProfileValueSourceRole, "profile_value_source")
        self.addRoleName(self.UserValueRole, "user_value")
        self.addRoleName(self.CategoryRole, "category")

    def setExtruderId(self, extruder_id):
        if extruder_id != self._extruder_id:
            self._extruder_id = extruder_id
            self._update()
            self.extruderIdChanged.emit()

    extruderIdChanged = pyqtSignal()
    @pyqtProperty(str, fset = setExtruderId, notify = extruderIdChanged)
    def extruderId(self):
        return self._extruder_id

    def setExtruderDefinition(self, extruder_definition):
        if extruder_definition != self._extruder_definition_id:
            self._extruder_definition_id = extruder_definition
            self._update()
            self.extruderDefinitionChanged.emit()

    extruderDefinitionChanged = pyqtSignal()
    @pyqtProperty(str, fset = setExtruderDefinition, notify = extruderDefinitionChanged)
    def extruderDefinition(self):
        return self._extruder_definition_id

    def setQuality(self, quality):
        if quality != self._quality_id:
            self._quality_id = quality
            self._update()
            self.qualityChanged.emit()

    qualityChanged = pyqtSignal()
    @pyqtProperty(str, fset = setQuality, notify = qualityChanged)
    def quality(self):
        return self._quality_id

    def setMaterial(self, material):
        if material != self._material_id:
            self._material_id = material
            self._update()
            self.materialChanged.emit()

    materialChanged = pyqtSignal()
    @pyqtProperty(str, fset = setMaterial, notify = materialChanged)
    def material(self):
        return self._material_id

    def _update(self):
        if not self._quality_id:
            return

        items = []

        settings = collections.OrderedDict()
        definition_container = Application.getInstance().getGlobalContainerStack().getBottom()

        containers = self._container_registry.findInstanceContainers(id = self._quality_id)
        if not containers:
            UM.Logger.log("w", "Could not find a quality container with id %s", self._quality_id)
            return

        quality_container = None
        quality_changes_container = None

        if containers[0].getMetaDataEntry("type") == "quality":
            quality_container = containers[0]
        else:
            quality_changes_container = containers[0]

            criteria = {
                "type": "quality",
                "quality_type": quality_changes_container.getMetaDataEntry("quality_type"),
                "definition": quality_changes_container.getDefinition().getId()
            }

            quality_container = self._container_registry.findInstanceContainers(**criteria)
            if not quality_container:
                UM.Logger.log("w", "Could not find a quality container matching quality changes %s", quality_changes_container.getId())
                return
            quality_container = quality_container[0]

        quality_type = quality_container.getMetaDataEntry("quality_type")
        definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(quality_container.getDefinition())
        definition = quality_container.getDefinition()

        # Check if the definition container has a translation file.
        definition_suffix = ContainerRegistry.getMimeTypeForContainer(type(definition)).preferredSuffix
        catalog = i18nCatalog(os.path.basename(definition_id + "." + definition_suffix))
        if catalog.hasTranslationLoaded():
            self._i18n_catalog = catalog

        for file_name in quality_container.getDefinition().getInheritedFiles():
            catalog = i18nCatalog(os.path.basename(file_name))
            if catalog.hasTranslationLoaded():
                self._i18n_catalog = catalog

        criteria = {"type": "quality", "quality_type": quality_type, "definition": definition_id}

        if self._material_id and self._material_id != "empty_material":
            criteria["material"] = self._material_id

        criteria["extruder"] = self._extruder_id

        containers = self._container_registry.findInstanceContainers(**criteria)
        if not containers:
            # Try again, this time without extruder
            new_criteria = criteria.copy()
            new_criteria.pop("extruder")
            containers = self._container_registry.findInstanceContainers(**new_criteria)

        if not containers and "material" in criteria:
            # Try again, this time without material
            criteria.pop("material", None)
            containers = self._container_registry.findInstanceContainers(**criteria)

        if not containers:
            # Try again, this time without material or extruder
            criteria.pop("extruder") # "material" has already been popped
            containers = self._container_registry.findInstanceContainers(**criteria)

        if not containers:
            UM.Logger.log("w", "Could not find any quality containers matching the search criteria %s" % str(criteria))
            return

        if quality_changes_container:
            criteria = {"type": "quality_changes", "quality_type": quality_type, "definition": definition_id, "name": quality_changes_container.getName()}
            if self._extruder_definition_id != "":
                extruder_definitions = self._container_registry.findDefinitionContainers(id = self._extruder_definition_id)
                if extruder_definitions:
                    criteria["extruder"] = Application.getInstance().getMachineManager().getQualityDefinitionId(extruder_definitions[0])
                    criteria["name"] = quality_changes_container.getName()
            else:
                criteria["extruder"] = None

            changes = self._container_registry.findInstanceContainers(**criteria)
            if changes:
                containers.extend(changes)

        global_container_stack = Application.getInstance().getGlobalContainerStack()
        is_multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1

        current_category = ""
        for definition in definition_container.findDefinitions():
            if definition.type == "category":
                current_category = definition.label
                if self._i18n_catalog:
                    current_category = self._i18n_catalog.i18nc(definition.key + " label", definition.label)
                continue

            profile_value = None
            profile_value_source = ""
            for container in containers:
                new_value = container.getProperty(definition.key, "value")

                if new_value is not None:
                    profile_value_source = container.getMetaDataEntry("type")
                    profile_value = new_value

                # Global tab should use resolve (if there is one)
                if not self._extruder_id:
                    resolve_value = global_container_stack.getProperty(definition.key, "resolve")
                    if resolve_value is not None and profile_value is not None and profile_value_source != "quality_changes":
                        profile_value = resolve_value

            user_value = None
            if not self._extruder_id:
                user_value = global_container_stack.getTop().getProperty(definition.key, "value")
            else:
                extruder_stack = self._container_registry.findContainerStacks(id = self._extruder_id)
                if extruder_stack:
                    user_value = extruder_stack[0].getTop().getProperty(definition.key, "value")

            if profile_value is None and user_value is None:
                continue

            if is_multi_extrusion:
                settable_per_extruder = global_container_stack.getProperty(definition.key, "settable_per_extruder")
                # If a setting is not settable per extruder (global) and we're looking at an extruder tab, don't show this value.
                if self._extruder_id != "" and not settable_per_extruder:
                    continue

                # If a setting is settable per extruder (not global) and we're looking at global tab, don't show this value.
                if self._extruder_id == "" and settable_per_extruder:
                    continue


            label = definition.label
            if self._i18n_catalog:
                label = self._i18n_catalog.i18nc(definition.key + " label", label)

            items.append({
                "key": definition.key,
                "label": label,
                "unit": definition.unit,
                "profile_value": "" if profile_value is None else str(profile_value),  # it is for display only
                "profile_value_source": profile_value_source,
                "user_value": "" if user_value is None else str(user_value),
                "category": current_category
            })

        self.setItems(items)
