/*
 * Created on 02.12.2003 17:20:04
 *
 * Multimediale Algorithmen und Datenstrukturen Assessments
 */
package mauda;

import mauda.operation.*;
import mauda.undoredo.AnimatedUndoRedo;
import mauda.undoredo.UndoRedo;
import mauda.utils.Prop;
import mauda.plugin.PlugInHandler;
import mauda.plugin.JedasMainCompObj;

import java.util.*;

import jedas.*;
import java.awt.event.MouseListener;
/**
 * A central class to hold all relevant exercise-specific
 * values and references to important other classes. 
 * 
 * Extends SimpleExercise with GUI-specific needs.
 * 
 * @author Markus Krebs
 */
public class Exercise extends SimpleExercise {
	
	public JedasPanel jedasPanel;
	
	public static Scheduler scheduler;
	public static ControlPanel controlPanel;
	public static CompPanel compPanel;
	
	public static MouseListener interactive;
		
	public static boolean quickAnim;	// wird von FibHeap, FibHeapExt und FibNode direkt abgefragt

	protected UndoRedo undoRedo;
	
	protected boolean actionsAllowed;	// Sperren der Eingabemöglichkeiten
	
	public Vector opsToAnimate;
	
	private boolean modified;	// gibt an ob die aktuelle Aufgabe verändert wurde
	
	// Modus: Art der Bearbeitung
	
	/**
	 * Show only operations in the interactive-menu 
	 */
	public static final int OPERATION = 0;		// Normale Operationen
	/**
	 * Show only suboperations in the interactive-menu
	 */
	public static final int SUBOPERATION = 1;	// SubOperationen
	/**
	 * Show both operations and suboperations in the interactive-menu
	 */
	public static final int BOTHOPERATION = 2;	// Beide Operationen erlauben
	/**
	 * Allow no user-inputs in the interactive-menu
	 */
	public static final int NOPOPUP = 3;		// Interactive abschalten

	protected int interactiveMode = OPERATION;
	
	
	/**
	 * Creates a new exercise
	 * @param type the plug-in-type, e.g. FibHeap
	 */
	public Exercise(String type) {
		super();
		
		jedasPanel = new JedasPanel();
		
		scheduler = jedasPanel.getScheduler();
		controlPanel = jedasPanel.getControlPanel();
		compPanel = jedasPanel.getCompPanel();
		
		actionsAllowed = true;

		quickAnim = false;
		
		undoRedo = null;
		
		interactive = null;	// Abspeichern des Objektes zur Interactiven Kommunikation

		// PlugInHandler neu instanziieren, da er sonst auf
		// SimpleExercise arbeitet, was falsch wäre
		// ACHTUNG: Muss auf jeden Fall nach scheduler.jedasPanel.getScheduler() stehen
		setPlugInHandler(new PlugInHandler(this));

		switchTo(type);
	}
	/**
	 * Sets the modification-state of the exercise
	 * @param m true if modified, false otherwise
	 */
	public void setModified(boolean m) { 
		//System.out.println("Exercise.setModified("+m+"): change needed = "+(modified!=m));
		modified = m;
	}
	/**
	 * Returns the modification-state of the exericse
	 * @return true if exercise was modified, false otherwise
	 */
	public boolean isModified() { return modified; }
	
