/*
 * Created on 02.03.2004 15:26:44
 *
 * Multimediale Algorithmen und Datenstrukturen Assessments
 */
package mauda.feedback.types;

import mauda.*;
import mauda.operation.*;
import mauda.feedback.FeedbackExercise;
import mauda.feedback.SimpleFeedback;
import mauda.treeoperationview.*;
import mauda.feedback.FeedbackObject;
import mauda.feedback.FaultEvaluator;
import mauda.feedback.select.*;

import java.util.*;

/**
 * Provides the logic for <i>Fault-Feedback</i> in FaultMode-
 * exercises.
 * 
 * @author Markus Krebs
 */
public class FaultFeedback extends SimpleFeedback {

	private static final int type = FeedbackSelector.FAULT_FEEDBACK;

	// Zunächst komplette Aufgabe abspielen?
	private static final boolean playWholeExercise = true;
	private boolean playing;
	
	// Schon probierte SubOperationen
	private Vector tries_vector;
	private SimpleOperation nextCorrect;
	
	// Modus zum korrigieren des Fehlers
	private boolean makeRightMode;
	private int interactiveEditPos;	// Position der zu korrigierenden SubOperation
	
	private boolean backMessage;

	private static final String specialStart = "Start ...";
	private static final String specialThisIsIncorrect = "This Operation is incorrect ...";
	private static final String specialPerform = "Perform correct Operation...";
	
	private HashMap failureTypes;
	
	private Vector operations;
	
	// Wird benötigt, dass kein Undo bei correctType-Aufruf
	// durchgeführt wird, da wir uns bereits auf der richtigen
	// Operation per Jump-Event befinden
	private boolean stateConfigurated;

	/**
	 * Creates a FaultFeedback
	 * @param exercise FeedbackExercise
	 */
	public FaultFeedback(FeedbackExercise exercise) {
		super(exercise);
		setBackButtonText("Back");
		setForwardButtonText("Forward");
		failureTypes = new HashMap();
		failureTypes.put("missing", "All SubOperations correct, but missing some SubOperations for <i>OPERATION</i>.");
		failureTypes.put("different", "The false SubOperation differs from the correct one.");
		failureTypes.put("additionalfalse", "All correct, but additional false SubOperations, what means that all false SubOperations unnecessary for <i>OPERATION</i>");
		makeRightMode = false;
		backMessage = true;
		tries_vector = new Vector();
		nextCorrect = null;
		
		stateConfigurated = true;
	}
	private String genFailureTypesList() {
		String msg="";
		Iterator it = failureTypes.keySet().iterator();
		while(it.hasNext()) {
			String key = (String)it.next();
			String value = (String)failureTypes.get(key);
			msg += "<li><a href='"+key+"'>"+value+"</a></li>";			
		}
		OperationRecorder or = exercise.getOperationRecorder();
		int opnr = or.getCurrentOperationNr();
		// Wenn wir uns auf einer Operation befinden, dann muss
		// vorherige eingesetzt werden, da MISSING_SUBOP
		if(or.getCurrentOperation() instanceof Operation) opnr--;
		Operation op = or.getOperation(opnr);
		if(op!=null)
			msg = msg.replaceAll("OPERATION", op.out());
		return msg;
	}
	
	protected void jumpEvent(int delta) {
		// kein super.jumpEvent(delta) ausführen!!!
		counter += delta;
		String msg = "";
		if(counter>=0) {
			SimpleOperation o = (SimpleOperation)exercise.getOperationRecorder().getCurrentOperation();
			msg = feedbackGenerator.genMessage(
						"information.gif",
						"<b>Jump</b> to "+o.out());
		} else {
			msg = feedbackGenerator.genMessage(
							"information.gif",
							"<b>Jump</b> to beginning");			
		}
		if(!makeRightMode) displayMessage("FEEDBACK", msg);
		updateBackForwardButtons();
		checkEditable();
	}
	
