# Copyright (c) 2020 BigRep GmbH
# PrimeTowerConfigurator plugin is released under the terms of the LGPLv3.

from cura.CuraApplication import CuraApplication

from UM.Extension import Extension
from UM.Application import Application
from UM.i18n import i18nCatalog
from UM.Logger import Logger
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator

catalog = i18nCatalog("PrimeTowerConfigurator")


class PrimeTowerConfigurator(Extension):
    MINIMAL_PRIME_TOWER_SIZE = 80

    def __init__(self):
        Extension.__init__(self)
        # Connection is called when mesh movement or scaling is done
        CuraApplication.getInstance().getController().toolOperationStopped.connect(self.adjustPrimeTower)
        CuraApplication.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)

        self.globalStack = None

        self.models = dict()
        self.primeTowerSize = None  # Diameter in mm without helper
        self.primeTowerPositions = list()  # X, Y Coordinate relative to upper right corner
        self.selectedPTPosition = str()  # Relative Prime Tower position around the highest model
        self.primeTowerPositionOffset = list()  # X, Y offset for Tower position, can be negative
        self.primeTowerPositionOffset = [0, 0]
        self.sortedModels = tuple()  # Sorted meshes by z height [nodeNumber, max Z]

    def sortModelsPerHeight(self):
        zComparison = dict()
        for nodeNumber in self.models:
            zComparison[nodeNumber] = self.models[nodeNumber]["maximum"]["z"]

        self.sortedModels = sorted(zComparison.items(), reverse=True, key=lambda x: x[1])

    def setPrimeTowerSize(self):
        self.primeTowerSize = max((self.sortedModels[0][1] / 3), self.MINIMAL_PRIME_TOWER_SIZE)
        self.globalStack.setProperty("prime_tower_size", "value", self.primeTowerSize)

        Logger.log("i", "PrimeTowerConfigurator: Set Prime Tower Size to %s", str(self.primeTowerSize))

    def setPrimeTowerPosition(self):
        # pylint: disable=too-many-statements
        def getBBCornersFromHighestModel() -> dict:
            dimensionsHighestModel = dict()
            dimensionsHighestModel["x min"] = self.models[self.sortedModels[0][0]]["minimum"]["x"]
            dimensionsHighestModel["y min"] = self.models[self.sortedModels[0][0]]["minimum"]["y"]
            dimensionsHighestModel["x max"] = self.models[self.sortedModels[0][0]]["maximum"]["x"]
            dimensionsHighestModel["y max"] = self.models[self.sortedModels[0][0]]["maximum"]["y"]

            cornersHighestModel = dict()
            cornersHighestModel["frontLeft"] = [dimensionsHighestModel["x min"], dimensionsHighestModel["y max"]]
            cornersHighestModel["backLeft"] = [dimensionsHighestModel["x min"], dimensionsHighestModel["y min"]]
            cornersHighestModel["backRight"] = [dimensionsHighestModel["x max"], dimensionsHighestModel["y min"]]
            cornersHighestModel["frontRight"] = [dimensionsHighestModel["x max"], dimensionsHighestModel["y max"]]

            return cornersHighestModel

        def getRelativePositionForPT(cornersHighestModel: dict, helperSize: float):
            bedSize = [self.globalStack.getProperty("machine_width", "value"), self.globalStack.getProperty("machine_depth", "value")]

            if self.selectedPTPosition == "frontLeft":
                xPosition = cornersHighestModel["frontLeft"][0] + (bedSize[0] / 2)
                yPosition = abs(cornersHighestModel["frontLeft"][1] + self.primeTowerSize + (bedSize[1] / 2) - bedSize[1])
                if self.globalStack.getProperty("prime_tower_brim_enable", "value"):
                    xPosition += 0
                    yPosition += 0
                self.primeTowerPositions = [(xPosition + self.primeTowerPositionOffset[0]), (yPosition + self.primeTowerPositionOffset[1])]

            elif self.selectedPTPosition == "left":
                xPosition = cornersHighestModel["frontLeft"][0] + (bedSize[0] / 2)
                yPosition = abs(((cornersHighestModel["frontLeft"][1] + cornersHighestModel["backLeft"][1]) /
                                2) + (self.primeTowerSize / 2) + (bedSize[1] / 2) - bedSize[1])
                if self.globalStack.getProperty("prime_tower_brim_enable", "value"):
                    xPosition += 0
                    yPosition += helperSize
                self.primeTowerPositions = [(xPosition + self.primeTowerPositionOffset[0]), (yPosition + self.primeTowerPositionOffset[1])]

            elif self.selectedPTPosition == "backLeft":
                xPosition = cornersHighestModel["backLeft"][0] + (bedSize[0] / 2)
                yPosition = abs((cornersHighestModel["backLeft"][1]) + (bedSize[1] / 2) - bedSize[1])
                if self.globalStack.getProperty("prime_tower_brim_enable", "value"):
                    xPosition += 0
                    yPosition += (helperSize * 2)
                self.primeTowerPositions = [(xPosition + self.primeTowerPositionOffset[0]), (yPosition + self.primeTowerPositionOffset[1])]

            elif self.selectedPTPosition == "back":
                xPosition = ((cornersHighestModel["backLeft"][0] + cornersHighestModel["backRight"][0]) / 2) + \
                    (self.primeTowerSize / 2) + (bedSize[0] / 2)
                yPosition = abs((cornersHighestModel["backLeft"][1]) + (bedSize[1] / 2) - bedSize[1])
                if self.globalStack.getProperty("prime_tower_brim_enable", "value"):
                    xPosition += helperSize
                    yPosition += (helperSize * 2)
                self.primeTowerPositions = [(xPosition + self.primeTowerPositionOffset[0]), (yPosition + self.primeTowerPositionOffset[1])]

            elif self.selectedPTPosition == "backRight":
                xPosition = cornersHighestModel["backRight"][0] + self.primeTowerSize + (bedSize[0] / 2)
                yPosition = abs((cornersHighestModel["backLeft"][1]) + (bedSize[1] / 2) - bedSize[1])
                if self.globalStack.getProperty("prime_tower_brim_enable", "value"):
                    xPosition += (helperSize * 2)
                    yPosition += (helperSize * 2)
                self.primeTowerPositions = [(xPosition + self.primeTowerPositionOffset[0]), (yPosition + self.primeTowerPositionOffset[1])]

            elif self.selectedPTPosition == "right":
                xPosition = cornersHighestModel["backRight"][0] + self.primeTowerSize + (bedSize[0] / 2)
                yPosition = abs(((cornersHighestModel["frontRight"][1] + cornersHighestModel["backRight"]
                                [1]) / 2) + (self.primeTowerSize / 2) + (bedSize[1] / 2) - bedSize[1])
                if self.globalStack.getProperty("prime_tower_brim_enable", "value"):
                    xPosition += (helperSize * 2)
                    yPosition += helperSize
                self.primeTowerPositions = [(xPosition + self.primeTowerPositionOffset[0]), (yPosition + self.primeTowerPositionOffset[1])]

            elif self.selectedPTPosition == "frontRight":
                xPosition = cornersHighestModel["frontRight"][0] + self.primeTowerSize + (bedSize[0] / 2)
                yPosition = abs(cornersHighestModel["frontRight"][1] + self.primeTowerSize + (bedSize[1] / 2) - bedSize[1])
                if self.globalStack.getProperty("prime_tower_brim_enable", "value"):
                    xPosition += (helperSize * 2)
                    yPosition += 0
                self.primeTowerPositions = [(xPosition + self.primeTowerPositionOffset[0]), (yPosition + self.primeTowerPositionOffset[1])]

            elif self.selectedPTPosition == "front":
                xPosition = ((cornersHighestModel["frontLeft"][0] + cornersHighestModel["frontRight"][0]) / 2) + \
                    (self.primeTowerSize / 2) + (bedSize[0] / 2)
                yPosition = abs(cornersHighestModel["frontRight"][1] + self.primeTowerSize + (bedSize[1] / 2) - bedSize[1])
                if self.globalStack.getProperty("prime_tower_brim_enable", "value"):
                    xPosition += helperSize
                    yPosition += 0
                self.primeTowerPositions = [(xPosition + self.primeTowerPositionOffset[0]), (yPosition + self.primeTowerPositionOffset[1])]

            else:
                # should not happen, but just in case
                self.primeTowerPositions = [(bedSize[0] - self.primeTowerSize - 100), (bedSize[1] - 100)]

        def getPrimeTowerHelperSize():
            adhesion_extruder_nr = int(self.globalStack.getExtruderPositionValueWithDefault("adhesion_extruder_nr"))

            skirt_brim_line_width = self.globalStack.extruderList[adhesion_extruder_nr].getProperty("skirt_brim_line_width", "value")
            initial_layer_line_width_factor = self.globalStack.extruderList[adhesion_extruder_nr].getProperty(
                "initial_layer_line_width_factor", "value")
            brim_line_count = self.globalStack.extruderList[adhesion_extruder_nr].getProperty("brim_line_count", "value")
            brim_gap = self.globalStack.extruderList[adhesion_extruder_nr].getProperty("brim_gap", "value")

            helperSize = brim_line_count * skirt_brim_line_width * initial_layer_line_width_factor / 100 + brim_gap
            return helperSize

        # Selectable settings: frontRight, front, frontLeft, left, backLeft, back, backRight, right
        self.selectedPTPosition = self.globalStack.getProperty("br_ptc_position", "value")
        self.primeTowerPositionOffset[0] = self.globalStack.getProperty("br_ptc_position_offset_x", "value")
        self.primeTowerPositionOffset[1] = self.globalStack.getProperty("br_ptc_position_offset_y", "value")

        helperSize = getPrimeTowerHelperSize()

        cornersHighestModel = getBBCornersFromHighestModel()
        getRelativePositionForPT(cornersHighestModel, helperSize)

        self.globalStack.setProperty("prime_tower_position_x", "value", self.primeTowerPositions[0])
        self.globalStack.setProperty("prime_tower_position_y", "value", self.primeTowerPositions[1])

        Logger.log("i", "PrimeTowerConfigurator: Set Prime Tower Position to X: %s, Y: %s",
                   str(self.primeTowerPositions[0]), str(self.primeTowerPositions[1]))

    def getModelBoundingBoxes(self):
        self.models = dict()  # to make sure, there are no old models
        for nodeNumber, node in enumerate(DepthFirstIterator(Application.getInstance().getController().getScene().getRoot())):
            if node.callDecoration("isSliceable"):
                self.models[nodeNumber] = dict()
                boundingBox = node.getBoundingBox()
                self.models[nodeNumber] = {"minimum": {"x": boundingBox.minimum.x,
                                                       "y": boundingBox.minimum.z,
                                                       "z": boundingBox.minimum.y},
                                           "maximum": {"x": boundingBox.maximum.x,
                                                       "y": boundingBox.maximum.z,
                                                       "z": boundingBox.maximum.y}}

    def adjustPrimeTower(self, *args) -> None:  # pylint: disable=unused-argument
        self.globalStack = Application.getInstance().getGlobalContainerStack()
        if not self.globalStack.getProperty("br_ptc_enable", "value"):
            return

        self.getModelBoundingBoxes()
        self.sortModelsPerHeight()
        if not self.sortedModels:
            # In case we don't have any mesh, happens when last mesh is removed
            return

        self.setPrimeTowerSize()
        self.setPrimeTowerPosition()

    def _onGlobalStackChanged(self) -> None:
        self.globalStack = Application.getInstance().getGlobalContainerStack()
        if self.globalStack:
            self.globalStack.propertyChanged.connect(self._onSettingValueChanged)

    def _onSettingValueChanged(self, key: str, property_name: str) -> None:
        keys = ["br_ptc_enable", "br_ptc_position", "br_ptc_position_offset_x", "br_ptc_position_offset_x", "prime_tower_brim_enable",
                "skirt_brim_line_width", "initial_layer_line_width_factor", "brim_line_count", "brim_gap"]

        if property_name != "value":  # Not the value that was changed.
            return

        if key in keys:
            self.adjustPrimeTower()