	public Vector addFullLoadedMessage(Vector v) {
		ExerciseUpdateEvent eue = new ExerciseUpdateEvent(this, ExerciseUpdateEvent.FULL_LOADED);
		v.add(eue);
		return v;
	}
	/**
	 * Prepares the exercise-object for loading a new exercise, or
	 * to generate a new exercise.
	 * This method generates also an <code>ExerciseUpdateEvent</code>
	 * with the id <code>RESET</code>, and sends it to its
	 * listeners.
	 */
	public void reset() {
		sendExerciseUpdateMessages(new ExerciseUpdateEvent(this, ExerciseUpdateEvent.RESET));

		String type = getMetaData().getType();
		int diff = getMetaData().getDifficulty();
		int currentMode = getMetaData().getMode();

		setMetaData(new MetaData());

		getMetaData().setType(type);		
		getMetaData().setDifficulty(diff);
		setMode(currentMode);
		
		setModified(false);
		
		if(!(this instanceof mauda.evaluation.PlayerExercise)) jedasPanel.setNormalCompPanel(compPanel);
	}
	/* (non-Javadoc)
	 * @see mauda.SimpleExercise#switchTo(java.lang.String)
	 */
	public void switchTo(String plugInName) {
		if(plugInName==null) return;
		reset();
		init();
		
		if(interactive!=null) {
			compPanel.getDrawPanel().removeAll();	// Heap-Menus, etc. entfernen
			compPanel.getDrawPanel().removeMouseListener(interactive);
		}
		
		getPlugInHandler().load(plugInName);
		
		//System.out.println("DSOBJECT = "+getDSObject());
		
		compPanel.getDrawPanel().addMouseListener(interactive);
				
		opsToAnimate = new Vector();

		setDifficulty(difficulty);
		setMode(ExerciseMode.NORMAL);
		
		setModified(false);
		
		if(getUndoRedo() != null)
			removeExerciseUpdateListener(getUndoRedo());
		// REDO animiert oder nicht?
		//UndoRedo undoRedo = new UndoRedo(this);	// normal (funktioniert nicht mehr -> irgendwo ein Bug!)
		UndoRedo undoRedo = new AnimatedUndoRedo(this);	// animiert
		addExerciseUpdateListenerFirst(undoRedo);
		setUndoRedo(undoRedo);
				
		jedasPanel.setExercise(this);

		compPanel.clear();
		compPanel.addItem(((JedasMainCompObj)getDSObject()).getMainCompObj()); // add Item to compPanel
		undoRedo.initialSnapshot(null);
		Jedas.updateDisplay();

		sendExerciseUpdateMessages(new ExerciseUpdateEvent(this, ExerciseUpdateEvent.PLUGIN_CHANGED));
		//reset();
	}
	
	/* (non-Javadoc)
	 * @see mauda.SimpleExercise#setMode(int)
	 */
	public void setMode(int mode) {
		super.setMode(mode);
		jedasPanel.setModeLabel("Mode: "+ExerciseMode.getString(mode));
		setModified(true);
	}
	/**
	 * Sets the quick-animation-mode for the jedas-panel
	 * @param b true turns quick-animation on, false off
	 */
	public static void setQuickAnim(boolean b) {
		// Bemerkung: Leider ist meine QuickAnim-Technik noch schneller
		// als nur TransitionSkipping => beides aktivieren => schneller
		quickAnim = b;
		scheduler.setTransitionSkipping(b);
	}
	
	/**
	 * Mode for editing on exercise
	 * @param mode see NOPOPUP OPERATION SUBOPERATION BOTHOPERATION
	 */
	public void setInteractiveMode(int mode) {
		this.interactiveMode = mode;
	}
	/**
	 * Gets the current editing-mode
	 * @return editing-mode
	 * @see mauda.Exercise#setInteractiveMode
	 */
	public int getInteractiveMode() { return interactiveMode; }
	
	/**
	 * Turns actions for buttons/menus on or off
	 * @param b true to turn on actions, false otherwise
	 */
	public void setActionsAllowed(boolean b) {
		this.actionsAllowed = b;
	}
	/**
	 * Returns the actions-allowed state
	 * @return actions-allowed-state
	 */
	public boolean actionsAllowed() { return actionsAllowed; }
	
	protected void setUndoRedo(UndoRedo ur) { undoRedo = ur; }
	/**
	 * Gets the undo/redo-handler
	 * @return UndoRedo
	 */
	public UndoRedo getUndoRedo() { return undoRedo; }
	
	/* (non-Javadoc)
	 * @see mauda.SimpleExercise#setDifficulty(int)
	 */
	public void setDifficulty(int difficulty) {
		super.setDifficulty(difficulty);
		jedasPanel.setDifficultyLabel("Difficulty: "+Difficulties.getGUIString(getDifficulty()));
		setModified(true);
	}
		
	/**
	 * Adds an operation to the animation-operations that should
	 * JedasPanel execute on the data-structure
	 * @param op SimpleOperation
	 */
	protected void addToAnimationQueue(SimpleOperation op) {
		synchronized (opsToAnimate) {
			opsToAnimate.add(op);
		}		
	}
	
	/**
	 * Adds several operations/exercise-update-events to the
	 * animation-operations that the JedasPanel should execute
	 * @param v Vector of operations and/or ExerciseUpdateEvents
	 */
	protected void addToAnimationQueue(Vector v) {
		synchronized (opsToAnimate) {
			opsToAnimate.addAll(v);
		}
	}
	
