/*
 * Created on 29.01.2004 15:40:54
 *
 * Multimediale Algorithmen und Datenstrukturen Assessments
 */
package mauda.feedback;

import mauda.*;
import mauda.utils.*;
import mauda.operation.*;
import mauda.feedback.select.*;
import mauda.feedback.types.FaultFeedback;
import mauda.undoredo.UndoRedoInfo;

import java.util.*;

import javax.swing.JOptionPane;
/**
 * The base-class for feedback-types. All specific feedback-
 * types have to extend this class.
 * 
 * @author Markus Krebs
 */

public abstract class SimpleFeedback implements ExerciseUpdateListener {
	
	protected FeedbackExercise exercise;
	
	protected Evaluator evaluator;
	
	public static FeedbackGenerator feedbackGenerator;
		
	protected boolean finishedLoading;
	
	protected boolean finishedExercise;
	
	// Gibt an ob beim Laden einer angefangenen Aufgabe die
	// Konfiguration (FEEDBACK_CONFIG) schon gemacht wurde
	// siehe (P1) IF&EC: interactiveEvent
	protected boolean configurated;

	// ==============================================
	// Aktuelles FeedbackObject vermerken, um mehrstufiges
	// Feedback zu erlauben
	protected FeedbackObject currentFeedbackObject;
	//protected Vector todoOperations;
	protected OperationQueue todoOperationQueue;
	protected OperationQueue initOperationQueue;
	protected String specialState;

	protected int feedbackBound;
	
	/**
	 * Defines the amount of the maximal tries for incorrect
	 * operations.
	 */
	// Maximale Fehlversuche pro Operation
	public static final int MAX_TRIES = 3;
	
	// Angabe auf welcher Operation wir gerade arbeiten
	protected int todoPosition;
	// Maximal bearbeitete todoPosition
	// wird für update der Task-Message benötigt
	protected int maxTodoPosition;
	// Genaue Positionsangabe innerhalb aller Operationen
	protected int counter;

	// Abspeichern des jeweils ersten Fehlers (FeedbackObject) einer Operation
	// Diese Information kann nicht aus feedbackObjects geholt werden,
	// da ADDITIONAL_FALSE und MISSING_SUBOP
	protected Vector firstFailureFeedbackObjects;
	protected Vector FFFOVector;
	
	private UndoRedoInfo undoRedoInfo;
	private UndoRedoInfo undoRedoInfoFFFO;
	
	// Abspeichern der FeedbackObjekte direkt zu den Operationen
	protected Vector feedbackObjects;
	
	// Flag wird auf TRUE gesetzt, wenn bei einem interactiveEvent eine andere, als eine
	// eventuell bereits vorhandene Operation ausgeführt wird.
	// -> wird bei preInteractiveEvent gesetzt und bei postInteractiveEvent zurückgesetzt.
	// => darf nur bei interactiveEvent abgefragt werden (siehe verschiedene Feedback-Typen).
	protected boolean differentOperation;

	// ==============================================

	/**
	 * Creates a SimpleFeedback
	 * @param exercise FeedbackExercise
	 */	
	public SimpleFeedback(FeedbackExercise exercise) {
		
		this.exercise = exercise;
		
		// NEU
		exercise.getFeedbackPanel().disableAllButtons();
		
		exercise.getFeedbackPanel().hideDemandButton();
		
		if(this instanceof mauda.feedback.types.FaultFeedback) 
			setEvaluator(new FaultEvaluator());
		else
			setEvaluator(new NormalEvaluator());
		
		finishedLoading = false;
		finishedExercise = false;

		// Ab welcher Position soll feedback erlaubt sein
		// wirkt sich auf makeNextLink() aus
		feedbackBound = -1;
		
		counter = -1;
		todoPosition = -1;
		maxTodoPosition = -1;
				
		configurated = true;
		
		undoRedoInfo = new UndoRedoInfo();
		//undoRedoInfo.DEBUG = true;
		undoRedoInfoFFFO = new UndoRedoInfo();
		//undoRedoInfoFFFO.DEBUG = true;	// UndoRedoInfoFFFO DEBUG=ON

		currentFeedbackObject = null;
		firstFailureFeedbackObjects = new Vector();
		feedbackObjects = new Vector();
		
		FFFOVector = new Vector();
		
		todoOperationQueue = null;
		initOperationQueue = null;
	}
	
