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.experimenter_gui.util.DateTimeUtils;
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.authority.AuthorityFinder;
import be.iminds.ilabt.jfed.ui.javafx.FXMLUtil;
import be.iminds.ilabt.jfed.ui.javafx.GlyphUtils;
import be.iminds.ilabt.jfed.ui.javafx.dialogs.JFDialogs;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
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 javafx.util.Callback;
import javafx.util.StringConverter;
import org.controlsfx.glyphfont.FontAwesome;

import java.time.*;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * User: twalcari
 * Date: 2/24/14
 * Time: 4:03 PM
 */
public class RenewSliceDialog extends VBox {

    private final Window parentWindow;
    private final Dialog<ButtonType> dialog;
    private final Slice slice;
    private final ExperimentController experimentController;

    @FXML
    private TableView<RenewableExperimentPart> authoritiesTableView;

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

    @FXML
    protected ComboBox<Integer> hoursComboBox;
    @FXML
    protected ComboBox<Integer> minutesComboBox;
    @FXML
    protected ComboBox<Integer> daysComboBox;

    @FXML
    private Label sliceInfoLabel;

    private final ObservableList<RenewableExperimentPart> renewableAuthorities;
    private static final Duration DEFAULT_DURATION = Duration.ofHours(2);

    private static class RenewableExperimentPart {

        private final ExperimentPart experimentPart;

        private final BooleanProperty renewRequested = new SimpleBooleanProperty(true);

        public RenewableExperimentPart(ExperimentPart experimentPart) {
            this.experimentPart = experimentPart;
        }

        public ExperimentPart getExperimentPart() {
            return experimentPart;
        }

        public boolean getRenewRequested() {
            return renewRequested.get();
        }

        public BooleanProperty renewRequestedProperty() {
            return renewRequested;
        }

        public void setRenewRequested(boolean renewRequested) {
            this.renewRequested.set(renewRequested);
        }
    }

