package de.rico.adventure.actions;

import java.util.LinkedList;
import java.util.List;

import javax.media.opengl.GL;

import de.rico.adventure.actions.custom.RunCode;
import de.rico.adventure.actions.custom.concurrent.AbstractConcurrentAction;
import de.rico.adventure.script.ScriptInterpreter;

/**
 * This is a very important class in the adventure framework. It is used to
 * process game actions sequencially and makes use of the singleton design
 * pattern.
 * <br>
 * <br>
 * <b>Note:</b> Don't forget to call the update() and draw()
 * methods of the singleton instance in the game's main update()
 * and draw() methods.
 *
 * @author Frank Bruns
 */
public class ActionScheduler
{
	private static ActionScheduler instance = new ActionScheduler();

	private LinkedList<AbstractAction> queue = null;
	private LinkedList<AbstractConcurrentAction> queueConcurrent = null;
	
	/**
	 * The objects in this list will get added to the list of
	 * concurrent game actions during the next update cycle.
	 *
	 * <b>Note:</b> Don't reference this object in your code directly! It had
	 * to be declared <code>public</code> to circumvent some trouble with scripting.
	 */
	public LinkedList<AbstractConcurrentAction> concurrentActionsToAdd = null;
	private LinkedList<AbstractConcurrentAction> concurrentActionsToRemove = null;
	
	/**
	 * Private singleton constructor.
	 *
	 */
	private ActionScheduler()
	{
		this.queue = new LinkedList<AbstractAction>();
		
		this.concurrentActionsToAdd = new LinkedList<AbstractConcurrentAction>();
		this.concurrentActionsToRemove = new LinkedList<AbstractConcurrentAction>();
		this.queueConcurrent = new LinkedList<AbstractConcurrentAction>();
	}

	/**
	 * Returns the singleton instance of the action scheduler.
	 *
	 * @return instance of the action scheduler
	 */
	public static ActionScheduler getInstance()
	{
		return instance;
	}

	/**
	 * Returns the queue object that is used for the game actions queue.
	 *
	 * @return the queue object
	 */
	public LinkedList<AbstractAction> getQueue()
	{
		return this.queue;
	}

	/**
	 * Adds a game action to the queue of game actions.
	 *
	 * @param action game action to add to the game action queue
	 */
	public void add(AbstractAction action)
	{
		this.queue.add(action);
	}

	/**
	 * Adds a game action to the concurrent queue of game actions.
	 * The update() and draw() methods of each action in this list
	 * gets called every cycle.
	 * <br>
	 * <br>
	 * <b>Note:</b> Be aware of potential game state conflicts when
	 * adding concurrent actions that are changing the game's state while running.
	 * I recommend only to run game actions in a concurrent way, if they
	 * do not influence the current game state. Also be aware that concurrent
	 * game actions cannot be aborted manually or prematurely.
	 *
	 * @param action game action to add to the concurrent game action queue
	 * @param i reference to the script interpreter of the current room
	 */
	public void add(AbstractConcurrentAction action, ScriptInterpreter i)
	{
		// make action visible for script code to be callable in the RunCode() action
		String scriptId = "action"+Integer.toString(action.hashCode());
		i.bindVariable(scriptId, action);

		this.queue.add(new RunCode("scheduler.concurrentActionsToAdd.add("+scriptId+")",i));
	}
	

	/**
	 * Removes every game action from the queue of game actions
	 */
	public void clear()
	{
		// save actions to temporary list
		List<AbstractAction> list = new LinkedList<AbstractAction>();
		list.addAll(this.queue);

		// clear original list of actions
		this.queue.clear();

		// call the finishUp() method of the saved actions
		for(AbstractAction action : list)
			action.finishUp();
	}

	/**
	 * Removes first element in the game action queue.
	 */
	public void removeFirst()
	{
		if(this.queue.size() > 0)
		{
			this.getFirst().finishUp();
			this.queue.remove();
		}
	}

	/**
	 * Removes the specified concurrent action from the concurrent game action queue.
	 */
	public void removeConcurrentAction(AbstractConcurrentAction action)
	{
		this.concurrentActionsToRemove.add(action);				
	}

	/**
	 * Returns first element of the game action queue without removing it. It
	 * return <code>null</code> if the queue is empty.
	 *
	 * @return first element of the game action queue
	 */
	public AbstractAction getFirst()
	{
		return this.queue.peek();
	}

	/**
	 * Returns the first element of the queue and removes it at the same time
	 * from the queue.
	 *
	 * @return first element of the game action queue
	 */
	public AbstractAction poll()
	{
		if(this.queue.size() > 0) this.getFirst().finishUp();
		return this.queue.poll();
	}

	/**
	 * Returns the list of with the actions of the game actions queue.
	 *
	 * @return list of game actions
	 */
	public LinkedList<AbstractAction> getGameActions()
	{
		LinkedList<AbstractAction> actions = new LinkedList<AbstractAction>();

		for (AbstractAction a : this.queue)
		{
			actions.add(a);
		}

		return actions;
	}

	/**
	 * Updates the game action that is currently on top of the queue.
	 *
	 * @param elapsedTime time between two frames
	 */
	public void update(long elapsedTime)
	{
		if(this.queue.peek() != null)
			this.queue.peek().update(elapsedTime);

		// remove concurrent actions
		for(AbstractConcurrentAction a : this.concurrentActionsToRemove)
		{
			a.finishUp();
			this.queueConcurrent.remove(a);
		}
		this.concurrentActionsToRemove.clear();
		
		// add actions to the concurrent actions queue
		this.queueConcurrent.addAll(this.concurrentActionsToAdd);
		this.concurrentActionsToAdd.clear();
		
		// update every concurrent game action
		for(AbstractConcurrentAction cga : this.queueConcurrent)
			cga.update(elapsedTime);
	}

	/**
	 * Draws the currently active game action.
	 * @param gl
	 */
	public void draw(GL gl)
	{
		if(this.queue.peek() != null)
			this.queue.peek().draw(gl);

		// draw every concurrent game action
		for(AbstractConcurrentAction cga : this.queueConcurrent)
			cga.draw(gl);
	}
}
