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

import mauda.plugin.OperationExecuter;
import mauda.plugin.PlugInHandler;
import mauda.operation.*;

import java.util.*;
/**
 * A central class to hold all relevant exercise-specific
 * values and references to important other classes.
 * 
 * If a GUI is needed please use the Exercise-class. 
 * 
 * @author Markus Krebs
 */
public class SimpleExercise {

	private final String defaultFilename = "untitled.xml";
	private String filename;

	private Vector updateObjects;	// ExerciseUpdateListener's	
	
	private Object dsObject;	// FibHeap, AVLTree, BinomialQueue, etc.
	/**
	 * Stores the OperationExecuter who maps the Operation-IDs to
	 * the corresponding methods in the data-structure.
	 */
	public static OperationExecuter operationExecuter; // wird per PlugIn-Handler geladen	
	private OperationRecorder operationRecorder;

	private PlugInHandler plugInHandler;

	// Setzen durch PlugInHandler
	/**
	 * Stores the possible operations on the data-structure
	 * @see mauda.plugin.PlugInHandler
	 */
	public static Vector availableOperations;	// Mögliche Operation für Add bei OpTemplateTab
	/**
	 * Stores the possible suboperations on the data-structure
	 * @see mauda.plugin.PlugInHandler
	 */
	public static Vector availableSubOperations;	// für initiale Generierung von rating.xml
	protected String dataDirectory;	// Datenablegungs-Verzeichnis (Init, Scramble, AutoGen, etc.)
	
	protected int mode;

	
	// Meta-Daten
	protected MetaData metaData;
	protected int difficulty;
	
	/**
	 * Creates a SimpleExercise
	 */
	public SimpleExercise() {
		operationRecorder = null;
		updateObjects = new Vector();

		plugInHandler = new PlugInHandler(this);

		init();		
	}
	/**
	 * Initializes the SimpleExercise, so that it can be used
	 */
	public void init() {
		filename = defaultFilename;
		
		mode = ExerciseMode.NORMAL;

		difficulty = 0;
		dsObject = null;
		metaData = new MetaData();
		metaData.setDifficulty(difficulty);
		
		dataDirectory = null;

		if(operationRecorder!=null)
			removeExerciseUpdateListener(operationRecorder);

		operationRecorder = new OperationRecorder();
		operationRecorder.add(null);
		addExerciseUpdateListenerFirst(operationRecorder);		
	}
	/**
	 * Switches between available PlugIns.
	 * @param plugInName The name of the PlugIn
	 */
	public void switchTo(String plugInName) {
	}

	/**
	 * Gets the PlugInHandler
	 * @return PlugInHandler
	 */
	public PlugInHandler getPlugInHandler() {
		return plugInHandler;
	}
	/**
	 * Sets the PlugInHandler
	 * @param pih PlugInHandler
	 */
	public void setPlugInHandler(PlugInHandler pih) {
		plugInHandler = pih;
	}