	/**
	 * Starts the jedas-animation. This means that the operations
	 * in the animation-queue are executed in the JedasPanel.
	 */
	public void animate() {
		/*
		System.out.println("Exercise.ANIMATE: opsToAnimate:");
		Enumeration en = opsToAnimate.elements();
		int counter = 0;
		while(en.hasMoreElements()) {
			System.out.println(counter+". "+en.nextElement()+" ");
			counter++;
		}
		System.out.println();
		
		if(opsToAnimate.size()==1 && opsToAnimate.firstElement() instanceof ExerciseUpdateEvent) {
			jedasPanel.run();
			return;
		}*/
		
		if (!scheduler.isActive()) {
			controlPanel.enablePlay();
			controlPanel.play();
		}
	}

	/**
	 * Performs a step back. That means that an ExerciseUpdateEvent with
	 * the ID BACK will be transmitted to all ExerciseListeners.
	 */
	public void back() {
		if(!undoRedo.canBack()) return;
		Vector v = new Vector();
		v.add(new ExerciseUpdateEvent(this, ExerciseUpdateEvent.BACK));
		commit(v);
		//sendExerciseUpdateMessages(new ExerciseUpdateEvent(this, ExerciseUpdateEvent.UNDO));
	}
	/**
	 * Performs a step forward. That means that an ExerciseUpdateEvent with
	 * the ID FORWARD will be transmitted to all ExerciseListeners.
	 */
	public void forward() {
		// bei animiertem Redo wird der Rest nach der Animation erledigt
		if(!undoRedo.canForward()) return;
		if(undoRedo instanceof AnimatedUndoRedo) {
			Vector v = new Vector();
			v.add(new ExerciseUpdateEvent(this, ExerciseUpdateEvent.FORWARD_ANIMATED));
			commit(v);
			//sendExerciseUpdateMessages(new ExerciseUpdateEvent(this, ExerciseUpdateEvent.REDO_ANIMATED));			
			return;
		}
		Vector v = new Vector();
		v.add(new ExerciseUpdateEvent(this, ExerciseUpdateEvent.FORWARD));
		commit(v); 
		//sendExerciseUpdateMessages(new ExerciseUpdateEvent(this, ExerciseUpdateEvent.REDO));
	}
	/**
	 * Performs an undo. That means that an ExerciseUpdateEvent with
	 * the ID UNDO will be transmitted to all ExerciseListeners.
	 */
	public void undo() {
		if(!undoRedo.canUndo() || !getOperationRecorder().canUndo()) return;
		Vector v = new Vector();
		v.add(new ExerciseUpdateEvent(this, ExerciseUpdateEvent.UNDO));

		int delta = undoRedo.getUndoJumpDelta();
		//if(delta!=0) {
			ExerciseUpdateEvent eue = new ExerciseUpdateEvent(this, ExerciseUpdateEvent.JUMP);
			eue.setValue(delta);
			v.add(eue);
		//}
		//jumpToBeginning();
		
		commit(v);
	}
	/**
	 * Performs a redo. That means that an ExerciseUpdateEvent with
	 * the ID REDO will be transmitted to all ExerciseListeners.
	 */
	public void redo() {
		if(!undoRedo.canRedo()) return;
		Vector v = new Vector();
		v.add(new ExerciseUpdateEvent(this, ExerciseUpdateEvent.REDO));

		int delta = undoRedo.getRedoJumpDelta();
		//if(delta!=0) {
			ExerciseUpdateEvent eue = new ExerciseUpdateEvent(this, ExerciseUpdateEvent.JUMP);
			eue.setValue(delta);
			v.add(eue);
		//}
		//jumpToBeginning();

		commit(v);
	}
	/**
	 * Jumps to the beginning of the exercise.
	 */
	public void jumpToBeginning() {
		OperationRecorder or = getOperationRecorder();
		if(or.getCurrentOperation()==null) return;	// Wenn bereits am Anfang
		int delta = or.getOffset(0,-1) - or.getCurrentOffset();
		delta--; // Damit auf Wurzelknoten gesprungen wird
		//System.out.println("Exercise.jumpToBeginning(): delta="+delta);
		// Message fürs springen
		Vector v = new Vector();
		if(delta!=0) {
			ExerciseUpdateEvent eue = new ExerciseUpdateEvent(this, ExerciseUpdateEvent.JUMP);
			eue.setValue(delta);
			v.add(eue);
			commit(v);
		}			
	}
	public void jumpToEnd() {
		OperationRecorder or = getOperationRecorder();
		if(or.getCurrentOffset()+1 == or.getOperationCount()) return; // Wenn bereits am Ende
		int delta = or.getOffset(0,-1) - or.getCurrentOffset() + or.getOperationCount();
		delta--;
		delta--;
		//System.out.println("Exercise.jumpToEnd(): delta="+delta);
		// Message fürs springen
		Vector v = new Vector();
		if(delta!=0) {
			ExerciseUpdateEvent eue = new ExerciseUpdateEvent(this, ExerciseUpdateEvent.JUMP);
			eue.setValue(delta);
			v.add(eue);
			commit(v);
		}
	}

