/*
 * Created on 01.12.2003 16:41:27
 *
 * Multimediale Algorithmen und Datenstrukturen Assessments
 */
package mauda.undoredo;

import mauda.Exercise;
import mauda.ExerciseUpdateEvent;
import mauda.ExerciseUpdateListener;
//import mauda.MAUDA;
import mauda.plugin.*;

import jedas.*;
import java.util.*;
/**
 * Stores copies of the current data-structure (e.g. FibHeap),
 * so that it is possible to quickly undo/redo operations.
 * 
 * If a animated redo needed, please use AnimatedUndoRedo.
 * 
 * @author Markus Krebs
 */
public class UndoRedo implements ExerciseUpdateListener {
	
	protected UndoRedoInfo undoRedoInfo;
	
	protected Exercise exercise;
	
	protected CompPanel compPanel;
	
	protected Object dsObject;
	
	protected Vector vector;		// Speicherung der FibHeaps
	
	protected int position = -1;
	protected int minPosition = 0;
	
	/**
	 * Creates a UndoRedo-Handler.
	 * @param exercise The Exercise
	 */
	public UndoRedo(Exercise exercise) {
		this.exercise = exercise;
		this.compPanel = Exercise.compPanel;
		undoRedoInfo = new UndoRedoInfo();
		//undoRedoInfo.DEBUG = true;
		vector = new Vector();
	}
	
	/* (non-Javadoc)
	 * @see mauda.ExerciseUpdateListener#exerciseUpdate(mauda.ExerciseUpdateEvent)
	 */
	public void exerciseUpdate(ExerciseUpdateEvent e) {
		if(e.getID() == ExerciseUpdateEvent.BACK) {
			back(); 
			e.getExercise().setDSObject(getDSObject());
		} else if(e.getID() == ExerciseUpdateEvent.FORWARD) {
			forward();
			e.getExercise().setDSObject(getDSObject());
		} else if(e.getID() == ExerciseUpdateEvent.FORWARD_ANIMATED) {
			// Folgendes Forward von AnimatedUndoRedo erzeugt eine neue auszuführende
			// Operation
			// => START_OF_EXECUTION + OPERATION_EXECUTED ... Messages
			forward();	// <-- von AnimatedUndoRedo
		}
		else if(e.getID() == ExerciseUpdateEvent.UNDO) undo();
		else if(e.getID() == ExerciseUpdateEvent.REDO) redo();
		else if(e.getID() == ExerciseUpdateEvent.OPERATION_EXECUTED) {
			snapshot(e.getOperation());
		} else if(e.getID() == ExerciseUpdateEvent.RESET) {
			clear();
			e.getExercise().setDSObject(getDSObject());			
		} else if(e.getID() == ExerciseUpdateEvent.CLEAR_PRECEDING) {
			clearPreceding();
		} else if(e.getID() == ExerciseUpdateEvent.CLEAR_FOLLOWING) {
			clearFollowing();
		} else if(e.getID() == ExerciseUpdateEvent.JUMP) {
			position += e.getValue();
			//System.out.println("UndoRedo: position = "+position+" / op = "+((AnimatedUndoRedo)this).operations.elementAt(position));
			compPanel.clear();
			dsObject = ((Copyable)vector.elementAt(position)).deepCopy();
			compPanel.addItem(((JedasMainCompObj)dsObject).getMainCompObj());
			Jedas.updateDisplay();
			e.getExercise().setDSObject(dsObject);
		}
	}
	
	/**
	 * Creates a snapshot for a given operation.<br>In this data-structure (UndoRedo) there
	 * must be always one entry at the beginning because if the user jumps to the beginning
	 * of the exercise a valid data-structure (e.g. an empty one) is necessary. This method
	 * generates this initial data-structure.
	 * @param o generally deliver <code>null</code>
	 */
	public void initialSnapshot(Object o) {
		snapshot(o);
	}
	