	protected void specialPressed() {
		if(specialState == specialStart) {
			//counter++;
			setSpecialButtonText(specialThisIsIncorrect);
			specialState = specialThisIsIncorrect;
			//updateUndoRedoButtons();
			if(playWholeExercise) {
				String msg = feedbackGenerator.genMessage(
						"information.gif",
						"<b>Playing exericse...</b>");
				displayMessage("FEEDBACK", msg);
				// Zunächst komplette Aufgabe abspielen (siehe auch interactiveEvent())
				playing = true;
				setSpecialEnabled(false);
				setGiveUpEnabled(false);
				exercise.commit(operations);
			} else {
				// Sofort mit Back/Forward navigieren
				OperationRecorder or = exercise.getOperationRecorder();
				Enumeration en = operations.elements();
				while(en.hasMoreElements()) or.addEntry(en.nextElement());
				exercise.commit((SimpleOperation)operations.elementAt(0));
			}
		} else if(specialState == specialThisIsIncorrect) {
			//System.out.println("Counter = "+counter);
			OperationRecorder or = exercise.getOperationRecorder();
			Failure f = or.getUnlimitedFailure(0);
			// ACHTUNG:
			// f kann null sein, da die korrekten SubOperationen
			// in den Operation-Templates noch nicht eingetragen
			// sind. Diese werden erst beim Ausführen des
			// Templates ermittelt.
			String falseNotFirst = feedbackGenerator.genMessage(
					"correct_v.gif" ,
					"This operation is false, but it is not the first failure in Exercise!");
			falseNotFirst = makeNextLink(falseNotFirst);
			String selectmsg;
			String yesFirst = feedbackGenerator.genMessage(
					"correct_v.gif",
					"Yes, this is the first incorrect operation!",
					"explanation.gif",
					"Please specify the type of the failure:");
			yesFirst += genFailureTypesList();
			// kein next link möglich, da man dann nicht mehr zur Auswahl zurückkommt
			//yesFirst = makeNextLink(yesFirst);
			String noCorrect = feedbackGenerator.genMessage(
					"incorrect_x.gif",
					"No, this is correct!");
			noCorrect = makeNextLink(noCorrect);
			callKB();
			int currentOffset = or.getCurrentOffset();
			//int failureOffset = -1;
			//if(f !=null) failureOffset = f.getOffset();
			//System.out.println(currentOffset+" =? "+failureOffset);
			TreeOperationView tov = exercise.getTreeOperationView();
			if(f!=null && currentOffset == f.getOffset()) {
				exercise.getTreeOperationView().disableJumping();
				setBackEnabled(false);
				setForwardEnabled(false);
				setSpecialEnabled(false);
				int opnr = or.getCurrentOperationNr();
				if(f.getDescription() == Failure.MISSING_SUBOP)
					opnr--;
				//Operation op = or.getOperation(opnr);
				//yesFirst = yesFirst.replaceAll("OPERATION", op.out());
				displayMessage("FEEDBACK", yesFirst);
				tov.markCorrectness(false, true);
				evaluator.log(FaultEvaluator.FIRST_FAILURE_FOUND);
			} else if(f!=null && f.containsOffset(currentOffset)) {
				evaluator.log(FaultEvaluator.FALSE_NOT_FIRST);
				evaluator.log(currentFeedbackObject);
				displayMessage("FEEDBACK", falseNotFirst);
				tov.markActAsIncorrect();				
			} else {
				int opnr = or.getCurrentOperationNr();
				f = or.getUnlimitedFailure(or.getOffset(opnr,-1));
				//failureOffset = -1;
				//if(f !=null) failureOffset = f.getOffset();
				if(f !=null && f.containsOffset(currentOffset)) {
					displayMessage("FEEDBACK", falseNotFirst);
					tov.markActAsIncorrect();
					evaluator.log(FaultEvaluator.FALSE_NOT_FIRST);
				}
				else {
					displayMessage("FEEDBACK", noCorrect);
					tov.markActAsCorrect();
					evaluator.log(FaultEvaluator.FALSE_CORRECT);
				}
				evaluator.log(currentFeedbackObject);
			}				
		} else if(specialState == specialPerform) {
			// Wieder auf true setzen, damit in interactiveEvent
			// erkannt wird das die Aufgabe fertig ist.
			makeRightMode = true;
			exercise.commit(nextCorrect);
		}
	}
	protected void demandPressed() { }
	protected void backPressed() {
		exercise.back();
	}
	protected void forwardPressed() {
		if(makeRightMode){
			exercise.forward();
			return;
		}
		if(counter>=operations.size()) return;
		exercise.commit((SimpleOperation)operations.elementAt(counter+1));
	}
	