	/**
	 * Gets the FeedbackGenerator
	 * @return FeedbackGenerator
	 */
	public FeedbackGenerator getFeedbackGenerator() {
		return feedbackGenerator;
	}
	/**
	 * Sets the Evaluator
	 * @param e Evaluator (NormalEvaluator or FaultEvaluator)
	 * @see mauda.feedback.NormalEvaluator
	 * @see mauda.feedback.FaultEvaluator
	 */
	public void setEvaluator(Evaluator e) { evaluator = e; }
	/**
	 * Gets the Evaluator
	 * @return Evaluator
	 */
	public Evaluator getEvaluator() { return evaluator; }
	
	/**
	 * This method will be called, when a new exercise is start
	 * loading.
	 */
	// Wird aufgerufen, wenn eine Aufgabe geladen wird
	public void displayLoadingMessage() {
		displayMessage("FEEDBACK", "");	// Muss vor TASK stehen!
		displayMessage("TASK", 
				"<b>Loading Exercise.</b><br><br>"+
				"Please wait ..."
			);		
	}
	
	// Message anzeigen, und in den aktualisierten View wechseln
	protected void displayMessage(String view, String html) {
		displayMessage(view, html, true);
	}
	// Message anzeigen: gotoView = true => in View wechseln
	protected void displayMessage(String view, String html, boolean gotoView) {
		FeedbackPanel fbp = exercise.getFeedbackPanel();
		if(view.equals("TASK")) {
			if(gotoView) fbp.getTabbedPane().setSelectedIndex(0);
			fbp.getTaskView().setBodyHTML(html);
		} 
		else if(view.equals("FEEDBACK")) {
			if(gotoView) fbp.getTabbedPane().setSelectedIndex(1);
			fbp.getFeedbackView().setBodyHTML(html);
		} 
	}
	
	protected void setBackButtonText(String t) {
		exercise.getFeedbackPanel().backButton.setText(t);
	}
	protected void setBackEnabled(boolean b) {
		exercise.getFeedbackPanel().backButton.setEnabled(b);
	}
	
	protected void setForwardButtonText(String t) {
		exercise.getFeedbackPanel().forwardButton.setText(t);
	}
	protected void setForwardEnabled(boolean b) {
		exercise.getFeedbackPanel().forwardButton.setEnabled(b);
	}

	protected void setSpecialButtonText(String t) {
		exercise.getFeedbackPanel().specialButton.setText(t); 
	}
	protected void setSpecialEnabled(boolean b) {
		exercise.getFeedbackPanel().specialButton.setEnabled(b);
	}

	protected void setDemandButtonText(String t) {
		exercise.getFeedbackPanel().demandButton.setText(t);
	}
	protected void setDemandEnabled(boolean b) {
		exercise.getFeedbackPanel().demandButton.setEnabled(b);
	}
	
	protected void setGiveUpEnabled(boolean b) {
		exercise.getFeedbackPanel().giveUpButton.setEnabled(b);
	}
	
	protected void giveUpPressed() {
		OperationQueue outstanding = null;
		boolean fault = this instanceof FaultFeedback;
		String msg = "Really give up?";
		if(!fault) {
			msg+="\n\n";
			outstanding = new OperationQueue();
			msg += "Outstanding operations:\n";
			for(int i=todoPosition+1; i<todoOperationQueue.length(); i++) {
				Operation op = todoOperationQueue.get(i);
				outstanding.add((Operation)op.clone());
				msg+=" - "+op.out()+"\n";
			}
		}
		else {
			msg+="\n\n";
			msg+="This will result in zero points!!!";
		}
		int result = JOptionPane.showConfirmDialog(MAUDA.getApplicationMainFrame(), msg, "Give up...", JOptionPane.OK_CANCEL_OPTION);
		if(result == JOptionPane.CANCEL_OPTION) return;
		if(!fault) {
			exercise.getFeedbackPanel().disableAllButtons();
			Vector v = outstanding.toVector();
			ExerciseUpdateEvent eue = new ExerciseUpdateEvent(exercise, ExerciseUpdateEvent.SPECIAL);
			eue.setMessage("GIVE-UP-PRESSED");	// siehe exerciseUpdate(..)
			v.add(eue);
			exercise.commit(v);
		}
		else {
			((FaultEvaluator)evaluator).setState(FaultEvaluator.GIVE_UP);
			finishedExercise();
		}
	}

	// Folgende Methoden muss ggf. überschrieben werden
	protected void jumpEvent(int delta) {
		OperationRecorder or = exercise.getOperationRecorder();
		int firstOpOffset = or.getOffset(0,-1);
		counter += delta;		
		int opCounter = -1;
		int c = 0;
		while(c<=counter) {
			if(or.getEntry(c+firstOpOffset) instanceof Operation) opCounter++;
			c++;
		}
		todoPosition = opCounter;
	}
	// Folgende Methoden müssen überschrieben werden
	protected abstract void backPressed();
	protected abstract void forwardPressed();
	protected abstract void specialPressed();
	protected abstract void demandPressed();
	protected abstract void interactiveEvent();
	
