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

import be.iminds.ilabt.jfed.lowlevel.JFedException;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Task: a Runnable with lots of extra info
 */
public abstract class Task {
    private static final AtomicLong nextId = new AtomicLong();
    public final List<TaskThread.FutureTask> futureTasks = new ArrayList<>();
    protected final String name;
    private final List<TaskExecution> currentActiveTaskExecutions = Collections.synchronizedList(new ArrayList<>());
    private final List<TaskFinishedCallback> callbacks = new ArrayList<>();
    //private helper that makes sure all dependencies STAY the same objects
    protected List<Task> depCache = null;
    //private helper that makes sure all dependencies STAY the same objects
    protected List<Task> alwaysDepCache = null;
    JavaFXTaskThread.Task javafx;
    private final long id = nextId.getAndIncrement();
    private TaskExecution lastCompletedTaskExecution = null;

    public Task(String name) {
        this.name = name;
    }

    /**
     * execute the call and throw a GeniException when needed.
     * The FXTaskExecution this is running is is provided.
     */
    protected abstract void doTask(TaskExecution taskExecution) throws JFedException, InterruptedException;

    /**
     * this runs when the task is cancelled becuase one or more of the deps failed.
     */
    protected void onCancelledDueToDependencyFailed() {
        //do nothing
    }

    /**
     * override to specify dependencies
     * this is called only once
     */
    public List<Task> initDependsOn() {
        return Collections.emptyList();
    }

    /**
     * Override this method to specify dependencies that should ALWAYS run,
     * even if the same call has been made in the past.
     * this is called only once
     */
    public List<Task> initAlwaysDependsOn() {
        return Collections.emptyList();
    }

    /**
     * Is the call still needed?
     * This checks if the data that the call retrieves is already available, or the action is already done.
     * <p/>
     * default: needed as long as this task has never completed successfully
     */
    public boolean newExecutionNeeded() {
        return lastCompletedTaskExecution == null || lastCompletedTaskExecution.getState() != TaskExecution.TaskState.SUCCESS;
    }

    public String getName() {
        return name;
    }

    public String toString() {
        String res = "Task(\"" + getName() + "";
        synchronized (currentActiveTaskExecutions) {
            if (currentActiveTaskExecutions.size() > 0)
                res += " active={";
            boolean first = true;
            for (TaskExecution st : currentActiveTaskExecutions) {
                if (!first)
                    res += ", ";
                res += st.getState();
                first = false;
            }
            if (currentActiveTaskExecutions.size() > 0)
                res += "}";
        }
        if (lastCompletedTaskExecution != null)
            res += " last=" + lastCompletedTaskExecution.state;
        res += ")";
        return res;
    }

    protected List<Task> getDependsOn() {
        if (depCache == null) {
            depCache = new ArrayList<>(initDependsOn());

            for (Task t : depCache) {
                assert (t != null) : "DependsOn-list of task " + getName() + " contains a null value";
            }

        }

        return depCache;
    }

    protected List<Task> getAlwaysDependsOn() {
        if (alwaysDepCache == null) {
            alwaysDepCache = new ArrayList<>(initAlwaysDependsOn());
            for (Task t : alwaysDepCache) {
                assert (t != null) : "AlwaysDependsOn-list of task " + getName() + " contains a null value";
            }
        }
        return alwaysDepCache;
    }

    public synchronized boolean isActive() {
        return !currentActiveTaskExecutions.isEmpty();
    }

    public synchronized int getActiveTaskCount() {
        return currentActiveTaskExecutions.size();
    }

    synchronized void addActiveTaskExecution(TaskExecution taskExecution) {
        currentActiveTaskExecutions.add(taskExecution);
    }

    synchronized boolean removeActiveTaskExecution(TaskExecution taskExecution) {
        return currentActiveTaskExecutions.remove(taskExecution);
    }

    public synchronized List<TaskExecution> getCurrentActiveTaskExecutionsCopy() {
        synchronized (currentActiveTaskExecutions) {
            return new ArrayList<>(currentActiveTaskExecutions);
        }
    }

    synchronized void addFuture(TaskThread.FutureTask futureTask) {
        futureTasks.add(futureTask);
    }

    synchronized boolean removeFuture(TaskThread.FutureTask futureTask) {
        return futureTasks.remove(futureTask);
    }

    synchronized List<TaskThread.FutureTask> getFuturesCopy() {
        return new ArrayList<>(futureTasks);
    }

    /*
   * WARNING: No guarantee on what thread this callback will be called!
   * In particular: it most likely is not the JavaFX thread
   *
   * Note: All Call logging callbacks are already done when this callback is called.
   * */
    public void addCallback(TaskFinishedCallback callback) {
        callbacks.add(callback);
    }

    public void removeCallback(TaskFinishedCallback callback) {
        callbacks.remove(callback);
    }

    public List<TaskFinishedCallback> getCallbacks() {
        return Collections.unmodifiableList(callbacks);
    }

    /**
     * returns a CountDownLatch, that when waited for will unlock once the task is finished.
     */
    public CountDownLatch getCompletionCountDownLatch() {
        final CountDownLatch latch = new CountDownLatch(1);

        addCallback((task, taskExecution, state) -> latch.countDown());

        return latch;
    }

    public long getId() {
        return id;
    }

    public TaskExecution getLastCompletedTaskExecution() {
        return lastCompletedTaskExecution;
    }

    /**
     * Only to be called by TaskThread
     */
    void setLastCompletedTaskExecution(TaskExecution lastCompletedTaskExecution) {
        this.lastCompletedTaskExecution = lastCompletedTaskExecution;
    }
}
