/*
 * Created on 21.03.2004 14:40:38
 *
 * Multimediale Algorithmen und Datenstrukturen Assessments
 */
package mauda.evaluation;

import mauda.ExerciseUpdateEvent;
import mauda.ExerciseUpdateListener;
import mauda.Exercise;
//import mauda.undoredo.UndoRedoInfo;

import java.util.*;

/**
 * Records the time-stamps for each operation during recording to
 * a jedas-animation.
 * 
 * @author Markus Krebs
 */
public class TimeStampRecorder implements ExerciseUpdateListener {
	
	// Millisekunden sind zu ungenau => *100
	// Bei einem Wert von 1000 könnte nur noch eine 35 Minuten Animation gemacht werden, deshalb
	// der Wert von 100 (müsste aber ausreichen)
	// Doch nicht notwendig, da bei dem Fall Stop=Start-Zeit trotzdem korrekt abgespielt wird
	public static final long timeExtend = 1;

	private Exercise exercise;

	private boolean finishedLoading;
	private boolean playerMode;
	
	//private UndoRedoInfo undoRedoInfo;	// Real Undo/Redo Wird nicht benötigt
	
	private Vector timeStamps;
	private int position;
	
	private int oldPosition;
	
	private int suspendPosition;
	
	private Vector seqTimeStamps;	// sequentielle TimeStamps: 0-Animationslänge (ohne Übersprünge)
	
	/**
	 * Creates a TimeStampRecorder
	 * @param exercise Exercise
	 */
	public TimeStampRecorder(Exercise exercise) {
		super();
		this.exercise = exercise;
		
		finishedLoading = false;
		
		//undoRedoInfo = new UndoRedoInfo();
		
		position = -1;
		oldPosition = -1;
		timeStamps = new Vector();
		playerMode = false;
		
		seqTimeStamps = null;
	}
	/**
	 * Sets the player-mode of the timestamp-recorder. If player-
	 * is true, then were no entries made for time-stamps, whereas
	 * in non-player-mode, the time-stamps have to be recorded.
	 * @param b true for player-mode, false otherwise.
	 */
	public void setPlayerMode(boolean b) { playerMode = b; }