	/**
	 * Adds an ExerciseUpdateListener to the exercise. The added
	 * listener will receive events for changes in the exercise.
	 * @param eul ExerciseUpdateListener
	 */
	// ExerciseUpdateListener
	public void addExerciseUpdateListener(ExerciseUpdateListener eul) {
		updateObjects.add(eul);
		//printUpdateObjects();
	}
	/**
	 * Adds an ExerciseUpdateListener to the exercise. The added
	 * listener will receive events for changes in the exercise.
	 * Important: This method will, in contrast to
	 * <code>addExerciseUpdateListener(ExerciseUpdateListener)</code>
	 * place the Listener at the beginning of the Listener-Vector.
	 * This is necessary, because OperationRecorder and UndoRedo,
	 * have to notified before all other listeners, because other
	 * listeners request the actual state of e.g. the
	 * OperationRecorder's current operation. So there must be
	 * entered an operation in the OperationRecorder before other
	 * listeners perform a readout of that information.<br>
	 * In the case of UndoRedo, other listeners requests the actual
	 * data-structure, so UndoRedo must also placed at the beginning
	 * of the listeners.<br>
	 * Because OperationRecorder and UndoRedo doesn't readout one
	 * another, their position doesn't matter. 
	 * @param eul ExerciseUpdateListener
	 */
	public void addExerciseUpdateListenerFirst(ExerciseUpdateListener eul) {
		updateObjects.add(0, eul);
		//printUpdateObjects();
		//System.out.println("SimpleExercise.addExerciseUpdateListenerFirst(...)");
		//System.out.println("updateObjects(): ");
		//System.out.println(updateObjects);
	}
	/*
	private void printUpdateObjects() {
		System.out.println("SimpleExercise.printUpdateObjects()");
		int counter = 0;
		Enumeration en = updateObjects.elements();
		while(en.hasMoreElements()) {
			Object o = en.nextElement();
			String s="?";
			if(o instanceof OperationRecorder) s="OperationRecorder";
			else if(o instanceof AnimatedUndoRedo) s="AnimatedUndoRedo";
			else if(o instanceof UndoRedo) s="UndoRedo";
			else s=o.toString();
			System.out.println("  "+counter+". "+s);
			counter++;
		}
	}*/
	/**
	 * Removes an ExerciseUpdateListener from the exercise.
	 * @param eul ExerciseUpdateListener
	 */
	public void removeExerciseUpdateListener(ExerciseUpdateListener eul) {
		updateObjects.remove(eul);
	}
	/**
	 * Sends an ExerciseUpdateEvent to all listeners.
	 * @param event ExerciseUpdateEvent
	 */
	public void sendExerciseUpdateMessages(ExerciseUpdateEvent event) {
		// Messages ausgeben (vor dem Senden)
		//System.out.println("SimpleExercise.sendExerciseUpdateMessages(..): "+event);
		if(event.getID() == ExerciseUpdateEvent.CLEAR_FOLLOWING) {
			if(getOperationRecorder().getNextOperation() == null) {
				//System.out.println("SimpleExercise.sendExerciseUpdateMessages(..): No CLEAR_FOLLOWING-Event execution needed!");
				return;
			}
		} else if(event.getID() == ExerciseUpdateEvent.CLEAR_PRECEDING) {
			// CLEAR_PRECEDING: filter unnecessary message (muss nicht gefiltert werden)
			//if(getOperationRecorder())
		}
		Enumeration en = updateObjects.elements();
		while(en.hasMoreElements())
			((ExerciseUpdateListener)en.nextElement()).exerciseUpdate(event);
	}
	
	/**
	 * Gets the current exercise-filename
	 * @return filename
	 */
	public String getFilename() { return filename; }
	/**
	 * Gets the default exercise-filename
	 * @return default-filename
	 */
	public String getDefaultFilename() { return defaultFilename; }
	/**
	 * Sets the current exercise-filename
	 * @param fn filename
	 */
	public void setFilename(String fn) { 
		filename = fn;
		ExerciseUpdateEvent event = new ExerciseUpdateEvent(this, ExerciseUpdateEvent.FILENAME_CHANGED);
		sendExerciseUpdateMessages(event);
	}
	/**
	 * Sets the current exercise-filename to the default-filename
	 */
	public void setDefaultFilename() {
		setFilename(getDefaultFilename());
	}
	
	/**
	 * Sets the directory where plug-specific information is stored
	 * e.g. init-operations for automatic generation of exercises,
	 * or the knowledge-base for feedback-information
	 * @param directory Data-Directory
	 * @see mauda.plugin.PlugInHandler
	 */
	public void setDataDirectory(String directory) { dataDirectory = directory; }
	/**
	 * Gets the data-directory
	 * @return Data-Directory
	 * @see mauda.SimpleExercise#setDataDirectory(String)
	 */
	public String getDataDirectory() { return dataDirectory; }
	
	/**
	 * Sets the possible operations for a data-structure. This
	 * is used by e.g. input-dialogs like in init-tab, where
	 * different operations can be added.
	 * @param v Vector of operation-IDs
	 * @see mauda.plugin.PlugInHandler
	 */
	public void setAvailableOperations(Vector v) { availableOperations = v; }
	/**
	 * Gets the possible operations for a data-structure
	 * @return Vector of operation-IDs
	 * @see mauda.SimpleExercise#setAvailableOperations(Vector)
	 */
	public Vector getAvailableOperations() { return availableOperations; }
	
