package eu.the5zig.mod.manager;

import com.google.common.collect.Queues;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListenableFutureTask;
import eu.the5zig.mod.The5zigMod;
import eu.the5zig.mod.event.EventHandler;
import eu.the5zig.mod.event.TickEvent;

import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

public class Scheduler {

	private final Queue<Task> jobs = Queues.newArrayDeque();

	@EventHandler
	public void onTick(TickEvent event) {
		synchronized (jobs) {
			for (int i = 0; i < jobs.size(); i++) {
				Task task = jobs.peek();
				if (++task.tickCount >= task.delay) {
					try {
						((FutureTask) task.listenableFuture).run();
					} catch (Throwable e) {
						The5zigMod.logger.error("Could not execute task", e);
					}
					jobs.remove(task);
				}
			}
		}
	}

	/**
	 * Posts a runnable to the Main Server Thread.
	 * <p/>
	 *
	 * @param runnable The runnable that should be executed.
	 * @return a Listenable Future.
	 */
	public ListenableFuture postToMainThread(Runnable runnable) {
		return postToMainThread(runnable, false);
	}

	/**
	 * Posts a runnable to the Main Server Thread.
	 * <p/>
	 *
	 * @param runnable The runnable that should be executed.
	 * @return a Listenable Future.
	 */
	public ListenableFuture postToMainThread(Runnable runnable, boolean noThreadCheck) {
		return postToMainThread(runnable, noThreadCheck, 0);
	}

	/**
	 * Posts a runnable to the Main Server Thread.
	 * <p/>
	 *
	 * @param runnable The runnable that should be executed.
	 * @return a Listenable Future.
	 */
	public ListenableFuture postToMainThread(Runnable runnable, boolean noThreadCheck, int delay) {
		Callable callable = Executors.callable(runnable);
		ListenableFuture listenableFuture = ListenableFutureTask.create(callable);
		if (noThreadCheck || delay > 0 || !The5zigMod.getVars().isMainThread()) {
			synchronized (jobs) {
				jobs.add(new Task(listenableFuture, delay));
				return listenableFuture;
			}
		} else {
			try {
				return Futures.immediateFuture(callable.call());
			} catch (Exception e) {
				return Futures.immediateFailedCheckedFuture(e);
			}
		}
	}

	private class Task {

		private ListenableFuture listenableFuture;
		private int delay;
		private int tickCount;

		public Task(ListenableFuture listenableFuture, int delay) {
			this.listenableFuture = listenableFuture;
			this.delay = delay;
		}
	}
}