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

import be.iminds.ilabt.jfed.experiment.Experiment;
import be.iminds.ilabt.jfed.experiment.ExperimentState;
import be.iminds.ilabt.jfed.experiment.util.NextExperimentExpiration;
import be.iminds.ilabt.jfed.experiment.util.NextExperimentExpirationBinding;
import be.iminds.ilabt.jfed.experimenter_gui.slice.events.RenewExperimentEvent;
import be.iminds.ilabt.jfed.preferences.JFedGuiPreferences;
import com.google.common.eventbus.EventBus;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import javafx.application.Platform;
import javafx.util.Duration;
import org.controlsfx.control.Notifications;
import org.controlsfx.control.action.Action;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * User: twalcari
 * Date: 8/13/2015
 * Time: 17:17
 */
public class ExpirationNotifier {
    private static final Logger LOG = LoggerFactory.getLogger(ExpirationNotifier.class);
    private static final ScheduledExecutorService executor =
            Executors.newSingleThreadScheduledExecutor(new ThreadFactoryBuilder().setNameFormat("ExpirationNotifier-%d").build());

    private final JFedGuiPreferences jFedPreferences;
    private final EventBus eventBus;

    private final ExperimentViewController experimentViewController;
    private final Experiment experiment;

    private ScheduledFuture<?> scheduledFuture = null;

    private boolean warningShown = false;
    private Instant expirationWarningInstant = null;
    private final NextExperimentExpirationBinding nextExperimentExpirationBinding;

    public ExpirationNotifier(ExperimentViewController experimentViewController, JFedGuiPreferences jFedPreferences, EventBus eventBus) {
        this.experimentViewController = experimentViewController;
        this.experiment = experimentViewController.getExperiment();
        this.jFedPreferences = jFedPreferences;
        this.eventBus = eventBus;

        this.nextExperimentExpirationBinding = new NextExperimentExpirationBinding(experiment);

        nextExperimentExpirationBinding.addListener(observable ->
                {
                    onExpirationTimesUpdated();
                    scheduleNextCheck();
                }
        );
    }

    private void scheduleNextCheck() {
        if (expirationWarningInstant != null) {
            if (scheduledFuture != null)
                scheduledFuture.cancel(false);

            final long secondsLeft =
                    ChronoUnit.SECONDS.between(Instant.now(), expirationWarningInstant);

            if (secondsLeft < 0) {
                LOG.info("Experiment already expired. Stopping ExpirationNotifier checks");
                return;
            }

            LOG.info("Schedule next check for {} in {} seconds.", experiment.getName(), secondsLeft);

            scheduledFuture = executor.schedule(this::checkToShowWarning, secondsLeft, TimeUnit.SECONDS);
        } else {
            LOG.warn("Cannot schedule next check for {}, as no expiration time is available.", experiment.getName());
        }
    }

    private void checkToShowWarning() {
        if (warningShown) {
            LOG.trace("Expiration warning for {} already shown. Ignoring.", experiment.getName());
            return;
        }

        if (experiment.getExperimentState() != ExperimentState.TESTING && experiment.getExperimentState() != ExperimentState.READY) {
            LOG.debug("Not checking for expiration as experiment is currently {}", experiment.getExperimentState());
            return;
        } else {
            LOG.trace("Checking for expiration, as slice state is {}", experiment.getExperimentState());
        }

        if (expirationWarningInstant == null) {
            LOG.info("Could not find a valid expiration time to compare against for experiment {}", experiment.getName());
            return;
        }

        assert expirationWarningInstant.isBefore(Instant.now());

        showExpirationWarning();
    }

    private void onExpirationTimesUpdated() {
        warningShown = false;

        NextExperimentExpiration nextExperimentExpiration = nextExperimentExpirationBinding.get();

        if (nextExperimentExpiration != null && nextExperimentExpiration.getFirstExpirationTime() != null) {
            expirationWarningInstant =
                    nextExperimentExpiration.getFirstExpirationTime().minus(jFedPreferences.getExpirationWarningTime(), ChronoUnit.MINUTES);
        } else {
            expirationWarningInstant = null;
        }

    }

    private void showExpirationWarning() {
        NextExperimentExpiration nextExperimentExpiration = nextExperimentExpirationBinding.get();
        assert nextExperimentExpiration != null;
        assert nextExperimentExpiration.getFirstExpirationTime() != null;

        long fullMinutesUntilExpiration =
                Instant.now().until(nextExperimentExpiration.getFirstExpirationTime(), ChronoUnit.MINUTES);

        if (jFedPreferences.isExpirationWarningEnabled() && fullMinutesUntilExpiration >= 0) {

            StringBuilder message = new StringBuilder();

            if (nextExperimentExpiration.getFirstExpiringPart() == null) {
                message.append("The experiment '").append(experiment.getName()).append("' is");
            } else {
                message.append("The resources on ").append(nextExperimentExpiration.getFirstExpiringPart().getName())
                        .append(" of experiment '").append(experiment.getName()).append("' are");
            }


            message.append(" expiring in less then ").append(fullMinutesUntilExpiration + 1).append(" minutes.");


            Action renewAction = new Action("Renew now",
                    event -> eventBus.post(new RenewExperimentEvent(experimentViewController.getScene().getWindow(),
                            experimentViewController.getExperimentController())));


            LOG.info("Showing expiration warning for experiment {} to user.", experiment.getName());

            Platform.runLater(() -> Notifications.create()
                    .title(nextExperimentExpiration.getFirstExpiringPart() == null
                            ? "Experiment expiring!" : "Resources expiring!")
                    .text(message.toString())
                    .action(renewAction)
                    .hideAfter(Duration.millis(java.time.Duration.between(Instant.now(), nextExperimentExpiration.getFirstExpirationTime()).toMillis()))
                    .showWarning());


        } else {
            LOG.info("Not showing expiration warning, as user disabled it!");
        }

        warningShown = true;
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        scheduledFuture.cancel(true);
    }
}