	/**
	 * Sets the possible suboperations for a data-structure.
	 * This is used by e.g. new generated plugins to initialize
	 * the rating.xml-file.
	 * @param v Vector of suboperation-IDs
	 * @see mauda.plugin.PlugInHandler
	 */
	// Verfügbare SubOperationen
	public void setAvailableSubOperations(Vector v) { availableSubOperations = v; }
	/**
	 * Gets the possible suboperations for a given data-structure.
	 * @return Vector of suboperation-IDs
	 */
	public Vector getAvailableSubOperations() { return availableSubOperations; }

	/**
	 * Sets the OperationExecuter
	 * @param oe OperationExecuter
	 * @see mauda.plugin.PlugInHandler
	 */
	public static void setOperationExecuter(OperationExecuter oe) { operationExecuter = oe; }
	/**
	 * Gets the OperationExecuter
	 * @return OperationExecuter
	 */
	public static OperationExecuter getOperationExecuter() { return operationExecuter; }

	/**
	 * Gets the Meta-Data
	 * @return Meta-Data
	 */
	public MetaData getMetaData() { return metaData; }
	/**
	 * Sets the Meta-Data
	 * @param meta Meta-Data
	 */
	public void setMetaData(MetaData meta) { this.metaData = meta; }

	public void updateMetaDataInitTodoRating() {
		getMetaData().setInitRating(getOperationRecorder().getAllInitOperationQueue().getRating());
		getMetaData().setTodoRating(getOperationRecorder().getAllTodoOperationQueue().getRating());		
	}

	/**
	 * Sets the mode of the exercise, e.g. NORMAL or FAULT
	 * @param mode mode-ID
	 * @see mauda.ExerciseMode
	 */
	public void setMode(int mode) {
		this.mode = mode;
		metaData.setMode(mode);
	}
	/**
	 * Gets the mode of the exercise
	 * @return mode-ID
	 * @see mauda.SimpleExercise#setMode(int)
	 */
	public int getMode() { return mode; }
	
	/**
	 * Sets the difficulty
	 * @param difficulty difficulty-ID
	 * @see mauda.Difficulties
	 */
	public void setDifficulty(int difficulty) {
		this.difficulty = difficulty;
		metaData.setDifficulty(difficulty);
	}
	/**
	 * Gets the difficulty
	 * @return Difficulty-ID
	 * @see mauda.SimpleExercise#setDifficulty(int)
	 */
	public int getDifficulty() {
		return difficulty;
	}
	
	/**
	 * Sets the data-structure, e.g. FibHeap.
	 * @param o The data-structure-object
	 */
	public void setDSObject(Object o) { dsObject = o; }
	/**
	 * Gets the data-structure
	 * @return data-structure-object
	 * @see mauda.SimpleExercise#setDSObject(Object)
	 */
	public Object getDSObject() { return dsObject; }
	
	/**
	 * Gets the OperationRecorder
	 * @return OperationRecorder
	 */
	public OperationRecorder getOperationRecorder() {
		return operationRecorder;
	}

	/**
	 * This method is called, when in <code>JedasPanel</code> an
	 * operation was executed.
	 * @param obj The executed operation
	 */
	public void operationExecuted(Object obj) {
		//System.out.println("SimpleExercise.operationExecuted(): "+obj);
		if(obj instanceof SimpleOperation) {
			ExerciseUpdateEvent eue = new ExerciseUpdateEvent(this, ExerciseUpdateEvent.OPERATION_EXECUTED);
			eue.setOperation((SimpleOperation)obj);
			sendExerciseUpdateMessages(eue);
		}
	}

	/**
	 * Loads an exercise
	 * @param filename Filename
	 * @return HashMap-representation of the exercise
	 */
	// ACHTUNG: switchTo funktioniert bei einer reinen
	// SimpleExercise-Instanz nicht. Wird aber auch nicht
	// benötigt. Es sei denn man will im AutoGenerator zwischen
	// PlugIns hin- und herspringen.
	public HashMap load(String filename) {
		//System.out.println("SimpleExercise.load(): LOAD EXERCISE...");
		
		Vector v = XMLFileAccess.load(filename);
		if(v==null) return null;	// <-- sollte nie vorkommen, da dies zuvor schon abgefragt werden muss

		HashMap ht = (HashMap)v.firstElement();
		
		boolean ok = loadConfig(ht);
		if(!ok) return null;
		return ht;
	}
	
