package com.eleybourn.bookcatalogue.utils;

import java.io.Serializable;
import java.util.Comparator;
import java.util.PriorityQueue;

import com.eleybourn.bookcatalogue.utils.SimpleTaskQueue.SimpleTask;
import com.eleybourn.bookcatalogue.utils.SimpleTaskQueue.SimpleTaskContext;

/**
 * Class to execute Runnable objects in a separate thread after a predetermined delay.
 * 
 * @author pjw
 */
public class Terminator {
	
	/**
	 * Dummy method to make sure static initialization is done. Need to be 
	 * called from main thread (usually at app startup).
	 */
	public static void init() {
	}

	/** Task queue to get book lists in background */
	private static final SimpleTaskQueue mTaskQueue = new SimpleTaskQueue("Terminator", 1);

	/** Flag indicating the main thread process is still running and waiting for 
	 *  a timer to elapse.
	 */
	private static boolean mIsRunning = false;
	/** Object used in synchronization */
	private static final Object mWaitObject = new Object();

	/** Details of the runnable to run */
	private static class Event {
		public final long time;
		public final Runnable runnable;
		public Event(Runnable r, long time) {
			runnable = r;
			this.time = time;
		}
	}

	/** Comparator to ensure Event objects are returned in the correct order */
	private static class EventComparator implements Comparator<Event>, Serializable {
		private static final long serialVersionUID = 1L;

		@Override
		public int compare(Event lhs, Event rhs) {
			if (lhs.time < rhs.time) {
				return -1;
			} else if (lhs.time > rhs.time) {
				return 1;
			} else {
				return 0;
			}
		}
	}

	/** Queue of Event objects currently awaiting execution */
	private static final PriorityQueue<Event> mEvents = new PriorityQueue<Event>(10, new EventComparator());

	/**
	 * Enqueue the passed runnable to be run after the specified delay.
	 * 
	 * @param r			Runnable to execute
	 * @param delay		Delay before execution
	 */
	public static void enqueue(Runnable r, long delay) {
		// Compute actual time
		long time = System.currentTimeMillis() + delay;
		// Create Event and add to queue.
		Event e = new Event(r, time);
		synchronized(mTaskQueue) {
			mEvents.add(e);
			// Make sure task is actually running
			if (!mIsRunning) {
				mTaskQueue.enqueue(new TerminatorTask());
				mIsRunning = true;
			} else {
				// Wake up task in case this object has a shorter timer
				synchronized(mWaitObject){
					mWaitObject.notify();					
				}
			}
		}
	}

	/**
	 * Background task to process the queue and schedule appropriate delays
	 * 
	 * @author pjw
	 */
	private static class TerminatorTask implements SimpleTask {

		@Override
		public void run(SimpleTaskContext taskContext) throws Exception {
			System.out.println("Terminator starting");
			do {
				Event e;
				long delay;
				// Check when next task due
				synchronized(mTaskQueue) {
					// Lok for a task; if exception or none found, abort.
					try {
						e = mEvents.peek();						
					} catch(Exception ex) {
						e = null;
					}
					if (e == null) {
						mIsRunning = false;
						return;
					}
					// Check how long until it should run
					delay = e.time - System.currentTimeMillis();
					// If it's due now, then remove it from the queue.
					if (delay <= 0)
						mEvents.remove(e);
				}

				if (delay > 0) {
					// If we have nothing to run, wait for first
					synchronized(mWaitObject) {
						try {	
							mWaitObject.wait(delay);
						} catch(Exception ex) {
							Logger.logError(ex);
						}
					}
				} else {
					// Run the available event
					// TODO: if 'run' blocks, then our Terminator stops terminating! Should probably be another thread. But...not for now.
					try {
						e.runnable.run();
					} catch(Exception ex) {
						Logger.logError(ex);
					}
				}
			} while (true);
		}

		@Override
		public void onFinish(Exception e) {
			System.out.println("Terminator terminating. I'll be back.");
		}
		
	}
}
