package be.iminds.ilabt.jfed.highlevel.jobs;

import be.iminds.ilabt.jfed.experiment.tasks.ExperimentTaskStatus;
import be.iminds.ilabt.jfed.lowlevel.JFedException;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * A state contains (part of ) the "real" logic of a job.
 * Typically, a state makes one or more calls to an external service to perform actions.
 * <p>
 * User: twalcari
 * Date: 11/25/2015
 * Time: 10:11
 */
public abstract class State {
    private static final Logger LOG = LoggerFactory.getLogger(State.class);
    private final String name;
    private final ReadOnlyStringWrapper message = new ReadOnlyStringWrapper();
    private final ReadOnlyObjectWrapper<ExperimentTaskStatus> status =
            new ReadOnlyObjectWrapper<>(ExperimentTaskStatus.INACTIVE);

    private final List<StateFinishedListener<? extends State>> stateFinishedListeners =
            new ArrayList<>();

    protected State(String name) {
        this.name = name;
    }

    /**
     * This method takes care of the internal bookkeeping of the internal status, and calls the StateFinishedListeners
     * when the main method ({@link State#executeState}) has finished.
     *
     * @param job The job in which the State must run
     * @return The resulting status of the State. This can only be SUCCESS, WARNING or FAILED
     */
    public final ExperimentTaskStatus run(Job<?> job) {
        updateStatus(ExperimentTaskStatus.BUSY);

        ExperimentTaskStatus finalStatus;
        try {
            finalStatus = executeState(job);
        } catch (AssertionError | InterruptedException | JFedException e) {
            finalStatus = ExperimentTaskStatus.FAILED;
            LOG.error("Exception while executing {}: {}", getName(), e.getMessage(), e);
        }

        assert (finalStatus == ExperimentTaskStatus.SUCCESS ||
                finalStatus == ExperimentTaskStatus.WARNING ||
                finalStatus == ExperimentTaskStatus.FAILED) : "Unexpected final status";

        updateStatus(finalStatus);

        for (StateFinishedListener sfl : stateFinishedListeners) {
            try {
                //noinspection unchecked
                sfl.onStateFinished(this);
            } catch (RuntimeException ex) {
                LOG.error("Runtime Exception while executing StateFinishedListener: {}", ex.getMessage(), ex);
            }
        }
        return finalStatus;
    }

    @Nonnull
    /**
     * Method containing the main logic of the state
     */
    protected abstract ExperimentTaskStatus executeState(Job<?> job) throws InterruptedException, JFedException;

    /**
     * @return Name of the State. Typically visible to the end-user
     */
    public final String getName() {
        return name;
    }

    /**
     * @return Current message of the state. Typically visible to the end-user while the state is active.
     */
    public final String getMessage() {
        return message.get();
    }

    public final ReadOnlyStringProperty messageProperty() {
        return message.getReadOnlyProperty();
    }


    private void updateStatus(@Nonnull final ExperimentTaskStatus newStatus) {
        FutureTask<Void> updateStatusFuture = new FutureTask<>(() -> status.set(newStatus), null);
        Platform.runLater(updateStatusFuture);

        try {
            updateStatusFuture.get();
        } catch (InterruptedException | ExecutionException e) {
            LOG.error("Error while changing status of state", e);
        }
    }

    /**
     * Update the current message of the state
     *
     * @param message New message
     */
    protected void updateMessage(final String message) {
        Platform.runLater(() -> this.message.set(message));
    }

    public final ExperimentTaskStatus getStatus() {
        return status.get();
    }

    public final ReadOnlyObjectProperty<ExperimentTaskStatus> statusProperty() {
        return status.getReadOnlyProperty();
    }

    public void registerStateFinishedListener(StateFinishedListener<?> stateFinishedListener) {
        this.stateFinishedListeners.add(stateFinishedListener);
    }

}