	protected void backEvent() {
		super.backEvent();
		counter--;
		checkEditable();
		if(!makeRightMode) {
			if(backMessage) {
				SimpleOperation o = (SimpleOperation)exercise.getOperationRecorder().getCurrentOperation();
				String msg="";
				if(o!=null) {
					msg = feedbackGenerator.genMessage(
						"information.gif",
						"<b>Back</b> to "+o.out());
				} else {
					msg = feedbackGenerator.genMessage(
						"information.gif",
						"<b>Back</b> to beginning");
				}
				displayMessage("FEEDBACK", msg);
			} else backMessage = true;
		}
		updateBackForwardButtons();
	}
	protected void forwardEvent() {
		super.forwardEvent();
		counter++;
		checkEditable();
		updateBackForwardButtons();
	}
	
	protected void interactiveEvent() {
		if(makeRightMode) {
			checkEditable();
			// Behobenes Problem: interactiveEvent wird bei Replay ausgelöst!!!
			if(!differentOperation && (counter<=interactiveEditPos || counter+1>interactiveEditPos)) return;
			OperationRecorder or = exercise.getOperationRecorder();
			// Auf Fehler prüfen
			Failure f = or.getUnlimitedFailure(0);
			int currentOffset = or.getCurrentOffset();
			if(f == null || f.getOffset()>currentOffset) {
				finishedExercise();
			} else {
				// Versuche zählen
				SimpleOperation so = (SimpleOperation)or.getCurrentOperation();
				if(tries_vector.contains(so)) {
					String msg = feedbackGenerator.genMessage(
							"information.gif",
							"This Operation ("+so.out()+") you already tried, and it was incorrect!");
					displayMessage("FEEDBACK", msg);
					backMessage = false; exercise.back();
					return;										
				}
				tries_vector.add(so);
				if(tries_vector.size()>=MAX_TRIES) {
					int opnr = or.getCurrentOperationNr();
					int subopnr = or.getCurrentSubOperationNr();
					nextCorrect = or.getCorrectSubOperation(opnr, subopnr); 
					String msg = feedbackGenerator.genMessage("information.gif",
								"You have tried this operation for "+MAX_TRIES+" times,"+
								"and it was allways incorrect.",
								"lamp.gif",
								"The correct Operation is<br>"+
								"<i>"+nextCorrect.out()+"</i>");
					msg += "<p>Press <b><i>"+specialPerform+"</i></b> to perform this correct operation.</p>";
					displayMessage("FEEDBACK", msg);
					setBackEnabled(false);
					setForwardEnabled(false);
					setSpecialEnabled(true);
					exercise.setInteractiveMode(Exercise.NOPOPUP);
					setSpecialButtonText(specialPerform);
					specialState = specialPerform;
					TreeOperationView tov = exercise.getTreeOperationView(); 
					tov.markCorrectness(true, true);
					tov.disableJumping();
					// Diesen Fehler auch noch loggen
					callKB();
					evaluator.log(currentFeedbackObject);
					// Auf false setzen, damit interactiveMode
					// nicht wieder aktiviert wird
					// siehe undoEvent -> checkEditable
					makeRightMode = false;
					// Jetzt automatisches undo
					backMessage = false; exercise.back();
				} else {
					// Bei weniger als MAX_TRIES-Versuchen
					FeedbackObject fo = callKB();
					displayMessage("FEEDBACK", fo.getNextMessage());
					TreeOperationView tov = exercise.getTreeOperationView(); 
					tov.markCorrectness(true, true);
					evaluator.log(currentFeedbackObject);
					backMessage = false; exercise.back();
				}
			}
			return;
		}

		if(playing) {
			// Zunächst komplette Aufgabe abspielen
			if(counter == operations.size()-1) {
				setSpecialEnabled(true);
				setGiveUpEnabled(true);
				//System.out.println("LAST OPERATION");
				updateBackForwardButtons();
				playing = false;
				// Task-Message updaten
				String msg = genOpToPerformFalseMessage();				
				msg += "You have to press <b><i>"+specialThisIsIncorrect+"</b></i> when you think the actual Operation is false.";
				displayMessage("TASK", msg);
				// Feedback-Message updaten
				msg = feedbackGenerator.genMessage(
						"information.gif",
						"Exercise completly played.",
						null,
						"Now you can navigate throug the exericse by clicking the back- and forward-buttons, "+
						"or jumping directly to an operation by clicking it below.");
				displayMessage("FEEDBACK", msg);
				exercise.getTreeOperationView().enableJumping();
				exercise.getTreeOperationView().enableStop();
			}
		} else {
			SimpleOperation o = (SimpleOperation)exercise.getOperationRecorder().getCurrentOperation();
			String msg = "";
			if(counter==0) msg = "<b>Start</b> with "+o.out();
			else msg = "<b>Forward</b> to "+o.out(); 
			msg = feedbackGenerator.genMessage(
					"information.gif",
					msg);
			displayMessage("FEEDBACK", msg);
			updateBackForwardButtons();
		}
	}	

