# Copyright (c) 2019 BigRep
# The Batch Printing plugin is released under the terms of the LGPLv3.

import math
import copy

from UM.Extension import Extension
from UM.Operations.GroupedOperation import GroupedOperation
from UM.Math.Quaternion import Quaternion
from UM.Math.Vector import Vector
from UM.Scene.SceneNode import SceneNode
from UM.Message import Message
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
from UM.i18n import i18nCatalog

from cura.CuraApplication import CuraApplication

from .BatchDescr import BatchDescr
from .BatchIterator import BatchIterator
from .BatchArrangement import BatchArrangement

i18n_catalog = i18nCatalog("BatchPrintingPlugin")


class BatchPrintingPlugin(Extension):
    def __init__(self):
        super().__init__()
        self.addMenuItem(i18n_catalog.i18n("Arrange objects for mixed mode"), self.arrangeObjectsForMixedMode)
        self.addMenuItem(i18n_catalog.i18n("Populate build plate completely"), self.populateBuildPlateCompletely)
        self.addMenuItem(i18n_catalog.i18n("Populate build plate for sequential printing"), self.populateBuildPlateSequential)
        self._message = None
        self._scene = CuraApplication.getInstance().getController().getScene()  # type: Scene

    @staticmethod
    def _extractBox(node):
        boundingBox = node.getBoundingBox()
        return (boundingBox.minimum.getData(), boundingBox.maximum.getData())


    @staticmethod
    def rotate90(node):
        rotation = Quaternion.fromAngleAxis(-0.5 * math.pi, Vector(0, 1, 0))
        node.rotate(rotation, SceneNode.TransformSpace.World)

    @staticmethod
    def _optimizeOrientation(node):
        bbox = BatchPrintingPlugin._extractBox(node)
        diag_x = bbox[1][0] - bbox[0][0]
        diag_y = bbox[1][2] - bbox[0][2]
        if diag_y > diag_x:
            BatchPrintingPlugin.rotate90(node)


    @staticmethod
    def run(nodes) -> bool:        
        bbox_list = [BatchPrintingPlugin._extractBox(node) for node in nodes]

        translations, warning = BatchArrangement(BatchDescr.getMachineDescr(nodes[0])).process(bbox_list)

        for counter, node in enumerate(nodes):
            vec = Vector(translations[counter][0], translations[counter][1], translations[counter][2])
            node.translate(vec, SceneNode.TransformSpace.World)

        return warning


    def arrangeObjectsForMixedMode(self, optimize=True):
        # If we still had a message open from last time, hide it.
        if self._message:
            self._message.hide()

        selected_nodes = self._collectNodes()
        if not selected_nodes:
            self._message = Message(i18n_catalog.i18nc("@info:status", "No objects to arrange."),
                                    title=i18n_catalog.i18nc("@info:title", "Batch Printing"))
            self._message.show()
            return

        if optimize:
            global_stack = CuraApplication.getInstance().getGlobalContainerStack()
            optimize = global_stack.getProperty("batch_printing_90deg_rotation", "value")
 
        if optimize:
            for node in selected_nodes:
                BatchPrintingPlugin._optimizeOrientation(node)

        warning = BatchPrintingPlugin.run(selected_nodes)

        if warning:
            self._message = Message(i18n_catalog.i18nc(
                "@info:status", "Unable to find a location within the build volume for all objects."), title=i18n_catalog.i18nc("@info:title", "Batch Printing"))
            self._message.show()

    def _collectNodes(self):
        return [node for node in BatchIterator(self._scene.getRoot())]  # pylint: disable=unnecessary-comprehension


    def populateBuildPlateCompletely(self):
        self.populateBuildPlate(True)


    def populateBuildPlateSequential(self):
        self.populateBuildPlate(False)

    def populateBuildPlate(self, complete: bool):
        # If we still had a message open from last time, hide it.
        if self._message:
            self._message.hide()

        nodes = self._collectNodes()

        if not nodes:
            self._message = Message(i18n_catalog.i18nc("@info:status", "No objects to arrange."),
                                    title=i18n_catalog.i18nc("@info:title", "Batch Printing"))
            self._message.show()
            return
        elif len(nodes) > 1:
            self._message = Message(i18n_catalog.i18nc("@info:status", "This feature does just work for a single object."),
                                    title=i18n_catalog.i18nc("@info:title", "Batch Printing"))
            self._message.show()
            return

        # If object is part of a group, multiply group
        current_node = nodes[0]
        while current_node.getParent() and (current_node.getParent().callDecoration("isGroup") or current_node.getParent().callDecoration("isSliceable")):
            current_node = current_node.getParent()


        batchArrangement = BatchArrangement(BatchDescr.getMachineDescr(current_node))
        if batchArrangement.shallRotateForPopulate(BatchPrintingPlugin._extractBox(current_node)):
            BatchPrintingPlugin.rotate90(current_node)

        if complete:
            multiplier = batchArrangement.calcMultiplier(BatchPrintingPlugin._extractBox(current_node))
        else:
            multiplier = batchArrangement.calcMultiplierSequential(BatchPrintingPlugin._extractBox(current_node))

        new_nodes = [copy.deepcopy(current_node) for _ in range(multiplier - 1)]  # We already one item!

        if new_nodes:
            # Same build plate
            for new_node in new_nodes:
                build_plate_number = current_node.callDecoration("getBuildPlateNumber")
                new_node.callDecoration("setBuildPlateNumber", build_plate_number)
                for child in new_node.getChildren():
                    child.callDecoration("setBuildPlateNumber", build_plate_number)

            op = GroupedOperation()
            for new_node in new_nodes:
                op.addOperation(AddSceneNodeOperation(new_node, current_node.getParent()))
            op.push()

        self.arrangeObjectsForMixedMode(optimize=False)

        self._message = Message(i18n_catalog.i18nc("@info:status", "Build plate is populated with " + str(multiplier) + " objects."),
                                title=i18n_catalog.i18nc("@info:title", "Batch Printing"))
        self._message.show()
