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

from unittest.mock import patch, MagicMock
import pytest

from UM.Settings.Interfaces import ContainerInterface
from cura.Machines.MachineNode import MachineNode

metadata_dict = {
    "has_materials": "false",
    "has_variants": "true",
    "has_machine_quality": "true",
    "quality_definition": "test_quality_definition",
    "exclude_materials": ["excluded_material_1", "excluded_material_2"],
    "preferred_variant_name": "beautiful_nozzle",
    "preferred_material": "beautiful_material",
    "preferred_quality_type": "beautiful_quality_type"
}


@pytest.fixture
def container_registry():
    result = MagicMock()
    result.findInstanceContainersMetadata = MagicMock(return_value = [{"id": "variant_1", "name": "Variant One", "quality_type": "normal"}, {"id": "variant_2", "name": "Variant Two", "quality_type": "great"}])
    result.findContainersMetadata = MagicMock(return_value = [metadata_dict])
    return result

@pytest.fixture
def empty_machine_node():
    """Creates a machine node without anything underneath it. No sub-nodes.

    For testing stuff with machine nodes without testing _loadAll(). You'll need
    to add subnodes manually in your test.
    """

    empty_container_registry = MagicMock()
    empty_container_registry.findContainersMetadata = MagicMock(return_value = [metadata_dict])  # Still contain the MachineNode's own metadata for the constructor.
    empty_container_registry.findInstanceContainersMetadata = MagicMock(return_value = [])
    with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value = empty_container_registry)):
        with patch("cura.Machines.MachineNode.MachineNode._loadAll", MagicMock()):
            return MachineNode("machine_1")

def getMetadataEntrySideEffect(*args, **kwargs):
    return metadata_dict.get(args[0])


def createMockedInstanceContainer():
    result = MagicMock(spec = ContainerInterface)
    result.getMetaDataEntry = MagicMock(side_effect = getMetadataEntrySideEffect)
    return result


def createMachineNode(container_id, container_registry):
    with patch("cura.Machines.MachineNode.VariantNode"):  # We're not testing the variant node here, so patch it out.
        with patch("cura.Machines.MachineNode.QualityNode"):
            with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value = container_registry)):
                return MachineNode(container_id)


def test_machineNodeInit(container_registry):
    machine_node = createMachineNode("machine_1", container_registry)

    # As variants get stored by name, we want to check if those get added.
    assert "Variant One" in machine_node.variants
    assert "Variant Two" in machine_node.variants
    assert len(machine_node.variants) == 2  # And ensure that *only* those two got added.

def test_metadataProperties(container_registry):
    node = createMachineNode("machine_1", container_registry)

    # Check if each of the metadata entries got stored properly.
    assert not node.has_materials
    assert node.has_variants
    assert node.has_machine_quality
    assert node.quality_definition == metadata_dict["quality_definition"]
    assert node.exclude_materials == metadata_dict["exclude_materials"]
    assert node.preferred_variant_name == metadata_dict["preferred_variant_name"]
    assert node.preferred_material == metadata_dict["preferred_material"]
    assert node.preferred_quality_type == metadata_dict["preferred_quality_type"]


def test_getQualityGroupsBothExtrudersAvailable(empty_machine_node):
    """Test getting quality groups when there are quality profiles available for

    the requested configurations on two extruders.
    """

    # Prepare a tree inside the machine node.
    extruder_0_node = MagicMock(quality_type = "quality_type_1")
    extruder_1_node = MagicMock(quality_type = "quality_type_1")  # Same quality type, so this is the one that can be returned.
    empty_machine_node.variants = {
        "variant_1": MagicMock(
            materials = {
                "material_1": MagicMock(
                    qualities = {
                        "quality_1": extruder_0_node
                    }
                )
            }
        ),
        "variant_2": MagicMock(
            materials = {
                "material_2": MagicMock(
                    qualities = {
                        "quality_2": extruder_1_node
                    }
                )
            }
        )
    }
    global_node = MagicMock(
        container = MagicMock(id = "global_quality_container"),  # Needs to exist, otherwise it won't add this quality type.
        getMetaDataEntry = lambda _, __: "Global Quality Profile Name"
    )
    empty_machine_node.global_qualities = {
        "quality_type_1": global_node
    }

    # Request the quality groups for the variants in the machine tree.
    result = empty_machine_node.getQualityGroups(["variant_1", "variant_2"], ["material_1", "material_2"], [True, True])

    assert "quality_type_1" in result, "This quality type was available for both extruders."
    assert result["quality_type_1"].node_for_global == global_node
    assert result["quality_type_1"].nodes_for_extruders[0] == extruder_0_node
    assert result["quality_type_1"].nodes_for_extruders[1] == extruder_1_node
    assert result["quality_type_1"].name == global_node.getMetaDataEntry("name", "Unnamed Profile")
    assert result["quality_type_1"].quality_type == "quality_type_1"


def test_getQualityGroupsAvailability(empty_machine_node):
    """Test the "is_available" flag on quality groups.

    If a profile is available for a quality type on an extruder but not on all
    extruders, there should be a quality group for it but it should not be made
    available.
    """

    # Prepare a tree inside the machine node.
    extruder_0_both = MagicMock(quality_type = "quality_type_both")  # This quality type is available for both extruders.
    extruder_1_both = MagicMock(quality_type = "quality_type_both")
    extruder_0_exclusive = MagicMock(quality_type = "quality_type_0")  # These quality types are only available on one of the extruders.
    extruder_1_exclusive = MagicMock(quality_type = "quality_type_1")
    empty_machine_node.variants = {
        "variant_1": MagicMock(
            materials = {
                "material_1": MagicMock(
                    qualities = {
                        "quality_0_both": extruder_0_both,
                        "quality_0_exclusive": extruder_0_exclusive
                    }
                )
            }
        ),
        "variant_2": MagicMock(
            materials = {
                "material_2": MagicMock(
                    qualities = {
                        "quality_1_both": extruder_1_both,
                        "quality_1_exclusive": extruder_1_exclusive
                    }
                )
            }
        )
    }
    global_both = MagicMock(container = MagicMock(id = "global_quality_both"), getMetaDataEntry = lambda _, __: "Global Quality Both")
    global_0 = MagicMock(container = MagicMock(id = "global_quality_0"), getMetaDataEntry = lambda _, __: "Global Quality 0 Exclusive")
    global_1 = MagicMock(container = MagicMock(id = "global_quality_1"), getMetaDataEntry = lambda _, __: "Global Quality 1 Exclusive")
    empty_machine_node.global_qualities = {
        "quality_type_both": global_both,
        "quality_type_0": global_0,
        "quality_type_1": global_1
    }

    # Request the quality groups for the variants in the machine tree.
    result = empty_machine_node.getQualityGroups(["variant_1", "variant_2"], ["material_1", "material_2"], [True, True])

    assert "quality_type_both" in result, "This quality type was available on both extruders."
    assert result["quality_type_both"].is_available, "This quality type was available on both extruders and thus should be made available."
    assert "quality_type_0" in result, "This quality type was available for one of the extruders, and so there must be a group for it (even though it's unavailable)."
    assert not result["quality_type_0"].is_available, "This quality type was only available for one of the extruders and thus can't be activated."
    assert "quality_type_1" in result, "This quality type was available for one of the extruders, and so there must be a group for it (even though it's unavailable)."
    assert not result["quality_type_1"].is_available, "This quality type was only available for one of the extruders and thus can't be activated."