package be.iminds.ilabt.jfed.experimenter_gui.dialogs;

import be.iminds.ilabt.jfed.experiment.ExperimentController;
import be.iminds.ilabt.jfed.experiment.ExperimentPart;
import be.iminds.ilabt.jfed.experiment.SfaExperimentPart;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.highlevel.model.Slice;
import be.iminds.ilabt.jfed.highlevel.model.Sliver;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.impl.AutomaticAggregateManagerWrapper;
import be.iminds.ilabt.jfed.rspec.model.RspecNode;
import be.iminds.ilabt.jfed.ui.javafx.GlyphUtils;
import be.iminds.ilabt.jfed.ui.javafx.dialogs.JFDialogs;
import be.iminds.ilabt.jfed.ui.javafx.util.InformationLabel;
import com.google.common.base.Joiner;
import javafx.beans.binding.Bindings;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.control.cell.CheckBoxTreeCell;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Window;
import javafx.util.Callback;
import org.controlsfx.glyphfont.FontAwesome;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 */
public class RebootDialog extends BorderPane {
    private final static Logger LOG = LoggerFactory.getLogger(RebootDialog.class);

    private final Window parentStage;
    private final ExperimentController experimentController;
    private final AutomaticAggregateManagerWrapper.AutomaticAggregateManagerWrapperFactory
            automaticAggregateManagerWrapperFactory;

    private final Slice slice;

    private final TreeView<Rebootable> treeView;

    private final boolean hasRebootableItems;
    private final CheckBoxTreeItem<Rebootable> rootItem;

    public RebootDialog(Window parent, ExperimentController experimentController, AutomaticAggregateManagerWrapper.AutomaticAggregateManagerWrapperFactory automaticAggregateManagerWrapperFactory) {
        this.parentStage = parent;
        this.experimentController = experimentController;
        this.automaticAggregateManagerWrapperFactory = automaticAggregateManagerWrapperFactory;

        this.slice = experimentController.getExperiment().getSlice();

        VBox center = new VBox(10);

        setCenter(center);

        List<String> informations = new ArrayList<>();

        boolean hasRebootableItems = false;

        treeView = new TreeView<>();

        treeView.setEditable(true);
        treeView.setCellFactory(new Callback<TreeView<Rebootable>, TreeCell<Rebootable>>() {
            @Override
            public TreeCell<Rebootable> call(TreeView<Rebootable> rebootableTreeView) {
                return new CheckBoxTreeCell<Rebootable>() {
                    @Override
                    public void updateItem(Rebootable rebootable, boolean empty) {
                        super.updateItem(rebootable, empty);
                        if (rebootable != null && !empty) {
                            setEditable(rebootable.isRebootable());

                        }
                    }
                };
            }
        });

        rootItem = new CheckBoxTreeItem<>(new RebootableExperiment());
        rootItem.setExpanded(true);
        treeView.setRoot(rootItem);
        int itemsCount = 1;

        for (ExperimentPart experimentPart : experimentController.getExperiment().getParts()) {
            if (!(experimentPart instanceof SfaExperimentPart)) {
                continue;
            }
            SfaExperimentPart sfaExperimentPart = (SfaExperimentPart)experimentPart;


            CheckBoxTreeItem<Rebootable> authorityTreeItem =
                    new CheckBoxTreeItem<>(new RebootableExperimentPart(sfaExperimentPart));
            authorityTreeItem.setExpanded(true);

            assert sfaExperimentPart.getSlivers() != null;
            for (Sliver sliver : sfaExperimentPart.getSlivers()) {
                Collection<RspecNode> nodes = sliver.getNodes();
                if (nodes != null && !nodes.isEmpty()) {
                    RebootableSliver rebootableSliver = new RebootableSliver(sliver, nodes);
                    if (rebootableSliver.isRebootable()) {
                        LOG.debug("RebootDialog: Found rebootable sliver: " + rebootableSliver.getSliver().getUrnString());
                        authorityTreeItem.getChildren().add(new CheckBoxTreeItem<>(rebootableSliver));
                    } else
                        LOG.debug("RebootDialog: Found NON-rebootable sliver: " + rebootableSliver.getSliver().getUrnString());
                }
            }

            LOG.debug("RebootDialog: " + experimentPart.getName() + " has " + authorityTreeItem.getChildren().size() + " rebootable resources");
            if (!authorityTreeItem.getChildren().isEmpty()) {
                rootItem.getChildren().add(authorityTreeItem);
                hasRebootableItems = true;
                itemsCount += authorityTreeItem.getChildren().size() + 1;
            } else {
                informations.add(String.format("%s does not have rebootable resources.", experimentPart.getName()));
            }

        }
        rootItem.setSelected(true);
        treeView.setPrefWidth(100);
        treeView.setPrefHeight(Math.min(Math.max(1 + (itemsCount * 35), 250), 800));

        center.getChildren().add(treeView);

        if (!informations.isEmpty()) {
            InformationLabel label = new InformationLabel(Joiner.on("\n").join(informations));
            center.getChildren().add(label);
            //label.prefWidthProperty().bind(center.widthProperty()); //causes height-bug  FEDIBBTDEV-747
        }

        this.hasRebootableItems = hasRebootableItems;
        LOG.debug("RebootDialog: final verdict: there are " + (hasRebootableItems ? " "
                : "no ") + "reboo.getDefaultComponentManagerUrn() in slice " + slice.getUrnString());
    }