	protected void doInteractiveEvent() {
		preInteractiveEvent();
		interactiveEvent();
		postInteractiveEvent();		
	}
	// Vor Feedback-spezifischem interactiveEvent ausgeführt
	private void preInteractiveEvent() {
		//System.out.println("Event: INTERACTIVE");
		counter++;
		
		differentOperation = true;
		
		// Jedes FeedbackObject vermerken
		if(feedbackObjects.size() > counter) {
			FeedbackObject fo = (FeedbackObject)feedbackObjects.elementAt(counter);
			//System.out.println(fo);
			Object o = exercise.getOperationRecorder().getCurrentOperation();
			SimpleOperation so = null;
			if(o instanceof Operation) so = fo.getOperation();
			else if(o instanceof SubOperation) so = fo.getSubOperation(); 
			if(so!=null && !so.equals(o)) {
				undoRedoInfo.storeChange(counter, feedbackObjects, callKB());
				//undoRedoInfoFFFO.storeChange(counter, FFFOVector, deepCopyFFFO());
				//System.out.println("clear feedbackObjects");
				//while(feedbackObjects.size()>counter)
				//	feedbackObjects.removeElementAt(counter);
				//feedbackObjects.add(callKB());
			} else {
				// UndoRedoInfo-Aktion?: Nur für ReloadKB relevant. Stimmt aber so auch nicht,
				// da callKB() eventuell vorhandene folgende Operationen nicht betrachtet, und
				// diese somit beim Feedback nicht berücksichtigt werden. Somit kann in einer
				// Operation kein Fehler sein, wohingegen callKB() einen Fehler einträgt!
				//System.out.println("SimpleFeedback.preInteractiveEvent(): special state occured! -> .setElementAt(callKB(),counter);");
				//feedbackObjects.setElementAt(callKB(), counter);
				if(so==null && o!=null) {
					undoRedoInfo.storeChange(counter, feedbackObjects, callKB());					
				} else {
					//System.out.println("SimpleFeedback.preInteractiveEvent(): differentOperation=false => gleiche Operation ausgeführt -> kein storeChange(..) ! -> so="+so+" o="+o);
					differentOperation = false;
				}
			}
		} else {
			//System.out.println("SimpleFeedback.preInteractiveEvent(): special state occured! -> .add(callKB());");
			undoRedoInfo.storeChange(counter, feedbackObjects, callKB());
			//undoRedoInfoFFFO.storeChange(counter, FFFOVector, deepCopyFFFO());
			//feedbackObjects.add(callKB());
		}
	}
	// Nach Feedback-spezifischem interactiveEvent ausgeführt
	private void postInteractiveEvent() {
		if(this instanceof mauda.feedback.types.SolutionFeedback) return;
		if(differentOperation) {
			
			//SimpleOperation so = (SimpleOperation)exercise.getOperationRecorder().getCurrentOperation();
			//System.out.println("SimpleFeedback.postInteractiveEvent(): called operation="+so);
			
			undoRedoInfoFFFO.storeChange(counter, FFFOVector, deepCopyFFFO());
		}
		differentOperation = true;
	}
	// Kopieren der firstFailureFeedbackObjects
	protected Vector deepCopyFFFO() {
		Vector v = new Vector();
		Enumeration en = firstFailureFeedbackObjects.elements();
		while(en.hasMoreElements()) {
			FeedbackObject fo = (FeedbackObject)en.nextElement();
			if(fo!=null) v.add(fo.clone());
			else v.add(null);
		}
		return v;
	}
	
