package be.iminds.ilabt.jfed.experimenter_gui.editor.actions;

import be.iminds.ilabt.jfed.experimenter_gui.config.JFedGuiConfig;
import be.iminds.ilabt.jfed.experimenter_gui.editor.EditableRspec;
import be.iminds.ilabt.jfed.experimenter_gui.ui.HealthStatusPane;
import be.iminds.ilabt.jfed.experimenter_gui.ui.ResourceCell;
import be.iminds.ilabt.jfed.experimenter_gui.util.StringConverters;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.lowlevel.TestbedInfoSource;
import be.iminds.ilabt.jfed.rspec.model.ModelRspecType;
import be.iminds.ilabt.jfed.rspec.model.RspecNode;
import be.iminds.ilabt.jfed.rspec.model.javafx_impl.FXModelRspec;
import be.iminds.ilabt.jfed.rspec.model.javafx_impl.FXRspecNode;
import be.iminds.ilabt.jfed.rspec.rspec_source.RequestRspecSource;
import be.iminds.ilabt.jfed.ui.javafx.GlyphUtils;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Window;
import org.controlsfx.glyphfont.FontAwesome;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * User: twalcari
 * Date: 5/20/2015
 * Time: 11:05
 */
public class ProcessUnboundNodesAction extends EditorAction<Boolean> {
    private static final Logger LOG = LoggerFactory.getLogger(ProcessUnboundNodesAction.class);

    private final RequestRspecSource requestRspecSource;


    private final TestbedInfoSource testbedInfoSource;
    private final JFedGuiConfig config;

    ProcessUnboundNodesAction(EditableRspec editableRspec, Window parentWindow,
                              TestbedInfoSource testbedInfoSource, JFedGuiConfig config) {
        super(editableRspec, parentWindow);
        this.testbedInfoSource = testbedInfoSource;
        this.config = config;

        this.requestRspecSource = editableRspec.getRequestRspecSource();
    }

    @Override
    public Boolean call() {
        FXModelRspec modelRspec = requestRspecSource.getModelRspec(ModelRspecType.FX);
        if (modelRspec == null) {
            LOG.warn("Cannot process unbound nodes as modelRspec is null!");
            return false;
        }

        Collection<FXRspecNode> unboundNodes =
                modelRspec.getNodes().filtered(input1 -> input1.getComponentManagerId() == null);

        if (!unboundNodes.isEmpty()) {
            if (requestRspecSource.isLosingData(ModelRspecType.FX)) {
                LOG.warn("Cannot process unbound nodes as modelRspec is losing data!");

                Alert alert = new Alert(Alert.AlertType.WARNING);
                alert.setContentText("jFed detected unbound resources. These need to be bound to an authority before the experiment can be run." +
                        "jFed has detected that information will be lost when resolving this issue. Do you want to continue?");
                alert.setTitle("Unbound nodes detected");
                alert.setHeaderText("Unbound nodes detected");

                alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO);

                if (alert.showAndWait().orElse(ButtonType.NO) == ButtonType.NO)
                    return false;

                LOG.info("User accepted data loss to resolve unbound nodes.");
            }



            Dialog<ButtonType> dialog = new Dialog<>();
            dialog.initOwner(parentWindow);
            dialog.setTitle("Unbound nodes detected");
            dialog.setHeaderText("Unbound nodes detected");
            dialog.setGraphic(GlyphUtils.createDialogGlyph(FontAwesome.Glyph.PUZZLE_PIECE, Color.CHOCOLATE));
            dialog.setContentText(String.format("This RSpec contains %d unbound nodes. " +
                            "These nodes need to be assigned to a testbed before starting the experiment.\n\n" +
                            "Do you want to assign these nodes to a testbed now, or do you want to resolve this issue later?",
                    unboundNodes.size()));

            ButtonType resolveNowButtonType = new ButtonType("Assign these nodes now", ButtonBar.ButtonData.YES);
            ButtonType resolveLaterButtonType = new ButtonType("Resolve later", ButtonBar.ButtonData.NO);

            dialog.getDialogPane().getButtonTypes().setAll(resolveNowButtonType, resolveLaterButtonType);

            Optional<ButtonType> result = dialog.showAndWait();

            if (!result.isPresent() || result.get() != resolveNowButtonType)
                return false;

            List<Server> bindableAuthorities = new ArrayList<>(config.getBindableServers());

            bindableAuthorities.sort(Comparator.comparing(auth -> auth.getName().toLowerCase()));


            Dialog<Server> unboundNodesDialog = new Dialog<>();

            unboundNodesDialog.initOwner(parentWindow);
            unboundNodesDialog.setGraphic(GlyphUtils.createDialogGlyph(FontAwesome.Glyph.PUZZLE_PIECE, Color.CHOCOLATE));
            unboundNodesDialog.setHeaderText("Unbound nodes detected");

            VBox dialogContent = new VBox(10);
            dialogContent.getChildren().add(new Label(String.format("Please select the testbed to which the %d unbound nodes must be assigned:",
                    unboundNodes.size())));

            ComboBox<Server> serverComboBox = new ComboBox<>();
            serverComboBox.setPrefWidth(300);
            serverComboBox.setCellFactory(param -> new ResourceCell(null, config));
            serverComboBox.setConverter(StringConverters.SERVER_STRING_CONVERTER);
            serverComboBox.getItems().setAll(bindableAuthorities);

            unboundNodesDialog.getDialogPane().setContent(dialogContent);
            unboundNodesDialog.setResultConverter(param -> {
                if (param == ButtonType.OK) {
                    return serverComboBox.getSelectionModel().getSelectedItem();
                } else {
                    return null;
                }
            });
            dialogContent.getChildren().add(serverComboBox);


            HealthStatusPane healthStatusPane = new HealthStatusPane();
            healthStatusPane.setConfig(config);
            dialogContent.getChildren().add(healthStatusPane);
            serverComboBox.getSelectionModel().selectedItemProperty().addListener(observable ->
                    healthStatusPane.setServerGlimpse(
                            config.getServerGlimpse(serverComboBox.getSelectionModel().getSelectedItem())));

            unboundNodesDialog.getDialogPane().getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL);

            //disable OK button when nothing is selected
            Node okButton = unboundNodesDialog.getDialogPane().lookupButton(ButtonType.OK);
            okButton.disableProperty().bind(serverComboBox.getSelectionModel().selectedItemProperty().isNull());

            Optional<Server> chosenAuthorityInfo = unboundNodesDialog.showAndWait();


            if (chosenAuthorityInfo.isPresent()) {
                for (RspecNode rspecNode : unboundNodes) {
                    rspecNode.setComponentManagerId(chosenAuthorityInfo.get().getDefaultComponentManagerAsGeniUrn());
                }

                editableRspec.setRequestRspecSource(new RequestRspecSource(modelRspec));
                return true;
            }

            return false;
        } else
            return true;

    }


}