	/**
	 * Adds an operation to the animation-queue
	 * @param so SimpleOperation
	 */
	public void commit(SimpleOperation so) {
		addToAnimationQueue(so);
		animate();
	}
	/**
	 * Adds an operation to the animation-queue. This method should
	 * be called by the interactive-class (e.g. FibHeapInteractive)
	 * to distinguish between interactive-events and execution of
	 * e.g. operations while loading.
	 * @param so SimpleOperation
	 */
	// Aufgerufen aus FibHeapInteractive
	public void commitInteractive(SimpleOperation so) {
		if(so instanceof Operation) {
			int mode = getInteractiveMode();
			if(mode == Exercise.BOTHOPERATION) ((Operation)so).setExecution(false);
		}
		commit(so);
	}
	/**
	 * Adds several operations/exercise-update-events to the
	 * animation-operations that the JedasPanel should execute
	 * @param v Vector of operations and/or ExerciseUpdateEvents
	 */
	public void commit(Vector v) {
		addToAnimationQueue(v);
		animate();
	}
	/**
	 * Adds several operations (from OperationQueue) to the
	 * animation-operations that the JedasPanel should execute
	 * @param oq OperationQueue
	 */
	public void commit(OperationQueue oq) {
		commit(oq.toVector());
	}
	/**
	 * This methods stops the jedas-animation immediatly after the
	 * operation that is currently executed.
	 */
	public void stopPlaying() {
		synchronized(opsToAnimate) {
			opsToAnimate.removeAllElements();
		}		
	}
	/**
	 * Generates a failure-message in respect to the operation,
	 * that should executed. This method should
	 * be called by the interactive-class (e.g. FibHeapInteractive)
	 * in the case, that a selected operation cannot be executed.
	 * Example: FibHeap: link only on root-nodes allowed, and the
	 * data-structure of FibHeap not supports link on
	 * non-root-nodes.
	 * @param operation The ID of the selected operation
	 * @param alert A description, why this operation cannot be executed.
	 */
	public void commit(String operation, String alert) {
		javax.swing.JOptionPane.showMessageDialog(MAUDA.getApplicationMainFrame(), operation+": "+alert);
	}
	
	/* (non-Javadoc)
	 * @see mauda.SimpleExercise#load(java.lang.String)
	 */
	public HashMap load(String filename) {
		HashMap hm = super.load(filename);
		return hm;
	}
	/* (non-Javadoc)
	 * @see mauda.SimpleExercise#save(java.lang.String)
	 */
	public HashMap save(String filename) {
		HashMap hm = super.save(filename);
		setModified(false);
		return hm;
	}
		
	/**
	 * Checking for correct exercises. E.g.: if a FaultMode exercise
	 * contains an error.
	 * @return "OK" if the exercise is ready for saving, otherwise a string representing why it is incorrect
	 */
	public String isReadyToSave() {
		// Achtung: folgende Prop.get-Aufrufe holen Informationen aus
		// mauda.generation/properties_eng, da die Klasse als Unterklasse
		// GenExercise hat.
		boolean ok = getOperationRecorder().isValidTodoPosition();
		if(!ok) {
			return Prop.get(this, "ToDoPositionNotAllowed");
		}
		OperationQueue todoOps = getOperationRecorder().getAllTodoOperationQueue();		
		if(todoOps.length()==0) return Prop.get(this, "NoToDoOperations");
		if(getMode() == ExerciseMode.FAULT) {
			Failure f = getOperationRecorder().getUnlimitedFailure(0);
			if(f==null) return Prop.get(this, "FaultModeNoFailure");
			else {
				// Nach MISSING_SUBOP muss eine Operation folgen
				// da sonst später beim WorkEditor der Fehler nicht
				// angesprungen werden kann 
				if(f.getDescription() == Failure.MISSING_SUBOP &&
					getOperationRecorder().getOperation(f.getOpNr()+1) == null) {
					return Prop.get(this, "MissingSubOpNoNextOpFailure");					
				}
				return "OK";
			} 
		} else {
			Failure f = getOperationRecorder().getUnlimitedFailure(0);
			if(f!=null) return Prop.get(this, "NormalModeWithFailure");
		}
		return "OK";
	}
}