	private void checkEditable() {
		if(makeRightMode) {
			if(counter==interactiveEditPos)
				exercise.setInteractiveMode(Exercise.SUBOPERATION);
			else
				exercise.setInteractiveMode(Exercise.NOPOPUP);
		}		
	}
	protected void linkClicked(String href) {
		super.linkClicked(href);
		//System.out.println("href = "+href);
		Failure f = exercise.getOperationRecorder().getUnlimitedFailure(0);
		if(href.equals("missing")) {
			if(f.getDescription() != Failure.MISSING_SUBOP)
				falseType(href);
			else correctType(href);
		} else if(href.equals("different")) {
			if(f.getDescription() != Failure.DIFFERENT)
				falseType(href);
			else correctType(href);
		} else if(href.equals("additionalfalse")) {
			if(f.getDescription() != Failure.ADDITIONAL_FALSE_SUBOP)
				falseType(href);
			else correctType(href);
		}
	}
	private void correctType(String type) {
		exercise.getTreeOperationView().enableJumping();
		if(type.equals("missing")||type.equals("different")) {
			//loadNormalModeFeedback();
			// Task-Message updaten
			String msg = genOpToPerformFalseMessage();				
			msg += "You have to specify the correct SubOperation by pressing the right mouse-button in the drawing pane at the location of the failure.";
			displayMessage("TASK", msg);
			// Feedback-Message updaten
			SimpleOperation falseOp = ((SimpleOperation)exercise.getOperationRecorder().getCurrentOperation());
			msg = feedbackGenerator.genMessage(
					"correct_v.gif",
					"<b>Correct</b>",
					null,
					"Please specify the correct SubOperation by pressing the right mouse-button in the drawing pane."+
					"<br><br>That means you must replace the false operation <i>"+falseOp.out()+"</i>!");
			displayMessage("FEEDBACK", msg);
			makeRightMode = true;
			if(stateConfigurated) {
				interactiveEditPos = counter-1;
				backMessage = false;
				exercise.back();
			} else {
				interactiveEditPos = counter;
			}
			exercise.setInteractiveMode(Exercise.SUBOPERATION);
			evaluator.log(FaultEvaluator.CORRECT_MODE);
		} else {
			finishedExercise();
		}
	}
	private void falseType(String type) {
		failureTypes.remove(type);
		String msg = "";
		if(failureTypes.size()==1) {
			msg = feedbackGenerator.genMessage(
				"incorrect_x.gif",
				"No this is the false type!",
				"lamp.gif",
				"The correct type is the following (please click it to proceed):");
		} else {
			msg = feedbackGenerator.genMessage(
				"incorrect_x.gif",
				"No this is the false type. Try it again!",
				"explanation.gif",
				"Please specify the type of the failure:");
		}
		msg += genFailureTypesList();
		displayMessage("FEEDBACK", msg);
		evaluator.log(FaultEvaluator.FALSE_FAILURE_TYPE_SELECTED);		
	}
	