	/**
	 * Configurates the editor for loading a specific exercise,
	 * what means, that e.g. the plug-in was checked, etc.
	 * @param ht HashMap-representation of the exercise
	 * @return true if loading was successful, false otherwise
	 */
	// Editor konfigurieren für das Laden einer Aufgabe
	public boolean loadConfig(HashMap ht) {

		// alten Typ vermerken
		String oldType = metaData.getType();
		
		// Meta-Daten
		// zunächst in ein neu erzeugtes Objekt einfügen, falls
		// es Probleme gibt. Da dann ein Abbruch des Ladens
		// kein Problem ist (Aktuelle Aufgabe bleibt erhalten)
		MetaData m = new MetaData();
		m.load(ht.get("Meta-Data"));
		String newType = m.getType();	// neuen Typ holen
		// Falls anderer Typ: PlugIn wechseln
		if(!newType.equals(oldType)) {
			String plugInID = getPlugInHandler().getPlugInID(newType);
			if(plugInID == null) {
				// Dieser Fall sollte eigenlich NIE vorkommen
				// Kann nur vorkommen, wenn man an den XML-Dateien etwas verändert
				javax.swing.JOptionPane.showMessageDialog(MAUDA.getApplicationMainFrame(), "Error: Unknown type. Cant load PlugIn!");
				return false;
			}
			//javax.swing.JOptionPane.showMessageDialog(MAUDA.getApplicationMainFrame(), "Switch PlugIn to "+plugInID);
			switchTo(plugInID);
		}

		// Meta-Daten
		metaData.load(ht.get("Meta-Data"));

		difficulty = metaData.getDifficulty();
		setDifficulty(difficulty);

		setMode(metaData.getMode());
		//javax.swing.JOptionPane.showMessageDialog(MAUDA.getApplicationMainFrame(), "Mode: "+m);
		
		// if-Zeile für Abwärtskompatibilität
		if(ht.containsKey("Ratings"))
			getOperationExecuter().getRatings().load((Vector)ht.get("Ratings"));

		return true;
	}
	/**
	 * Loads a unworked exercise
	 * @param ht HashMap-representation of the exercise
	 * @return true if loading successful, false otherwise
	 */
	// Unbearbeitete Aufgabe laden
	public boolean load(HashMap ht) {		
	
		OperationQueue initOps = new OperationQueue();
		OperationQueue todoOps = new OperationQueue();
		initOps.load((Vector)ht.get("InitOperations"));
		todoOps.load((Vector)ht.get("TodoOperations"));
		
		operationRecorder.clear();
		operationRecorder.addOperations(initOps.toVector());
		operationRecorder.goInTodoMode();
		operationRecorder.addOperations(todoOps.toVector());
		return true;
	}
	
	/**
	 * Saves an exercise. If the filename is null, the exercise
	 * won't be saved to disc, but returned as an HashMap.
	 * @param filename Filename
	 * @return HashMap-representation of the exercise
	 */
	// Aufgabe speichern
	// filename == null => nicht speichern
	public HashMap save(String filename) {
		//System.out.println("SimpleExercise.save(): SAVE EXERCISE...");

		HashMap ht = new HashMap();
		
		OperationQueue initOps = operationRecorder.getAllInitOperationQueue();
		
		// Folgendes speichert die ToDo's nur bis zur aktuellen Position
		//OperationQueue todoOps = operationRecorder.getTodoOperationQueue();
		
		// Folgendes speichert alle ToDo'S		
		OperationQueue todoOps = operationRecorder.getAllTodoOperationQueue();		

		metaData.setInitRating(initOps.getRating());
		metaData.setTodoRating(todoOps.getRating());

		// Meta-Daten
		ht.put("Meta-Data", metaData.save());
				
		ht.put("InitOperations", initOps.save());
		ht.put("TodoOperations", todoOps.save());
		
		ht.put("Ratings", getOperationExecuter().getRatings().save());
				
		// Wenn Dateiname = null, dann nicht speichern
		if(filename!=null) {
			Vector v = new Vector();
			v.add(ht);
			boolean result = XMLFileAccess.save(filename, v);
			if(!result) javax.swing.JOptionPane.showMessageDialog(MAUDA.getApplicationMainFrame(), "Cant save file: '"+filename+"' !\n\n"+
									"--> File not saved!");
		}
		return ht;
	}

}