	/* (non-Javadoc)
	 * @see mauda.ExerciseUpdateListener#exerciseUpdate(mauda.ExerciseUpdateEvent)
	 */
	public void exerciseUpdate(ExerciseUpdateEvent e) {
		if(e.getID() == ExerciseUpdateEvent.FINISHED_LOADING) {
			finishedLoading = true;
			return;
		}
		if(!finishedLoading) return;
		switch(e.getID()) {
			case ExerciseUpdateEvent.OPERATION_EXECUTED :
				// Wird leider erst nach Ausführung gesendet
				// => Unbrauchbar, da der TimeStamp zu Beginn
				// benötigt wird
				break;
			case ExerciseUpdateEvent.START_OF_EXECUTION :
				// Wird kurz vor Ausführung gesendet
				// Aber ACHTUNG: OperationRecorder enthält jetzt noch
				// nicht den aktuellen Eintrag!!!
				position++;
				storeStopStartTimeStamp();
				break;
			case ExerciseUpdateEvent.BACK :
				position--;
				//showComment();
				break;
			case ExerciseUpdateEvent.FORWARD :
				position++;
				storeStopStartTimeStamp();
				break;
			//case ExerciseUpdateEvent.UNDO :
			//	undoRedoInfo.undo(timeStamps);
			//	break;
			//case ExerciseUpdateEvent.REDO :
			//	undoRedoInfo.redo(timeStamps);
			//	break;			
			case ExerciseUpdateEvent.RESET:
				finishedLoading = false;
				position = -1;
				oldPosition = -1;
				timeStamps.removeAllElements();
				//undoRedoInfo.clear(new Vector());
				break;
			case ExerciseUpdateEvent.JUMP :
				storeStopTimeStamp(oldPosition, getRecordingTime());
				oldPosition = -1;	// damit beim nächstem START_OF_EXECUTION keine Stop-Zeit gesetzt wird
				position += e.getValue();
				//storeStopStartTimeStamp();
				break;
			case ExerciseUpdateEvent.END_OF_EXECUTION :
				//oldPosition = -1;
				// Nichts tun
				break;
			case ExerciseUpdateEvent.SUSPEND_EXECUTION :
				OpTimeStamp ots1 = (OpTimeStamp)timeStamps.elementAt(position);
				ots1.addStop(getRecordingTime());
				suspendPosition = position;
				break;
			case ExerciseUpdateEvent.RESUME_EXECUTION :
				OpTimeStamp ots2 = (OpTimeStamp)timeStamps.elementAt(suspendPosition);
				ots2.addStart(getRecordingTime());
				break;
			default :
				break;
		}

	}
	private long getRecordingTime() {
		long timeStamp = 0;
		if(jedas.Jedas.isRecording())
			timeStamp = jedas.Jedas.getRecordTime();
		return timeStamp*timeExtend;
	}
	// Speichern des Start-TimeStamps der aktuellen Operation
	// Speichern des Start-TimeStamps der aktuellen Operation
	private void storeStopStartTimeStamp() {
		if(playerMode) return;
		long timeStamp = getRecordingTime();

		//long timeStamp = ((long)position)*10;		

		storeStopTimeStamp(oldPosition, timeStamp);
		oldPosition = position;
		
		//undoRedoInfo.blabla...

		// Start-Zeit für aktuelle Operation setzen
		OpTimeStamp ots = null;
		if(position>=timeStamps.size()) {
			ots = new OpTimeStamp();
			timeStamps.add(ots);
		} 
		else {
			ots = (OpTimeStamp)timeStamps.elementAt(position);
		}
		ots.clear();
		ots.addStart(timeStamp);
		//System.out.println("TimeStamp: "+timeStamp);
	}
	
	private void storeStopTimeStamp(int p, long timeStamp) {
		if(playerMode) return;
		// Stop-Zeit für Operation setzen
		if(p>=0 && p<timeStamps.size()) {
			//System.out.println("TimeStampRecorder.storeStartTimeStamp(): setStop: oldPosition="+oldPosition+" stop="+timeStamp);
			OpTimeStamp oldots = (OpTimeStamp)timeStamps.elementAt(p);
			//oldots.addStop(timeStamp-1);
			oldots.addStop(timeStamp);
		}		
	}
		
	/**
	 * Gets a specific entry in the time-stamp-recorder.
	 * @param offset The position of the entry to be returned
	 * @return The time-stamp-object
	 */
	public OpTimeStamp getEntry(int offset) {
		if(offset<0 || offset>=timeStamps.size()) return null;
		return (OpTimeStamp)timeStamps.elementAt(offset);
	}
	/**
	 * Gets a specific entry in the sequential-time-stamps.
	 * @param offset The position of the entry to be returned
	 * @return The time-stamp-object
	 * @see TimeStampRecorder#calculateSeqTimeStamps()
	 */
	public OpTimeStamp getSeqEntry(int offset) {
		if(offset<0 || offset>=seqTimeStamps.size()) return null;
		return (OpTimeStamp)seqTimeStamps.elementAt(offset);		
	}
	/**
	 * Gets the actual time-stamp.
	 * @return The actual time-stamp-object
	 */
	public OpTimeStamp getActOpTimeStamp() {
		return getEntry(position);
	}
	
