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

import be.iminds.ilabt.jfed.experiment.Experiment;
import be.iminds.ilabt.jfed.experiment.ExperimentController;
import be.iminds.ilabt.jfed.experiment.ExperimentPart;
import be.iminds.ilabt.jfed.experimenter_gui.util.DateTimeUtils;
import be.iminds.ilabt.jfed.highlevel.model.InternalState;
import be.iminds.ilabt.jfed.highlevel.model.Slice;
import be.iminds.ilabt.jfed.ui.javafx.FXMLUtil;
import be.iminds.ilabt.jfed.ui.javafx.GlyphUtils;
import be.iminds.ilabt.jfed.ui.javafx.util.InformationLabel;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Window;
import org.controlsfx.glyphfont.FontAwesome;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.stream.Collectors;

/**
 * User: twalcari
 * Date: 2/24/14
 * Time: 4:03 PM
 */

public class TerminateSliceDialog extends VBox {
    private final Window parentWindow;

    private final Dialog<Collection<ExperimentPart>> dialog;
    private final Slice slice;
    private final Experiment experiment;
    private final ExperimentController experimentController;

    private final IntegerProperty selectedAuthoritesCount = new SimpleIntegerProperty();

    @FXML
    private TableView<TerminableExperimentPart> authoritiesTableView;

    @FXML
    private TableColumn<TerminableExperimentPart, Boolean> selectionTableColumn;
    @FXML
    private TableColumn<TerminableExperimentPart, String> nameTableColumn;
    @FXML
    private TableColumn<TerminableExperimentPart, Instant> expirationTableColumn;

    @FXML
    private InformationLabel sliceTerminationLabel;

    private final ObservableList<TerminableExperimentPart> terminateableResources;


    TerminateSliceDialog(Window parentWindow, ExperimentController experimentController) {
        this.parentWindow = parentWindow;

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


        terminateableResources = FXCollections.observableArrayList();

        experimentController.getExperiment().getParts().stream()
                .filter(part -> part.getState() != InternalState.UNALLOCATED
                        && part.getState() != InternalState.DELETED
                        && part.getState() != InternalState.DELETING)
                .map(TerminableExperimentPart::new)
                .forEach(terminateableResources::add);

        terminateableResources.sort(Comparator.comparing(terminableExperimentPart -> terminableExperimentPart.getExperimentPart().getName()));

        FXMLUtil.injectFXML(this);

        DialogPane dialogPane = new DialogPane();
        dialogPane.setHeaderText("Terminate Experiment");
        dialogPane.setGraphic(GlyphUtils.createDialogGlyph(FontAwesome.Glyph.STOP, Color.RED));
        dialogPane.setContent(this);

        ButtonType releaseResourcesButtonType = new ButtonType("Release resources", ButtonBar.ButtonData.OK_DONE);
        dialogPane.getButtonTypes().setAll(releaseResourcesButtonType, ButtonType.CANCEL);

        dialog = new Dialog<>();
        dialog.initOwner(parentWindow);
        dialog.setDialogPane(dialogPane);
        dialog.setTitle("Terminate Experiment");

        dialog.setResultConverter(buttonType -> {
            if (buttonType == ButtonType.CANCEL)
                return null;
            else {
                return terminateableResources.stream()
                        .filter(TerminableExperimentPart::getTerminateRequested)
                        .map(TerminableExperimentPart::getExperimentPart)
                        .collect(Collectors.toSet());
            }
        });

        //initialize dialog
        Node terminateButton = dialog.getDialogPane().lookupButton(releaseResourcesButtonType);
        terminateButton.disableProperty().bind(selectedAuthoritesCount.isEqualTo(0));
    }

    @FXML
    private void initialize() {
        //populate table
        DoubleBinding dividableWidth =
                authoritiesTableView.widthProperty().subtract(selectionTableColumn.widthProperty()).subtract(2);
        nameTableColumn.prefWidthProperty().bind(dividableWidth.multiply(0.6));
        expirationTableColumn.prefWidthProperty().bind(dividableWidth.multiply(0.4));

        authoritiesTableView.setItems(terminateableResources);
        authoritiesTableView.setPrefHeight(26/*header*/ + terminateableResources.size() * 25);

        selectionTableColumn.setCellValueFactory(t -> t.getValue().terminateRequestedProperty());
        selectionTableColumn.setCellFactory(CheckBoxTableCell.forTableColumn(selectionTableColumn));

        nameTableColumn.setCellValueFactory(t -> new ReadOnlyStringWrapper(t.getValue().getExperimentPart().getName()));

        expirationTableColumn.setCellValueFactory(t -> t.getValue().getExperimentPart().expirationTimeProperty());
        expirationTableColumn.setCellFactory(t -> new TableCell<TerminableExperimentPart, Instant>() {
            @Override
            protected void updateItem(Instant date, boolean empty) {
                if (date == null || empty) {
                    setText(null);
                } else {
                    setText(DateTimeUtils.DATE_HOUR_MINUTES_SECONDS_FORMATTER.format(LocalDateTime.ofInstant(date, ZoneId.systemDefault())));
                }
            }
        });

        assert slice.getExpirationDate() != null;

        sliceTerminationLabel.setText(String.format("While all resources can be released from your experiment '%s', the experiment container itself (slice) must expire to be invalidated. This will happen at %s.",
                slice.getName(), DateTimeUtils.DATE_HOUR_MINUTES_FORMATTER.format(LocalDateTime.ofInstant(slice.getExpirationDate(), ZoneId.systemDefault()))));
    }


    public void showDialog() {
        Collection<ExperimentPart> partsToTerminate = dialog.showAndWait().orElse(Collections.emptyList());

        if (!partsToTerminate.isEmpty())
            experimentController.stop(partsToTerminate);

    }

    private class TerminableExperimentPart {

        private final ExperimentPart experimentPart;

        private final BooleanProperty terminateRequested = new SimpleBooleanProperty(true);

        {
            selectedAuthoritesCount.setValue(selectedAuthoritesCount.getValue() + 1);
            terminateRequested.addListener(observable -> {
                if (terminateRequested.get()) {
                    selectedAuthoritesCount.setValue(selectedAuthoritesCount.getValue() + 1);
                } else {
                    selectedAuthoritesCount.setValue(selectedAuthoritesCount.getValue() - 1);
                }
            });
        }

        private TerminableExperimentPart(ExperimentPart experimentPart) {
            this.experimentPart = experimentPart;
        }

        public boolean getTerminateRequested() {
            return terminateRequested.get();
        }

        public BooleanProperty terminateRequestedProperty() {
            return terminateRequested;
        }

        public void setTerminateRequested(boolean terminateRequested) {
            this.terminateRequested.set(terminateRequested);
        }

        public ExperimentPart getExperimentPart() {
            return experimentPart;
        }
    }
}