	protected void forwardEvent() {
		//System.out.println("Event: REDO");
	}
	protected void backEvent() {
		//System.out.println("Event: UNDO");
	}
	protected void linkClicked(String href) {
		//System.out.println("Event: linkClicked");
		if(href.equals("nextMessage")) {
			displayMessage("FEEDBACK", currentFeedbackObject.getNextMessage());
		} else if(href.equals("prevMessage")) {
			displayMessage("FEEDBACK", currentFeedbackObject.getPrevMessage());
		} else if(href.equals("showcorrectsolution")) {
			displayMessage("FEEDBACK", "Loading solution");
			loadCorrectSolution();
		}
	}
	private void loadCorrectSolution() {
		SimpleFeedback fb = new mauda.feedback.types.SolutionFeedback(exercise);
		exercise.setFeedback(fb);
		switchTo(fb, false);

		OperationRecorder or = exercise.getOperationRecorder();
		int delta = or.getOffset(0,-1) - or.getCurrentOffset();
		delta--; // damit vor erste Operation gesprungen wird
		//System.out.println("TOV: position = "+position+" delta="+delta);
		// Message fürs springen
		Vector cv = new Vector();	// kompletter Commit-Vector
		ExerciseUpdateEvent eue;
		int offsetEUE = 0;
		if(delta!=0) {
			eue = new ExerciseUpdateEvent(exercise, ExerciseUpdateEvent.JUMP);
			eue.setValue(delta);
			cv.add(eue);
			offsetEUE=-1;
		}
		// SubOperationen extrahieren/TOView enablen..
		Vector tv = todoOperationQueue.toVector();
		Enumeration en = tv.elements();
		while(en.hasMoreElements()) {
			Operation op = (Operation)en.nextElement();
			cv.add((Operation)op.clone());
			cv.addAll(((SubOperationQueue)op.getSubOperationQueue().clone()).toVector());
		}
		delta = cv.size()+offsetEUE;
		// Nach dem Abspielen der Aufgabe an den Anfang springen
		eue = new ExerciseUpdateEvent(exercise, ExerciseUpdateEvent.JUMP);
		eue.setValue(-delta);
		cv.add(eue);
		// Fertiges abspielen -> Message 
		eue = new ExerciseUpdateEvent(exercise, ExerciseUpdateEvent.SPECIAL);
		eue.setMessage("CorrectSolutionLoaded");
		cv.add(eue);
		// Jetzt übermitteln
		exercise.jedasPanel.nextNoProgressBar();
		//System.out.println("CORRECT-SOLUTION: "+cv);
		exercise.commit(cv);
	}
	// Enthält Aufgaben-Informationen
	protected void finishedLoading() {
		finishedLoading = true;
		exercise.setInteractiveMode(Exercise.NOPOPUP);		
		//System.out.println("finished loading.");
		//displayMessage("Exercise ready");
		exercise.getFeedbackPanel().giveUpButton.setEnabled(true);
	}
	protected String getFeedbackDescription() {
		return "ERROR: Unknown Feedback.";
	}
	// Aufgabenstellung
	protected String genOpToPerformMessage() {
		String msg = "<b>Exercise ready.</b>";
		msg += feedbackGenerator.genMessage(
					"explanation.gif",
					"You have to perform the following operations correctly.");
		msg += genOpList(-1);		
		return msg;
	}
	protected String genOpToPerformFalseMessage() {
		String msg = "<b>Exercise ready.</b>";
		msg += feedbackGenerator.genMessage(
					"explanation.gif",
					"You have to find the <i>first</i> error in the following operations.");
		msg += genOpList(-1);		
		return msg;		
	}
	protected String genOpList(int todoPosition) {
		if(todoPosition>maxTodoPosition) 
			maxTodoPosition = todoPosition;
		
		String msg = "<ol>";
		String state = "";
		int counter = 1;
		for(int i=0; i<todoOperationQueue.length(); i++) {
			String error = "";
			if(!(this instanceof mauda.feedback.types.NoTutor)) {
				if(i<firstFailureFeedbackObjects.size()) {
					FeedbackObject fo = (FeedbackObject)firstFailureFeedbackObjects.elementAt(i);
					if(fo!=null) error = " but with errors";
				}
			}
			if(todoPosition>=0) {
				if(i<todoPosition) {
					state = " (done"+error+")";
				} 
				else if(i==todoPosition) {
					state = " (in process)";
				} else {
					if(i<=maxTodoPosition) { 
						//state = " (commenced"+error+")";	// commenced = angefangen
						state = " (commenced)";
					} else {
						state = " (todo)";
					} 
				} 
			}
			msg += "<li><b>"+todoOperationQueue.get(i).out()+"</b>"+state+"</li>";
			counter++;			
		}
		msg += "</ol>";
		return msg;
	}
	// Updaten der Task-Anzeige
	// für jede Operation angeben, in welchem Zustand sie sich be-
	// findet
	protected void updateTaskMessage() {
		// Folgendes darf ich nicht machen, da beim switchen zwischen
		// den Feedback-Typen keine Aktualisierung der Feedback-
		// Description durchgeführt würde
		// Message nur ändern, wenn todoPosition anders ist
		//if(todoPosition<=maxTodoPosition) return;
		String msg = "<b>Exercise state</b>";
		msg += feedbackGenerator.genMessage(
					"information.gif",
					"This is your actual exercise state:");
		msg += genOpList(todoPosition);
		msg += "<br>";
		msg += getFeedbackDescription();
		displayMessage("TASK", msg, false);
		return;
	}
	
