# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import List, Optional, Union, Dict, Any

from cura.PrinterOutput.Models.PrinterConfigurationModel import PrinterConfigurationModel
from ...UM3PrintJobOutputModel import UM3PrintJobOutputModel
from ...ConfigurationChangeModel import ConfigurationChangeModel
from ..CloudOutputController import CloudOutputController
from .BaseCloudModel import BaseCloudModel
from .CloudClusterBuildPlate import CloudClusterBuildPlate
from .CloudClusterPrintJobConfigurationChange import CloudClusterPrintJobConfigurationChange
from .CloudClusterPrintJobImpediment import CloudClusterPrintJobImpediment
from .CloudClusterPrintCoreConfiguration import CloudClusterPrintCoreConfiguration
from .CloudClusterPrintJobConstraint import CloudClusterPrintJobConstraints


## Model for the status of a single print job in a cluster.
#  Spec: https://api-staging.ultimaker.com/connect/v1/spec
class CloudClusterPrintJobStatus(BaseCloudModel):
    ## Creates a new cloud print job status model.
    #  \param assigned_to: The name of the printer this job is assigned to while being queued.
    #  \param configuration: The required print core configurations of this print job.
    #  \param constraints: Print job constraints object.
    #  \param created_at: The timestamp when the job was created in Cura Connect.
    #  \param force: Allow this job to be printed despite of mismatching configurations.
    #  \param last_seen: The number of seconds since this job was checked.
    #  \param machine_variant: The machine type that this job should be printed on.Coincides with the machine_type field
    #       of the printer object.
    #  \param name: The name of the print job. Usually the name of the .gcode file.
    #  \param network_error_count: The number of errors encountered when requesting data for this print job.
    #  \param owner: The name of the user who added the print job to Cura Connect.
    #  \param printer_uuid: UUID of the printer that the job is currently printing on or assigned to.
    #  \param started: Whether the job has started printing or not.
    #  \param status: The status of the print job.
    #  \param time_elapsed: The remaining printing time in seconds.
    #  \param time_total: The total printing time in seconds.
    #  \param uuid: UUID of this print job. Should be used for identification purposes.
    #  \param deleted_at: The time when this print job was deleted.
    #  \param printed_on_uuid: UUID of the printer used to print this job.
    #  \param configuration_changes_required: List of configuration changes the printer this job is associated with
    #       needs to make in order to be able to print this job
    #  \param build_plate: The build plate (type) this job needs to be printed on.
    #  \param compatible_machine_families: Family names of machines suitable for this print job
    #  \param impediments_to_printing: A list of reasons that prevent this job from being printed on the associated
    #       printer
    def __init__(self, created_at: str, force: bool, machine_variant: str, name: str, started: bool, status: str,
                 time_total: int, uuid: str,
                 configuration: List[Union[Dict[str, Any], CloudClusterPrintCoreConfiguration]],
                 constraints: List[Union[Dict[str, Any], CloudClusterPrintJobConstraints]],
                 last_seen: Optional[float] = None, network_error_count: Optional[int] = None,
                 owner: Optional[str] = None, printer_uuid: Optional[str] = None, time_elapsed: Optional[int] = None,
                 assigned_to: Optional[str] = None, deleted_at: Optional[str] = None,
                 printed_on_uuid: Optional[str] = None,
                 configuration_changes_required: List[
                     Union[Dict[str, Any], CloudClusterPrintJobConfigurationChange]] = None,
                 build_plate: Union[Dict[str, Any], CloudClusterBuildPlate] = None,
                 compatible_machine_families: List[str] = None,
                 impediments_to_printing: List[Union[Dict[str, Any], CloudClusterPrintJobImpediment]] = None,
                 **kwargs) -> None:
        self.assigned_to = assigned_to
        self.configuration = self.parseModels(CloudClusterPrintCoreConfiguration, configuration)
        self.constraints = self.parseModels(CloudClusterPrintJobConstraints, constraints)
        self.created_at = created_at
        self.force = force
        self.last_seen = last_seen
        self.machine_variant = machine_variant
        self.name = name
        self.network_error_count = network_error_count
        self.owner = owner
        self.printer_uuid = printer_uuid
        self.started = started
        self.status = status
        self.time_elapsed = time_elapsed
        self.time_total = time_total
        self.uuid = uuid
        self.deleted_at = deleted_at
        self.printed_on_uuid = printed_on_uuid

        self.configuration_changes_required = self.parseModels(CloudClusterPrintJobConfigurationChange,
                                                               configuration_changes_required) \
            if configuration_changes_required else []
        self.build_plate = self.parseModel(CloudClusterBuildPlate, build_plate) if build_plate else None
        self.compatible_machine_families = compatible_machine_families if compatible_machine_families else []
        self.impediments_to_printing = self.parseModels(CloudClusterPrintJobImpediment, impediments_to_printing) \
            if impediments_to_printing else []

        super().__init__(**kwargs)

    ## Creates an UM3 print job output model based on this cloud cluster print job.
    #  \param printer: The output model of the printer
    def createOutputModel(self, controller: CloudOutputController) -> UM3PrintJobOutputModel:
        model = UM3PrintJobOutputModel(controller, self.uuid, self.name)
        self.updateOutputModel(model)

        return model

    ## Creates a new configuration model
    def _createConfigurationModel(self) -> PrinterConfigurationModel:
        extruders = [extruder.createConfigurationModel() for extruder in self.configuration or ()]
        configuration = PrinterConfigurationModel()
        configuration.setExtruderConfigurations(extruders)
        return configuration

    ## Updates an UM3 print job output model based on this cloud cluster print job.
    #  \param model: The model to update.
    def updateOutputModel(self, model: UM3PrintJobOutputModel) -> None:
        model.updateConfiguration(self._createConfigurationModel())
        model.updateTimeTotal(self.time_total)
        model.updateTimeElapsed(self.time_elapsed)
        model.updateOwner(self.owner)
        model.updateState(self.status)
        model.setCompatibleMachineFamilies(self.compatible_machine_families)
        model.updateTimeTotal(self.time_total)
        model.updateTimeElapsed(self.time_elapsed)
        model.updateOwner(self.owner)

        status_set_by_impediment = False
        for impediment in self.impediments_to_printing:
            # TODO: impediment.severity is defined as int, this will not work, is there a translation?
            if impediment.severity == "UNFIXABLE":
                status_set_by_impediment = True
                model.updateState("error")
                break

        if not status_set_by_impediment:
            model.updateState(self.status)

        model.updateConfigurationChanges(
            [ConfigurationChangeModel(
                type_of_change = change.type_of_change,
                index = change.index if change.index else 0,
                target_name = change.target_name if change.target_name else "",
                origin_name = change.origin_name if change.origin_name else "")
                for change in self.configuration_changes_required])