	// Snapshot des CompPanel's machen
	protected void snapshot(Object o) {
		int counter = compPanel.getItemCount();
		//System.out.println("#Items: "+counter);
		Item item = compPanel.getFirstItem();
		//System.out.println("SNAPSHOT-Item: "+item);
		/*
		// Das Richtige müsste immer das erste Item sein
		do {
			//System.out.println(item);
			if(item instanceof FibHeapExt) break;	// Bekanntes Objekt gefunden
			item = compPanel.getNextItem();
			counter--;			
		} while(counter>0);
		// Testen ob ein bekanntes Objekt gefunden wurde
		if(!(item instanceof FibHeapExt)) {
			javax.swing.JOptionPane.showMessageDialog(MAUDA.getApplicationMainFrame(), "UndoRedo.snapshot: No FibHeap found");
			return;
		}*/
		// Undo-Information:
		position++;
		undoRedoInfo.storeChange(position, vector, ((Copyable)exercise.getDSObject()).deepCopy());
		//while (vector.size() > position) vector.removeElementAt(position);
		//vector.add(((Copyable)exercise.getDSObject()).deepCopy());
	}
	/**
	 * Check if a step back is possible
	 * @return Returns true, if possible, false, otherwise
	 */
	public boolean canBack() { return position>minPosition; }
	/**
	 * Check if a step forward is possible
	 * @return Returns true, if possible, false, otherwise
	 */
	public boolean canForward() { return position<vector.size()-1; }
	/**
	 * Check if an undo is possible
	 * @return Returns true, if possible, false, otherwise
	 */
	public boolean canUndo() {
		return position>=minPosition&&undoRedoInfo.canUndo();
	}
	/**
	 * Check if a redo is possible
	 * @return Returns true, if possible, false, otherwise
	 */
	public boolean canRedo() { return undoRedoInfo.canRedo(); }
	
	/**
	 * Returns the new delta-position to jump to, when the next action should be an undo.
	 * @return relative jump-delta
	 */
	public int getUndoJumpDelta() {
		int p = undoRedoInfo.getUndoJumpPosition();
		//if(position<=p) return 0;	// Beim Undo Position beibehalten?
		return p-position;
	}
	/**
	 * Returns the new delta-positoin to jump to, when the next action should be a redo.
	 * @return relative jump-delta
	 */
	public int getRedoJumpDelta() {
		int p = undoRedoInfo.getRedoJumpPosition();
		//if(position+1<=p) return 0;	// Beim Redo Position beibehalten?
		return p-(position+1);
	}
	/**
	 * Performs an back.
	 */
	protected void back() {
		if(!canBack()) return;
		position--;
		compPanel.clear();
		dsObject = ((Copyable)vector.elementAt(position)).deepCopy();
		compPanel.addItem(((JedasMainCompObj)dsObject).getMainCompObj());
		//System.out.println("UndoRedo.undo(): compPanel.addItem: "+dsObject);
		Jedas.updateDisplay();
	}
	/**
	 * Performs a forward
	 */
	protected void forward() {
		if(!canForward()) return;
		position++;
		compPanel.clear();
		dsObject = ((Copyable)vector.elementAt(position)).deepCopy();
		compPanel.addItem(((JedasMainCompObj)dsObject).getMainCompObj());
		Jedas.updateDisplay();
	}
	protected void undo() {
		UndoRedoUnit uru = undoRedoInfo.undo(vector);
		//exercise.jumpToEnd();
	}
	protected void redo() {
		UndoRedoUnit uru = undoRedoInfo.redo(vector);
		//exercise.jumpToEnd();
	}
	/**
	 * Clears all entries in the undo-redo-queue 
	 */
	public void clear() {
		minPosition = 0;
		position = 1;
		back();
		Object o = vector.elementAt(0);
		
		Vector newv = new Vector();
		newv.add(o);
		undoRedoInfo.clear(newv);

		vector.removeAllElements();
		vector.addAll(newv);		
	}
	protected void clearPreceding() {
		position = 1;
		Object o = vector.elementAt(0);
		Object o2 = vector.lastElement();

		Vector newv = new Vector();
		newv.add(o);
		newv.add(o2);
		//undoRedoInfo.clearPreceding(vector, newv);
		undoRedoInfo.clear(newv);
		
		vector.removeAllElements();
		vector.addAll(newv);
		minPosition = 1;
	}
	protected void clearFollowing() {
		undoRedoInfo.clearFollowing(position+1, vector);
		//while (vector.size() > position+1)
		//	vector.removeElementAt(position+1);		
	}
	/**
	 * Method for retrieving the current data-structure-object,
	 * @return The data-structure-object, e.g. FibHeap
	 */
	public Object getDSObject() {
		return dsObject;
	}
	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		return	"#Entries: "+vector.size()+"\n"+
				"Position: "+position+"\n";
	}
	/**
	 * Methode for retrieving a specific data-structure-object
	 * @param offset The offset inside the undo-redo-queue
	 * @return The data-structure-object, e.g. FibHeap
	 */
	// DSObject an einer bestimmten Position zurückgeben
	public Object getEntry(int offset) {
		return vector.elementAt(offset+1);	// Einträge um 1 verschoben bzgl. OperationRecorder
	}
}
