package projectEQ2440.utils;

/**
 * <b>public abstract class ThreadManager extends Thread</b><br/>
 * Abstract class to implement a thread with two loop : one maintaining the thread alive and the other to apply an action
 */
public abstract class ThreadManager extends Thread {
	
	// Tell if the manager is alive and start
	private boolean alive;
	private boolean start;
	
	// Flag of wait
	private boolean waitStart;
	private boolean waitLoop;
	
	// number of loop calling
	protected int nbrLoop;
	
	// The callback
	private ThreadManagerCallback callback;
	
	/**
	 * <b>public ThreadManager()</b><br/>
	 * Simple constructor which initialized the thread
	 */
	public ThreadManager(String threadName) {
		alive = true;
		start = false;
		
		waitStart = false;
		waitLoop = false;
		
		nbrLoop = 0;
		
		callback = null;
		
		this.setName(threadName);
		
		this.start();
	}
	
	/**
	 * <b>public boolean isStart()</b><br/>
	 * Tell if the thread manager started
	 * 
	 * @return the state of thread manager
	 */
	public boolean isStart() {
		return start;
	}
	
	/**
	 * <b>public boolean startManager()</b><br/>
	 * Start the manager
	 * 
	 * @return if the start succeeded
	 */
	public boolean startManager() {
		if (!alive) return false;
		if (start) return false;
		
		start = true;
		if (waitStart) synchronized (this) { this.notify(); }
		return true;
	}
	
	/**
	 * <b>public void stopManager()</b><br/>
	 * Stop the manager
	 */
	public void stopManager() {
		nbrLoop = 1;
		start = false;
		if (waitLoop) synchronized (this) { this.notify(); }
	}
	
	/**
	 * <b>public void wakeManager()</b><br/>
	 * Wakes up the manager for a loop (or more than one loop)
	 */
	public void wakeManager() {
		nbrLoop++;
		if (waitLoop) synchronized (this) { this.notify(); }
	}
	
	/**
	 * <b>public void killManager()</b><br/>
	 * Kill the manager.<br/> 
	 * BE CAREFUL : the thread can't be regenerated after this
	 */
	public void killManager() {
		alive = false;
		stopManager();
		if (waitStart) synchronized (this) { this.notify(); }
		try {
			this.finalize();
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * <b>abstract protected void initAction() throws InterruptedException</b><br/>
	 * The initialization action before the main loop
	 * 
	 * @throws InterruptedException : The error is managed in the thread
	 */
	abstract protected void initAction() throws InterruptedException;
	
	/**
	 * <b>abstract protected void loopAction() throws InterruptedException</b><br/>
	 * The main action to apply every loop
	 * 
	 * @throws InterruptedException : The error is managed in the thread
	 */
	abstract protected void loopAction() throws InterruptedException;
	
	/**
	 * <b>abstract protected void endAction() throws InterruptedException</b><br/>
	 * The final action to apply when we leave the loop
	 * 
	 * @throws InterruptedException : The error is managed in the thread
	 */
	abstract protected void endAction() throws InterruptedException;
	
	/**
	 * <b>public void run()</b><br/>
	 * The run function of the Thread which create an alive loop which maintain the thread alive and a start loop which apply an action every time the loop is woken up
	 */
	@Override
	public void run() {
		// The alive loop which maintain the thread alive
		while (alive) {
			try {
				
				// If the manager is stop, the thread must wait here
				synchronized (this) {
					waitStart = true;
					this.wait();
					waitStart = false;
				}
				
				// We initialize the number of loop
				nbrLoop = 0;
				
				// Before beginning, we apply the initialization action
				if (callback != null) callback.start();
				initAction();
				
				// The start loop which maintain the action loop
				while (start) {
					
					// If there isn't any loop asked, the thread must wait here
					synchronized (this) {
						if (nbrLoop == 0) {
							waitLoop = true;
							this.wait();
							waitLoop = false;
						}  
						nbrLoop--;
					}
					
					// Each loop, the main loop action must be called
					if (callback != null) callback.loop();
					if (start) loopAction();
				}
				
				// When the start loop is left, we must call the final action
				endAction();
				if (callback != null) callback.end();
				
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			// Be sure that the start flag is false out of the loop (in case of error)
			start = false;
		}
		// destroy callback before end of thread
		callback = null;
	}

	/**
	 * <b>public void setCallback(ThreadManagerCallback callback)</b><br/>
	 * Set the callback to watch on the different steps of the Manager 
	 *  
	 * @param callback : The Picture Manager Callback
	 */
	public void setCallback(ThreadManagerCallback callback) {
		this.callback = callback;
	}
	
	/**
	 * <b>public interface PictureManagerCallback</b><br/>
	 * Interface for implementing the different callback of the Picture Manager
	 */
	public interface ThreadManagerCallback {
		
		/**
		 * <b>public void start()</b><br/>
		 * Callback when the Manager start
		 */
		public void start();
		
		/**
		 * <b>public void loop()</b><br/>
		 * Callback when the Manager make a new loop
		 */
		public void loop();
		
		/**
		 * <b>public void end()</b><br/>
		 * Callback when the Manager stop
		 */
		public void end();
	}
	
}