	/* (non-Javadoc)
	 * @see mauda.ExerciseUpdateListener#exerciseUpdate(mauda.ExerciseUpdateEvent)
	 */
	public void exerciseUpdate(ExerciseUpdateEvent e) {
		if(e.getID() == ExerciseUpdateEvent.FINISHED_LOADING) {
			finishedLoading();
		}
		if(!finishedLoading) return;
		//System.out.println("SimpleFeedback.exerciseUpdateEvent(..): Event: "+e);
		if(e.getID() == ExerciseUpdateEvent.OPERATION_EXECUTED) {
			doInteractiveEvent();
		} else if(e.getID() == ExerciseUpdateEvent.FORWARD) {
			forwardEvent();
		} else if(e.getID() == ExerciseUpdateEvent.BACK) {
			backEvent();
		} else if(e.getID() == ExerciseUpdateEvent.UNDO) {
			//System.out.println("SimpleFeedback.exerciseUpdateEvent(..): UNDO");
			undoRedoInfo.undo(feedbackObjects);
			undoRedoInfoFFFO.undo(FFFOVector);

			if(FFFOVector.size()>0) {
				firstFailureFeedbackObjects = (Vector)FFFOVector.lastElement();
				firstFailureFeedbackObjects = deepCopyFFFO();
			}
			else
				firstFailureFeedbackObjects = new Vector();
			/*
			int p = undoRedoInfoFFFO.getPosition();
			System.out.println("  FFFOVector-size="+FFFOVector.size()+" position="+p);
			System.out.println("  feedbackObjects-size="+feedbackObjects.size());
			if(p>=0) {
				firstFailureFeedbackObjects = (Vector)FFFOVector.elementAt(p);
				firstFailureFeedbackObjects = deepCopyFFFO();
			} else {
				System.out.println("  firstFailureFeedbackObjects = new Vector()");
				firstFailureFeedbackObjects = new Vector();
			}*/
		} else if(e.getID() == ExerciseUpdateEvent.REDO) {
			//System.out.println("SimpleFeedback.exerciseUpdateEvent(..): REDO");
			undoRedoInfo.redo(feedbackObjects);
			undoRedoInfoFFFO.redo(FFFOVector);
			
			if(FFFOVector.size()>0) {
				firstFailureFeedbackObjects = (Vector)FFFOVector.lastElement();
				firstFailureFeedbackObjects = deepCopyFFFO();
			}
			else
				firstFailureFeedbackObjects = new Vector();
			/*
			int p = undoRedoInfoFFFO.getPosition();
			System.out.println("  FFFOVector-size="+FFFOVector.size()+" position="+p);
			System.out.println("  feedbackObjects-size="+feedbackObjects.size());
			firstFailureFeedbackObjects = (Vector)FFFOVector.elementAt(p);
			firstFailureFeedbackObjects = deepCopyFFFO();*/
		} else if(e.getID() == ExerciseUpdateEvent.JUMP) {
			jumpEvent(e.getValue());
			/*
			int p = undoRedoInfoFFFO.getPosition();
			System.out.println("  FFFOVector-size="+FFFOVector.size()+" position="+p);
			System.out.println("  feedbackObjects-size="+feedbackObjects.size());
			if(p>=0) {
				firstFailureFeedbackObjects = (Vector)FFFOVector.elementAt(p);
				firstFailureFeedbackObjects = deepCopyFFFO();
			} else {
				System.out.println("  firstFailureFeedbackObjects = new Vector()");
				firstFailureFeedbackObjects = new Vector();
			}*/
			
		} else if(e.getID() == ExerciseUpdateEvent.SPECIAL) {
			if(e.getMessage().equals("CorrectSolutionLoaded")) {
				//SimpleFeedback fb = new mauda.feedback.types.SolutionFeedback(exercise);
				//exercise.setFeedback(fb);
				//switchTo(fb);
				exercise.getTreeOperationView().markCorrectness(false, true);
				exercise.getTreeOperationView().enableJumping();
				exercise.getTreeOperationView().enableStop();
				String msg = "<b>Correct solution loaded...</b><br>";
				msg += feedbackGenerator.genMessage(
							"explanation.gif",
							"Now you can navigate through the correct solution.");
				displayMessage("FEEDBACK", msg);
				exercise.setModified(false);
			} else if(e.getMessage().equals("GIVE-UP-PRESSED")) {	// siehe giveUpPressed();
				finishedExercise();
			}
		}
	}
	