	/**
	 * Calculates from the eventually out-of-order-time-stamps sequential time-stamps.
	 * That means, that the out-of-order time-stamps are calculated in
	 * in-order-time-stamps in that way, that they were time-connected (no time-gaps).
	 * Additionally this method sets an eventually missing stop-time of the last
	 * operation.<br>
	 * This method should only called by EvalPlayer.
	 * @see TimeStampRecorder#getSeqTimeStamps()
	 */
	public void calculateSeqTimeStamps() {
		long animLength = ((PlayerExercise)exercise).getJedasPlayer().getJedasAnimation().animLength;
		// Position (TimeStamp) 0 für "The Exercise"
		long seqTime = 1;
		seqTimeStamps = new Vector();
		Enumeration en = timeStamps.elements();
		while(en.hasMoreElements()) {
			OpTimeStamp ots = (OpTimeStamp)en.nextElement(); 
			Vector v = ots.getTimeStamps();
			OpTimeStamp seqots = new OpTimeStamp();
			Enumeration en2 = v.elements();
			while(en2.hasMoreElements()) {
				long start = ((Long)en2.nextElement()).longValue();
				// Die Operation, der eine Stop-Zeit fehlt, ist automatisch die letzte
				// abgespielte, und erhält somit als Stop-Zeit die Länge der Animation
				// --> siehe else-Fall
				long end = animLength*(int)TimeStampRecorder.timeExtend;
				if(en2.hasMoreElements()) end = ((Long)en2.nextElement()).longValue();
				seqots.addStart(seqTime);
				seqTime += (end-start);
				seqots.addStart(seqTime);
				seqTime++;
				if(end == animLength) {
					// fehlende Stop-Zeit setzen und en2-Schleife beenden
					ots.addStop(animLength);
					break;
				}
			}
			seqTimeStamps.add(seqots);
		}
	}
	/**
	 * Gets the real time-stamps corresponding to the animation.
	 * @return time-stamps
	 */
	public Vector getTimeStamps() { return timeStamps; }
	/**
	 * Gets the by the TimeStampRecorder.calculateSeqTimeStamps calculated time-stamps.
	 * @return time-stamps
	 * @see TimeStampRecorder#calculateSeqTimeStamps()
	 */
	public Vector getSeqTimeStamps() { return seqTimeStamps; }
	
	/**
	 * Prints out the entries of the sequential time-stamps.
	 */
	public void printSeqTimeStamps() {
		System.out.println("TimeStampRecorder.printSeqTimeStamps():");
		int counter = 0;
		Enumeration en = seqTimeStamps.elements();
		while(en.hasMoreElements()) {
			counter++; System.out.println(" "+counter+". Operation:");
			OpTimeStamp ots = (OpTimeStamp)en.nextElement(); 
			Vector v = ots.getTimeStamps();
			Enumeration en2 = v.elements();
			while(en2.hasMoreElements()) {
				long start = ((Long)en2.nextElement()).longValue();
				long end = ((Long)en2.nextElement()).longValue();
				System.out.println("   "+start+" - "+end);
			}
		}		
	}
	
	/**
	 * Converts the time-stamps in an appropriate format, that can
	 * be saved to an xml-file
	 * @return A Vector representation of the time-stamps
	 */
	public Vector save() {
		// Stop-Zeit für letzte Operation setzen, aber nur dann, wenn
		// sie noch keine Stop-Zeit hat
		//OpTimeStamp lastots = (OpTimeStamp)timeStamps.lastElement();
		//lastots.addStop(Long.MAX_VALUE);
		//System.out.println("TimeStampRecorder.save(): recordingTime: "+getRecordingTime());
		
		// Jetzt speichern
		Vector v = new Vector();
		Enumeration en = timeStamps.elements();
		while(en.hasMoreElements()) {
			OpTimeStamp ots = (OpTimeStamp)en.nextElement();
			v.add(ots.save());
		}
		return v;
	}
	/**
	 * Converts a vector that contains the time-stamps readed from a
	 * xml-file, and converts it for the internal representation.
	 * @param v The vector from the xml-file
	 */
	public void load(Vector v) {
		Enumeration en = v.elements();
		while(en.hasMoreElements()) {
			HashMap hm = (HashMap)en.nextElement();
			OpTimeStamp ots = new OpTimeStamp();
			ots.load(hm);
			timeStamps.add(ots);
		}
	}

}