    public void showDialog() {
        if (!hasRebootableItems) {
            JFDialogs.create()
                    .owner(parentStage)
                    .message("This experiment doesn't contain any resources that support rebooting.")
                    .masthead("Rebooting is unavailable")
                    .showWarning();
            return;
        }

        Dialog<ButtonType> dialog = new Dialog<>();
        dialog.initOwner(parentStage);
        dialog.setTitle("Reboot experiment nodes");
        dialog.setHeaderText("Please select the testbeds on which the nodes must be rebooted");
        dialog.setGraphic(GlyphUtils.createDialogGlyph(FontAwesome.Glyph.POWER_OFF, Color.RED));
        dialog.getDialogPane().setContent(this);
        dialog.getDialogPane().getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL);

        Node okButton = dialog.getDialogPane().lookupButton(ButtonType.OK);
        okButton.disableProperty().bind(Bindings.not(Bindings.or(rootItem.selectedProperty(), rootItem.indeterminateProperty())));

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

        if (response.isPresent() && response.get() != ButtonType.OK)
            return;

        long numberOfSliversSelected =
                treeView.getRoot().getChildren().stream().flatMap(item -> item.getChildren().stream())
                        .filter(item -> ((CheckBoxTreeItem<Rebootable>) item).isSelected())
                        .count();

        if (numberOfSliversSelected > 0) {

            Alert confirmAlert = new Alert(Alert.AlertType.WARNING);
            confirmAlert.setContentText(String.format("You are about to reboot %d sliver(s). All data in memory WILL BE LOST. Are you sure that you want to continue?", numberOfSliversSelected));
            confirmAlert.setTitle("Rebooting slivers");

            ButtonType rebootButtonType = new ButtonType("Reboot slivers", ButtonBar.ButtonData.OTHER);

            confirmAlert.getButtonTypes().setAll(rebootButtonType, ButtonType.CANCEL);

            ((Button) confirmAlert.getDialogPane().lookupButton(ButtonType.CANCEL)).setDefaultButton(true);


            if (confirmAlert.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.CANCEL)
                return;

        }

        //we do not process the root
        for (TreeItem<Rebootable> child : treeView.getRoot().getChildren()) {
            processTreeItem((CheckBoxTreeItem<Rebootable>) child);
        }
    }

    private void processTreeItem(CheckBoxTreeItem<Rebootable> treeItem) {
        if (!treeItem.isIndeterminate() && treeItem.isSelected()) {
            treeItem.getValue().reboot();
        } else {
            for (TreeItem<Rebootable> child : treeItem.getChildren()) {
                processTreeItem((CheckBoxTreeItem<Rebootable>) child);
            }
        }
    }

    private interface Rebootable {
        void reboot();

        boolean isRebootable();
    }

    private static class RebootableExperiment implements Rebootable {

        @Override
        public void reboot() {
            //do nothing
        }

        @Override
        public boolean isRebootable() {
            return true;
        }

        @Override
        public String toString() {
            return "All nodes";
        }
    }

    private class RebootableSliver implements Rebootable {
        private final Sliver sliver;
        private final boolean canReboot;
        private final Collection<RspecNode> nodes;

        private RebootableSliver(Sliver sliver, Collection<RspecNode> nodes) {
            this.sliver = sliver;
            this.nodes = nodes;

            canReboot =
                    automaticAggregateManagerWrapperFactory.create(sliver.getServer()).isRestartSupported();
        }

        public Sliver getSliver() {
            return sliver;
        }

        public boolean isRebootable() {
            return canReboot;
        }

        public void reboot() {
            experimentController.reboot(sliver);
        }

        @Override
        public String toString() {
            return nodes.stream()
                    .map(rspecNode -> rspecNode.getClientId() != null ? rspecNode.getClientId() : "<NO ID>")
                    .collect(Collectors.joining(", "));
        }
    }

    private class RebootableExperimentPart implements Rebootable {
        private final SfaExperimentPart experimentPart;
        private final boolean canReboot;

        private RebootableExperimentPart(SfaExperimentPart experimentPart) {
            this.experimentPart = experimentPart;

            canReboot =
                    automaticAggregateManagerWrapperFactory.create(experimentPart.getConnectSfaAuthority()).isRestartSupported();

        }

        public Server getAuthority() {
            return experimentPart.getConnectSfaAuthority();
        }

        public boolean isRebootable() {
            return canReboot;
        }

        public void reboot() {
            experimentController.reboot(experimentPart);
        }

        @Override
        public String toString() {
            return experimentPart.getName();
        }
    }
}