	// Creates a link for nextMessage falls aktuelle = SubOperation
	// Verwendung in Undo/Redo-Messages
	protected String makeNextLink(String msg) {
		if(counter>feedbackBound) return msg;
		Object actop = exercise.getOperationRecorder().getCurrentOperation();
		return makeNextLink(msg, actop);
	}
	protected String makeNextLink(String msg, Object actop) {
		if(counter>feedbackBound) return msg;
		if(actop instanceof SubOperation) {
			msg += "<br>To get information for <i>"+((SubOperation)actop).out()+"</i> ";
			msg += "click <a href='nextMessage'>here.</a>";
		}	
		return msg;	
	}
	protected void setFeedbackBound() {
		feedbackBound = counter;
	}
	protected void updateBackForwardButtons() {
		FeedbackPanel fbp = exercise.getFeedbackPanel();
		//System.out.println("COUNTER: "+counter);
		fbp.backButton.setEnabled(exercise.getUndoRedo().canBack() && counter>=0);
		fbp.forwardButton.setEnabled(exercise.getUndoRedo().canForward());		
	}
	/**
	 * Sets the todo-operations
	 * @param oq todo-operations
	 */
	public void setTodoOperationQueue(OperationQueue oq) {
		todoOperationQueue = (OperationQueue)oq.clone();
	}
	/**
	 * Gets the todo-operations
	 * @return todo-operations
	 */
	public OperationQueue getTodoOperationQueue() {
		return todoOperationQueue;
	}
	/**
	 * Sets the init-operations
	 * @param oq init-operations
	 */
	public void setInitOperationQueue(OperationQueue oq) {
		initOperationQueue = (OperationQueue)oq.clone();
		//System.out.println("setInitOperations: "+initOperationQueue);
	}
	/**
	 * Gets the init-operations
	 * @return init-operations
	 */
	public OperationQueue getInitOperationQueue() {
		return initOperationQueue;
	}
	protected FeedbackObject callKB() {
		FeedbackObject fo = feedbackGenerator.getFeedback();
		currentFeedbackObject = fo;
		if(fo!=null) return fo;
		fo = new FeedbackObject();
		fo.setCorrectness(FeedbackObject.UNKNOWN);
		Vector messages = new Vector();
		messages.add("Error in FeedbackGenerator: no feedback available");
		fo.setMessages(messages);
		return fo;		
	}
	// Setzen des ersten Fehler-FeedbackObjects der aktuellen
	// Operation	
	private void setFailureFeedbackObject(FeedbackObject fo) {
		//System.out.println("ToDoPos: "+todoPosition);
		//System.out.println(fo);
		//System.out.println("SimpleFeedback.setFailureFeedbackObject(..): called");
		if(firstFailureFeedbackObjects.size()<=todoPosition) {
			firstFailureFeedbackObjects.add(fo);	
		} else {
			firstFailureFeedbackObjects.setElementAt(fo, todoPosition);
		}
	}
	// firstFailureFeedbackObjects ab todoPosition löschen
	protected void cutFirstFailureFeedbackObjects() {
		int cutIndex = todoPosition+1;
		OperationRecorder or = exercise.getOperationRecorder();		
		Failure f = or.getLimitedFailure(or.getOffset(todoPosition, -1));
		// Wenn wir uns vor dem 1.Fehler befinden, den aktuellen
		// first-Failure-Eintrag auch entfernen, da dieser jetzt
		// behoben
		if(f == null || f.getOffset() > or.getCurrentOffset()) cutIndex--;
		//System.out.println("SimpleFeedback.cutFirstFailureFeedbackObjects(): called");
		while(firstFailureFeedbackObjects.size()>cutIndex)
			firstFailureFeedbackObjects.removeElementAt(cutIndex);
	}
	// Aktuelle Operation auf Fehler überprüfen, und in den
	// Fehler-Vector eintragen
	protected void checkForFailure() {
		// Fehler prüfen		
		OperationRecorder or = exercise.getOperationRecorder();		
		Failure f = or.getLimitedFailure(or.getOffset(todoPosition, -1));
		if(f==null || f.getOffset() > or.getCurrentOffset()+1) {
			setFailureFeedbackObject(null);
			return;
		}
		FeedbackObject fo = null;
		if(f.getOffset() == or.getCurrentOffset()) {
			// Wir befinden uns auf dem ersten Fehler => Eintragen
			fo = callKB();
			setFailureFeedbackObject(fo);
			evaluator.log(currentFeedbackObject);
		} else if(f.getDescription() == Failure.MISSING_SUBOP &&
					f.getOffset() == or.getCurrentOffset()+1) {
			// Wir befinden uns auf dem ersten Fehler, da wir
			// fehlende SubOperationen haben
			mauda.plugin.KBFormulaEvaluator kbfe = feedbackGenerator.getKBFormulaEvaluator();
			kbfe.setFinishOperationState(true);
			fo = callKB();
			kbfe.setFinishOperationState(false);
			setFailureFeedbackObject(fo);
		} else {
			if(firstFailureFeedbackObjects.size()>todoPosition)  
				fo = (FeedbackObject)firstFailureFeedbackObjects.elementAt(todoPosition);
			if(fo==null) {
				// bisher kein Fehler-Eintrag
				fo = callKB(); 
				if(fo.getCorrectness() == FeedbackObject.INCORRECT ||
					fo.getCorrectness() == FeedbackObject.INCORRECT_MISSING) {
					setFailureFeedbackObject(fo);
				} else {
					setFailureFeedbackObject(null);
				}
			}
		}
		//System.out.println("checkForFailure - END"); 
	}