	/* (non-Javadoc)
	 * @see mauda.feedback.SimpleFeedback#setTodoOperationQueue(mauda.operation.OperationQueue)
	 */
	public void setTodoOperationQueue(OperationQueue oq) {
		super.setTodoOperationQueue(oq);
		// Ganze Queue in sequentielle Operationen/SubOperationen
		// umwandeln
		operations = new Vector();
		for(int i=0; i<oq.length(); i++) {
			Operation op = (Operation)oq.get(i).clone();
			//op.setExecution(false);
			operations.add(op);
			SubOperationQueue soq = op.getSubOperationQueue();
			for(int j=0; j<soq.length(); j++)
				operations.add((SubOperation)soq.get(j).clone());
		}
		evaluator.postProcessing(operations);
	}
	
	/*
	private void loadNormalModeFeedback() {
		SimpleFeedback feedback = null;
		feedback = new ImmediateFeedbackAndErrorCorrection(exercise);
		Vector v = (Vector)todoOperations.clone();
		while(v.size()>counter) v.removeElementAt(counter);
		feedback.setTodoOperations(v);
		exercise.removeExerciseUpdateListener(this);
		exercise.addExerciseUpdateListener(feedback);
		exercise.setFeedback(feedback);
		feedback.displayMessage("TASK", "Switch to Normal-Mode");
		exercise.getFeedbackPanel().revalidate();
	}*/

	protected void finishedLoading() {
		super.finishedLoading();
		String msg = genOpToPerformFalseMessage();				
		if(playWholeExercise) {
			msg += "To play the whole exericse now, please click the <b><i>'"+specialStart+"'</i></b> button below.";
		} else {
			msg += "You have to press <b><i>"+specialThisIsIncorrect+"</b></i> when you think the actual Operation is false."+
					"<br>"+
					"To start, please click the <b><i>'"+specialStart+"'</i></b> button below.";
		}
		displayMessage("TASK", msg);
		
		setSpecialButtonText(specialStart);
		setSpecialEnabled(true);
		specialState = specialStart;
	}
	protected String getFeedbackDescription() {
		//return "You will get an immediate feedback after every click on '"+specialThisIsIncorrect+"'.";
		return "";
	}

	/* (non-Javadoc)
	 * @see mauda.feedback.SimpleFeedback#getFeedbackTypeID()
	 */
	public int getFeedbackTypeID() { return type; }
	