    RenewSliceDialog(Window parentWindow, ExperimentController experimentController, AuthorityFinder authorityFinder) {
        this.parentWindow = parentWindow;

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

        FXMLUtil.injectFXML(this);


        //populate table
        DoubleBinding dividableWidth =
                authoritiesTableView.widthProperty().subtract(selectionTableColumn.widthProperty()).subtract(2);
        nameTableColumn.prefWidthProperty().bind(dividableWidth.multiply(0.375));
        expirationTableColumn.prefWidthProperty().bind(dividableWidth.multiply(0.25));
        durationTableColumn.prefWidthProperty().bind(dividableWidth.multiply(0.375));


        Multimap<Server, Sliver> sliverPerAuthorityMap = HashMultimap.create();
        for (Sliver sliver : slice.getSlivers()) {
            Server sliverAuth = sliver.getServer();
            Server auth = authorityFinder.findByAnyUrn(sliverAuth.getDefaultComponentManagerAsGeniUrn(), AuthorityFinder.Purpose.STATUS);
            sliverPerAuthorityMap.put(auth, sliver);
        }

        renewableAuthorities = FXCollections.observableArrayList();

        experimentController.getExperiment().getParts().stream()
                .map(RenewableExperimentPart::new)
                .forEach(renewableAuthorities::add);

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

        selectionTableColumn.setCellValueFactory(param -> param.getValue().renewRequestedProperty());
        selectionTableColumn.setCellFactory(CheckBoxTableCell.forTableColumn(selectionTableColumn));

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

        expirationTableColumn.setCellValueFactory(param -> param.getValue().getExperimentPart().expirationTimeProperty());

        expirationTableColumn.setCellFactory(t -> new TableCell<RenewableExperimentPart, 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())));
                }
            }
        });
        durationTableColumn.setCellValueFactory(param -> param.getValue().getExperimentPart().expirationTimeProperty());

        durationTableColumn.setCellFactory(new Callback<TableColumn<RenewableExperimentPart, Instant>, TableCell<RenewableExperimentPart, Instant>>() {
            @Override
            public TableCell<RenewableExperimentPart, Instant> call(TableColumn<RenewableExperimentPart, Instant> renewableAuthorityDateTableColumn) {
                return new TableCell<RenewableExperimentPart, Instant>() {
                    private Instant dateTime = null;
                    private final Timeline timeline;

                    //instance initializer
                    {
                        this.timeline =
                                new Timeline(new KeyFrame(javafx.util.Duration.ZERO, actionEvent -> updateString()),
                                        new KeyFrame(new javafx.util.Duration(1000)));
                        this.timeline.setCycleCount(Animation.INDEFINITE);
                    }

                    @Override
                    protected void updateItem(Instant date, boolean empty) {
                        if (date == null || empty) {
                            timeline.stop();
                            dateTime = null;
                            setText("");
                        } else {
                            dateTime = date;
                            updateString();
                            timeline.play();

                        }
                    }

                    private void updateString() {
                        setText(DateTimeUtils.prettyPrintDuration(Duration.between(Instant.now(), dateTime).withNanos(0)));
                    }
                };
            }
        });


        Duration periodLeft = DEFAULT_DURATION;
        if (slice.getExpirationDate() != null) {
            periodLeft = Duration.between(Instant.now(), slice.getExpirationDate());

            //update sliceInfoLabel
            sliceInfoLabel.setText(String.format("Your experiment currently expires at %s. When necessary, it will automatically be renewed to match the latest authority expiration time.",
                    DateTimeUtils.DATE_HOUR_MINUTES_FORMATTER.format(LocalDateTime.ofInstant(slice.getExpirationDate(), ZoneId.systemDefault()))));
        }

        //populate combo-boxes
        daysComboBox.getItems().addAll(IntStream.range(0, 100).boxed().collect(Collectors.toList()));
        hoursComboBox.getItems().addAll(IntStream.range(0, 24).boxed().collect(Collectors.toList()));
        minutesComboBox.getItems().addAll(IntStream.range(0, 60).boxed().collect(Collectors.toList()));
        minutesComboBox.setConverter(new StringConverter<Integer>() {
            @Override
            public String toString(Integer integer) {
                return String.format("%02d", integer);
            }

            @Override
            public Integer fromString(String s) {
                return Integer.parseInt(s);
            }
        });

        long time = periodLeft.toMinutes();
        minutesComboBox.setValue((int) time % 60);
        time /= 60;
        hoursComboBox.setValue((int) time % 24);
        time /= 24;
        daysComboBox.setValue((int) time);

        //initialize dialog
        dialog = new Dialog<>();
        dialog.initOwner(parentWindow);
        dialog.setTitle("Renew Experiment");
        dialog.setHeaderText("Enter the new experiment duration:");
        dialog.setGraphic(GlyphUtils.createDialogGlyph(FontAwesome.Glyph.CLOCK_ALT, Color.LAWNGREEN));
        dialog.getDialogPane().setContent(this);

        ButtonType renewButtonType = new ButtonType("Renew", ButtonBar.ButtonData.OK_DONE);
        dialog.getDialogPane().getButtonTypes().setAll(renewButtonType, ButtonType.CANCEL);

        dialog.show();

        dialog.resultProperty().addListener(observable -> {
            if(dialog.getResult() == renewButtonType){
                        doRenew();
            }
        });
    }


    public void showDialog() {
        dialog.show();
    }


    public void doRenew() {
        Instant newExpirationTime = ZonedDateTime.now()
                .plusDays(daysComboBox.getSelectionModel().getSelectedItem())
                .plusHours(hoursComboBox.getSelectionModel().getSelectedItem())
                .plusMinutes(minutesComboBox.getSelectionModel().getSelectedItem())
                .plusSeconds(15) //prevent problems with checks that are too strict
                .toInstant();

        assert (slice.getExpirationDate() != null);

        List<ExperimentPart> selectedParts = renewableAuthorities.stream()
                .filter(RenewableExperimentPart::getRenewRequested)
                .map(RenewableExperimentPart::getExperimentPart).collect(Collectors.toList());


        boolean renewAll = renewableAuthorities.stream().allMatch(RenewableExperimentPart::getRenewRequested);



        Instant firstExpirationTime = selectedParts.stream()
                .map(ExperimentPart::getExpirationTime)
                .min(Comparator.nullsLast(Comparator.naturalOrder()))
                .orElse(slice.getExpirationDate());


        if (newExpirationTime.isBefore(firstExpirationTime)) {
            int[] timeLeft =
                    DateTimeUtils.getSecondsMinutesHoursDays(Duration.between(Instant.now(), firstExpirationTime));

            Optional<ButtonType> result = JFDialogs.create()
                    .owner(parentWindow)
                    .masthead("Abnormal experiment renewal")
                    .message(String.format("The new expiration duration is shorter thant the current one (%d days, %d hours and %d minutes).\n\nIs this what you want?",
                            timeLeft[DateTimeUtils.DAYS],
                            timeLeft[DateTimeUtils.HOURS],
                            timeLeft[DateTimeUtils.MINUTES]))
                    .buttonTypes(ButtonType.YES, ButtonType.NO)
                    .showWarning();

            if (result.orElse(ButtonType.NO) != ButtonType.YES) {
                dialog.show();
                return;
            }
        }

        if (renewAll) {
            experimentController.renew(newExpirationTime);
        } else {
            experimentController.renewParts(newExpirationTime, selectedParts);
        }
    }
}