	/**
	 * Returns if the exercise was finished.
	 * @return true, if exercise finished, false otherwise
	 */
	public boolean exerciseFinished() { return finishedExercise; }
	protected void finishedExercise() {
		finishedExercise = true;

		// Folgender Boolean gibt an ob die Aufgabe tutoriell
		// evaluiert werden soll
		
		// ALT
		//boolean tutorEvaluation = this instanceof mauda.feedback.types.NoTutor;
		// Auswahl-Dialog für tutorielle Evaluation
		boolean tutorEvaluation = false;
		if(!(this instanceof FaultFeedback)) {
			int result = JOptionPane.showConfirmDialog(MAUDA.getApplicationMainFrame(), "Do you want a tutoriel evaluation!", "Question", JOptionPane.YES_NO_OPTION);
			if(result == JOptionPane.YES_OPTION) tutorEvaluation = true;
		}
		
		if(!tutorEvaluation) {
			// Fehler im Tree markieren
			exercise.getTreeOperationView().markCorrectness(false,true);
		}

		// Aufgabe auswerten
		evaluator.evaluate(exercise.getOperationRecorder());
	
		// Aktionen deaktivieren
		exercise.setInteractiveMode(Exercise.NOPOPUP);
		exercise.getTreeOperationView().disableJumping();
		FeedbackPanel fbp = exercise.getFeedbackPanel();
		fbp.backButton.setEnabled(false);
		fbp.forwardButton.setEnabled(false);
		fbp.specialButton.setEnabled(false);
		fbp.demandButton.setEnabled(false);
		fbp.giveUpButton.setEnabled(false);
		
		// Message anzeigen
		String msg = feedbackGenerator.genMessage(
					"correct_v.gif",
					"<b>Congratulations</b>",
					null,
					"Exercise finished!");
		if(!tutorEvaluation) {
			msg += "<br>"+evaluator.getHTMLMessage();
		} else {
			msg += "<br>The exercise will be evaluated by a tutor. "+
					"When the tutor has processed the exercise, you can view it with the EvalPlayer.";
		}
		if(!(this instanceof mauda.feedback.types.FaultFeedback)) {
			msg += "<br><a href='showcorrectsolution'>Show correct solution</a>";
		}
		displayMessage("FEEDBACK", msg);
		
		// Folgendes um letzte Operation auch auf done zu setzen
		todoPosition++; updateTaskMessage();
		
		saveFile(tutorEvaluation);
	}
	private void saveFile(boolean tutorEvaluation) {
		String filename = null;
		MetaData md = exercise.getMetaData();
		md.actualizeEditorDate();
		md.setEditorState("completed");
		if(tutorEvaluation) {
			md.setEvaluatorState("not evaluated");
			filename = FileLocation.genFilename(
							FileLocation.completedPath,
							FileLocation.completedPrefix);
		} else {			
			md.setEvaluatorName("AutoEvaluator");
			md.actualizeEvaluatorDate();
			md.setEvaluatorState("evaluated");
			md.setEvaluatorScore(evaluator.getScoreString());
			
			// Save Exercise by a calculated Filename
			filename = FileLocation.genFilename(
								FileLocation.evaluatedPath,
								FileLocation.evaluatedPrefix);
		}
		md.inputDialog();
		exercise.saveWork(filename);
	}

	/**
	 * Returns the FeedbackTypeID
	 * @return feedback-type-ID
	 * @see mauda.feedback.select.FeedbackSelector
	 */
	public int getFeedbackTypeID() { return -1; }
	