	/* (non-Javadoc)
	 * @see mauda.feedback.SimpleFeedback#save()
	 */
	public HashMap save() {
		HashMap hm = super.save();
		// tries_vector
		Vector v = new Vector();
		Enumeration en = tries_vector.elements();
		while(en.hasMoreElements()) {
			SimpleOperation so = (SimpleOperation)en.nextElement();
			if(so instanceof Operation) v.add("OPERATION "+so.save());
			else if(so instanceof SubOperation) v.add("SUBOPERATION "+so.save()); 			
		}
		hm.put("tries_vector", v);
		// nextCorrect
		if(nextCorrect == null)
			hm.put("nextCorrect", "NULL");
		else if(nextCorrect instanceof Operation)
			hm.put("nextCorrect", "OPERATION "+nextCorrect.save());
		else if(nextCorrect instanceof SubOperation)
			hm.put("nextCorrect", "SUBOPERATION "+nextCorrect.save());
		// makeRightMode
		hm.put("makeRightMode", new Boolean(makeRightMode));
		// interactiveEditPos
		hm.put("interactiveEditPos", new Integer(interactiveEditPos));
		// failureTypes
		hm.put("failureTypes", failureTypes);
		return hm;
	}
	/* (non-Javadoc)
	 * @see mauda.feedback.SimpleFeedback#load(java.util.HashMap)
	 */
	public void load(HashMap hm) {
		super.load(hm);
		// tries_vector
		Vector v = (Vector)hm.get("tries_vector");
		tries_vector.removeAllElements();
		Enumeration en = v.elements();
		while(en.hasMoreElements()) {
			String s = (String)en.nextElement();
			if(s.startsWith("OPERATION ")) {
				Operation op = new Operation();
				op.load(s.substring(10));
				tries_vector.add(op);
			} else if(s.startsWith("SUBOPERATION ")) {
				SubOperation subop = new SubOperation();
				subop.load(s.substring(13));
				tries_vector.add(subop);
			}
		}
		// nextCorrect
		String s = (String)hm.get("nextCorrect");
		if(s.equals("NULL")) {
			nextCorrect = null;
		} else if(s.startsWith("OPERATION ")) {
			nextCorrect = new Operation();
			nextCorrect.load(s.substring(10));
		} else if(s.startsWith("SUBOPERATION")) {
			nextCorrect = new SubOperation();
			nextCorrect.load(s.substring(13));
		}
		// makeRightMode
		makeRightMode = ((Boolean)hm.get("makeRightMode")).booleanValue();
		// interactiveEditPos
		interactiveEditPos = ((Integer)hm.get("interactiveEditPos")).intValue();
		// failureTypes
		failureTypes = (HashMap)hm.get("failureTypes");
		// Jetzt die States überprüfen
		// da die States erst nach dem Jump geprüft werden dürfen
		// ein Event erzeugen das in FeedbackExercise behandelt
		// wird, und dann stateConfig ausführt
		ExerciseUpdateEvent eue = new ExerciseUpdateEvent(exercise, ExerciseUpdateEvent.SPECIAL);
		eue.setMessage("FAULT_STATE-CONFIG");
		v = new Vector(); v.add(eue);
		exercise.commit(v);
		stateConfigurated = false;
	}
	/**
	 * Configurates the state of Fault-Feedback, e.g. after loading
	 * an "in process" exercise.
	 */
	public void stateConfig() {
		switch(((FaultEvaluator)evaluator).getState()) {
			case FaultEvaluator.SEARCH_FAILURE :
				if(counter>=0) {
					setSpecialButtonText(specialThisIsIncorrect);
					specialState = specialThisIsIncorrect;
					exercise.getTreeOperationView().enableJumping();
					String msg = feedbackGenerator.genMessage(
								"information.gif",
								"You have to search the first failure!");
					displayMessage("FEEDBACK", msg);
					exercise.getTreeOperationView().enableStop();
				}
				break;
			case FaultEvaluator.FALSE_CORRECT :
			case FaultEvaluator.FALSE_NOT_FIRST :
				exercise.getTreeOperationView().enableJumping();
				exercise.getTreeOperationView().enableStop();
				setSpecialButtonText(specialThisIsIncorrect);
				specialState = specialThisIsIncorrect;
				String msg = feedbackGenerator.genMessage(
								"information.gif",
								"You have to search the first failure!");
				displayMessage("FEEDBACK", msg);
				break;
			case FaultEvaluator.FIRST_FAILURE_FOUND :			
				exercise.getTreeOperationView().enableStop();
				//exercise.getTreeOperationView().disableJumping();	// wird in specialPressed() erledigt			
				setSpecialButtonText(specialThisIsIncorrect);
				specialState = specialThisIsIncorrect;
				specialPressed();
				break;				
			case FaultEvaluator.FALSE_FAILURE_TYPE_SELECTED :
				exercise.getTreeOperationView().enableStop();
				exercise.getTreeOperationView().disableJumping();			
				setSpecialEnabled(false);
				setBackEnabled(false);
				setForwardEnabled(false);
				falseType(null);
				break;
			case FaultEvaluator.CORRECT_MODE :
				exercise.getTreeOperationView().enableStop();
				exercise.getTreeOperationView().enableJumping();
				// Folgendes kann auch "different" sein
				// es muss gewährleistet sein, dass die Message
				// angezeigt wird
				// Achtung: Undo in dieser Methode darf nicht
				// aufgerufen werden (boolean stateConfigurated)
				correctType("missing");
				if(tries_vector.size()>=MAX_TRIES) {
					// Letzte falsche nochmal übertragen
					// => interactiveEvent => Anzeige korrekt
					SimpleOperation so = (SimpleOperation)tries_vector.remove(MAX_TRIES-1);
					exercise.commit(so);
				}
				break;
		}
		stateConfigurated = true;		
	}
}