	/**
	 * Converts all feedback-values in a HashMap-representation for
	 * saving-purposes
	 * @return HashMap-representation
	 */
	// Folgende 2 Methoden müssen überschrieben werden
	public HashMap save() {
		HashMap hm = new HashMap();
		hm.put("evaluator", evaluator.save());
		hm.put("counter", new Integer(counter));
		hm.put("feedbackBound", new Integer(feedbackBound));
		return hm;
	}
	/**
	 * Loads the values of this feedback with the values from the
	 * delivered HashMap-representation
	 * @param hm HashMap-representation
	 */
	public void load(HashMap hm) {
		exercise.getTreeOperationView().enableJumping();

		evaluator.load((HashMap)hm.get("evaluator"));
		int newCounter = ((Integer)hm.get("counter")).intValue();
		feedbackBound = ((Integer)hm.get("feedbackBound")).intValue();
		
		if(newCounter>=0) {
			// Sprung zu counter
			// auch machen wenn delta = 0 um specialState upzudaten
			OperationRecorder or = exercise.getOperationRecorder();
			//int delta = newCounter+or.getOffset(0,-1) - or.getCurrentOffset();
			int delta = newCounter-counter;
			//System.out.println("Jump to ... (counter="+counter+" / newCounter="+newCounter+" / delta="+delta+")");
			ExerciseUpdateEvent eue = new ExerciseUpdateEvent(exercise, ExerciseUpdateEvent.JUMP);
			eue.setValue(delta);
			Vector v = new Vector();
			v.add(eue);
			exercise.commit(v);
		}
		configurated = true;
	}
	
	/**
	 * Switches to an other feedback-type
	 * @param newFeedback SimpleFeedback
	 * @param update true, to generate a jump-message with delta=0 which has the effect, that all data-structures are updated, false otherwise
	 */
	// Paramter dieses Feedbacks zu neuem Feedback kopieren
	public void switchTo(SimpleFeedback newFeedback, boolean update) {
		newFeedback.setEvaluator(getEvaluator());
		newFeedback.finishedLoading = finishedLoading;
		// Bei IF&EC feedbackBound so lassen (Integer.MAX_VALUE)
		if(!(newFeedback instanceof mauda.feedback.types.ImmediateFeedbackAndErrorCorrection))
			newFeedback.feedbackBound = feedbackBound;		
		// Variante: bei switch auf IFAEC newFeedback.counter auf ersten
		// Fehler setzen, und Baum ab dieser Position abschneiden
		newFeedback.counter = counter;
		newFeedback.todoPosition = todoPosition;
		newFeedback.maxTodoPosition = maxTodoPosition;
		newFeedback.configurated = configurated;
		newFeedback.feedbackObjects = feedbackObjects;
		newFeedback.firstFailureFeedbackObjects = firstFailureFeedbackObjects;
		newFeedback.undoRedoInfo = undoRedoInfo;
		newFeedback.undoRedoInfoFFFO = undoRedoInfoFFFO;
		newFeedback.FFFOVector = FFFOVector;

		// Achtung: hier darf man nicht set- und get-Methoden verwenden		
		newFeedback.initOperationQueue = initOperationQueue;
		newFeedback.todoOperationQueue = todoOperationQueue;

		exercise.removeExerciseUpdateListener(this);
		exercise.addExerciseUpdateListener(newFeedback);

		// Folgendes nur machen, wenn schon eine Bearbeitung statt-
		// gefunden hat.
		if(counter>=0) {
			switch(newFeedback.getFeedbackTypeID()) {
				case FeedbackSelector.IFAEC :
					exercise.getTreeOperationView().markCorrectness(false, false);
					break;
				case FeedbackSelector.ERROR_FLAGGING :
					break;
				case FeedbackSelector.DEMAND_FEEDBACK :
					setDemandEnabled(true);
					break;
				case FeedbackSelector.NO_TUTOR :
					break;
			}
			newFeedback.updateTaskMessage(); // Task-Message aktu.: überwiegend wegen Feedback-Descpription
			if(!(newFeedback instanceof mauda.feedback.types.SolutionFeedback))
				exercise.getFeedbackPanel().giveUpButton.setEnabled(true);
			if(update) {
				// Jetzt JUMP-Message machen, damit die Views aktualisiert
				// werden: specialState, displayMessage, etc.
				ExerciseUpdateEvent eue = new ExerciseUpdateEvent(exercise, ExerciseUpdateEvent.JUMP);
				eue.setValue(0);	// Achtung: delta-Wert
				Vector v = new Vector(); v.add(eue);
				exercise.commit(v);
			}
		} else {
			newFeedback.finishedLoading();		
		}
		updateBackForwardButtons();
	}
}
