/*
 * Created on 25.06.2004 12:35:03
 *
 * Multimediale Algorithmen und Datenstrukturen Assessments
 */
package evalplayer;

import java.awt.*;
import javax.swing.*;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;

import jedas.io.PlayerScheduler;

import mauda.OperationRecorder;
import mauda.evaluation.PlayerExercise;
import mauda.evaluation.TimeStampRecorder;
import mauda.evaluation.OpTimeStamp;
import mauda.operation.*;
import mauda.*;

/**
 * Handles the functionality to jump to any time in an animation/exercise.
 * 
 * @author Markus Krebs
 */
public class TimeSlider implements ChangeListener {
	
	private PlayerExercise exercise;

	private PlayerScheduler playerScheduler;
	
	private SimpleOperation actOperation;
	
	private final String actTimeLabelText = " Time: ";
	private final String lengthLabelText = " Length: ";
	
	private JPanel panel;
	private JLabel actTimeLabel;
	private JLabel lengthLabel;
	private JSlider slider;
	
	private static TimeSlider instance = null;
	
	// Wird gesetzt, wenn man den Slider von Hand bewegt
	private boolean manualChange;
	
	// für dynamische Programmierung
	// Bewegung innerhalb eines Blocks brauchen bei "updateTime()" nicht neu berechnet werden
	private long currentSeqStart;	// slider-Start-Zeit
	private long currentSeqEnd;		// slider-Stop-Zeit
	private long currentStart;		// Animations-Start-Zeit des aktuellen Start/Stop-Blocks
	private long currentOpEnd;		// Stop-Zeit der Operation (letzte Zeit die noch zur Operation gehört)
	private int position;			// Aktuelle Position
	private int oldPosition;		// Position der zuletzt ausgeführten Operation
	public SimpleOperation opProcessing;	// Gibt an ob eine Operation schon START_OF_EXECUTION erhalten hat (!=null)

	/**
	 * Creates a new TimeSlider
	 * @param exercise The current exercise
	 */
	public TimeSlider(PlayerExercise exercise) {
		super();
		this.exercise = exercise;
		playerScheduler = null;
		actOperation = null;
		panel = new JPanel();
		panel.setLayout(new BorderLayout());
		actTimeLabel = new JLabel(actTimeLabelText+getTimeString(0));
		lengthLabel = new JLabel(lengthLabelText+getTimeString(0)+" ");
		slider = new JSlider(0,0);	// New Slider: 0-maxValue
		
		JPanel top = new JPanel();
		top.setLayout(new BorderLayout());
		top.add(actTimeLabel, BorderLayout.WEST);
		top.add(lengthLabel, BorderLayout.EAST);
		
		panel.add(top, BorderLayout.NORTH);
		
		panel.add(slider, BorderLayout.CENTER);
		slider.addChangeListener(this);
		slider.setValue(0);

		Hashtable ht = new Hashtable();
		ht.put(new Integer(0), new JLabel("|"));
		slider.setLabelTable(ht);
		slider.setPaintLabels(true);
		
		updateTime();
		instance = this;
	}
	/* (non-Javadoc)
	 * @see javax.swing.event.ChangeListener#stateChanged(javax.swing.event.ChangeEvent)
	 */
	public void stateChanged(ChangeEvent e) {
		//if(manualChange) System.out.println("TimeSlider.stateChanged(): manualChange!");
		if(manualChange&&exercise.isPlaying()) {
			exercise.stopPlaying();
			try{
				exercise.operationPlayerThread.join();
			} catch(Exception ex) {
				System.out.println("TimeSlider.updateTime(): sleep-Exception");				
			}
			return;
		}
		updateTime();
	}
	/**
	 * Returns a panel that contains a slider to control the time.
	 * @return A panel containing a slider
	 */
	public JPanel getPanel() { return panel; }
	
	/**
	 * Configurates the time-slider with the current values of the actual exercise.
	 * E.g. labels were created, that represent the start-time-stamps of each
	 * operation.<br>
	 * This method should be called, when an other exercise was loaded.
	 */
	public void configure() {
		//System.out.println("TimeSlider.configure(): call");

		manualChange = true;
		
		// Dynamische Programmierung
		currentSeqStart = -1;
		currentSeqEnd = -1;
		currentStart = -1;
		currentOpEnd = -1;
		position = -1;
		oldPosition = -2;	// Initial: -2 == noch nicht fertig geladen (siehe updateAll(0)) / -1 ist ja "The exercise"
		opProcessing = null;

		playerScheduler = exercise.getJedasPlayer().getPlayerScheduler();

		slider.setValue(0);

		TimeStampRecorder tsr = exercise.getTimeStampRecorder();
		OpTimeStamp ots = (OpTimeStamp)tsr.getSeqTimeStamps().lastElement();
		long max = ((Long)ots.getTimeStamps().lastElement()).longValue();
		slider.setMaximum((int)max);	// Achtung: löst stateChanged(...) aus!!!
		//System.out.println("TimeSlider.configure(): "+max);
		
		Hashtable ht = new Hashtable();
		int counter = 0;
		Enumeration en = tsr.getSeqTimeStamps().elements();
		while(en.hasMoreElements()) {
			ots = (OpTimeStamp)en.nextElement();
			long start = ((Long)ots.getTimeStamps().firstElement()).longValue();
			//System.out.println("TimeSlider.configure(): "+start);
			//JLabel label = new JLabel(counter+"");
			JLabel label = new JLabel("|");
			ht.put(new Integer((int)start), label);
			counter++;
		}
		slider.setLabelTable(ht);
		slider.setPaintLabels(true);
		
		lengthLabel.setText(lengthLabelText+getTimeString((int)max)+" ");
	}
	
	/**
	 * Jump to a specified time
	 * @param t time in milliseconds
	 */
	public void setTime(long t) {
		manualChange = false;
		slider.setValue((int)t);
		manualChange = true;
	}
	/**
	 * Returns the current time-position
	 * @return time in milliseconds
	 */
	public long getTime() {
		return (long)slider.getValue();
	}
	
	private void updateTime() {
		if(exercise.getJedasPlayer() == null) return;
		playerScheduler = exercise.getJedasPlayer().getPlayerScheduler();

		int actualTime = slider.getValue();
		
		// Reale Zeit in der Animation auch anzeigen ?
		actTimeLabel.setText(actTimeLabelText+getTimeString(actualTime));
		//int realTime = (int)(actualTime-currentSeqStart+currentStart);
		//label.setText(labelText+getTimeString(actualTime)+"   "+getTimeString(realTime));
		
		// Bei Zeit 0 auf "The Exercise" (position = -1) springen
		if(actualTime == 0) {
			currentSeqStart = -1;
			currentSeqEnd = -1;
			currentStart = -1;
			currentOpEnd = -1;
			updateAll(0);
			return;
		}
		
		// Dynamische Programmierung (spart innerhalb von Start/Stop-Blöcken rechenzeit)
		// untere for-Schleifen müssen nicht ausgeführt werden
		if(actualTime>=currentSeqStart&&actualTime<=currentSeqEnd) {
			updateAll(actualTime);
			return;
		}
		//System.out.println("TimeSlider.updateTime(): calculation needed...");
		
		// Position berechnen und dahin springen
		TimeStampRecorder tsr = exercise.getTimeStampRecorder();
		Vector ts = tsr.getSeqTimeStamps();
		int size = ts.size();
		for(int i=0; i<size; i++) {
			OpTimeStamp ots = (OpTimeStamp)ts.elementAt(i);
			Vector v = ots.getTimeStamps();
			long start = ((Long)v.firstElement()).longValue();
			long end = ((Long)v.lastElement()).longValue();
			currentOpEnd = end;
			if(actualTime>=start&&actualTime<=end) {
				// Richtige Operation gefunden!!!
				// jetzt innerhalb der Operation suchen (mehrere Start/Stop-Zeiten pro Operation)
				size = v.size();
				for(int j=0; j<size; j+=2) {
					start = ((Long)v.elementAt(j)).longValue();
					end = ((Long)v.elementAt(j+1)).longValue();
					if(actualTime>=start&&actualTime<=end) {
						// Dynamische Programmierung:
						currentSeqStart = start;
						currentSeqEnd = end;

						long delta = actualTime-start;
						// Jetzt gilt:
						// i = Operation
						// j = TimeStamp innerhalb Operation
						// delta = Zeitunterschied seit aktueller Start-Zeit
						ts = tsr.getTimeStamps();
						ots = (OpTimeStamp)ts.elementAt(i);
						v = ots.getTimeStamps();
						start = ((Long)v.elementAt(j)).longValue();
						//end = ((Long)v.elementAt(j+1)).longValue();
						
						// Dynamische Programmierung:
						currentStart = start;
						
						position = i;
						updateAll(actualTime);
						//updateListenerDS();
						return;
					}
				}
			}
		}
	}
	private void updateAll(long actualTime) {

		if(actualTime==0) {
			checkAndSendOperationExecutedMessage();
			jumpToPosition(-1);
			
			if(oldPosition!=-2) {
				TimeStampRecorder tsr = exercise.getTimeStampRecorder();
				OpTimeStamp ots = (OpTimeStamp)tsr.getTimeStamps().firstElement();
				long s = ((Long)ots.getTimeStamps().firstElement()).longValue();
				//System.out.println("Jumping to Start = "+s);
				playerScheduler.jumpToTime(s);
				playerScheduler.updatePanels();
			}
			
			oldPosition = -1;

			return;
		}

		// Richtiger Zeitpunkt in der Animation: "start+delta"
		int delta = (int)(actualTime-currentSeqStart);
		// Richtiger Zeitpunkt in der Animation: "currentStart+delta"
		playerScheduler.jumpToTime((currentStart+delta)/(int)TimeStampRecorder.timeExtend);
		playerScheduler.updatePanels();
		
		//System.out.println("TimeSlider.updateAll(..): actualTime="+actualTime+" currentSeqStart="+currentSeqStart+" currentSeqEnd="+currentSeqEnd);
		
		OperationRecorder or = exercise.getOperationRecorder();
		SimpleOperation actOp = (SimpleOperation)or.getEntry(or.getOffset(0,-1)+position);
		//System.out.println("  actOp="+actOp);

		// START_OF_EXECUTION-Problem
		if(actualTime==currentOpEnd) {
			checkAndSendOperationExecutedMessage();
			if(position!=oldPosition) {
				//System.out.println("TimeSlider.updateAll(..): BOLD (END OF OPERATION): "+opProcessing);
				jumpToPosition(position);
				oldPosition = position;
			}
		} else {
			if(position != oldPosition) {
				checkAndSendOperationExecutedMessage();
				opProcessing = actOp;
				//System.out.println("TimeSlider.updateAll(..): BOLD-CURSIVE (PROCESSING OPERATION) (1): "+opProcessing);
				jumpToPosition(position-1);
				sendStartOfExecutionMessage();
				oldPosition = position;
			} else {
				// Rückwärts
				if(opProcessing == null) {
					opProcessing = actOp;
					//System.out.println("TimeSlider.updateAll(..): BOLD-CURSIVE (PROCESSING OPERATION) (2): "+opProcessing);
					jumpToPosition(position-1);
					sendStartOfExecutionMessage();
					oldPosition = position;
				}
			}
		}
		
		//jumpToPosition(position);
		//oldPosition = position;
	}
	
	/**
	 * Checks and perform an eventually needed OPERATION_EXECUTED-Message. This means
	 * if actually a START_OF_EXECUTION-Message was sended, then an OPERATION_EXECUTED
	 * -Message must follow!
	 */
	public void checkAndSendOperationExecutedMessage() {
		if(opProcessing!=null) {
			//System.out.println("TimeSlider.checkAndSendOperationExecutedMessage(): OPERATION_EXECUTED: "+opProcessing);
			
			ExerciseUpdateEvent eue = new ExerciseUpdateEvent(exercise, ExerciseUpdateEvent.OPERATION_EXECUTED);
			eue.setOperation(opProcessing);
			exercise.sendExerciseUpdateMessages(eue);

			opProcessing = null;
		}		
	}
	private void jumpToPosition(int p) {
		// Jetzt DS-Aktualisierungen
		if(p != oldPosition) {
			OperationRecorder or = exercise.getOperationRecorder();
		
			ExerciseUpdateEvent eue = null;
			int d = p+or.getOffset(0,-1) - or.getCurrentOffset();
			//d--;
			if(d!=0) {
				eue = new ExerciseUpdateEvent(exercise, ExerciseUpdateEvent.JUMP);
				eue.setValue(d);
				exercise.sendExerciseUpdateMessages(eue);
			}
		}
	}
	private void sendStartOfExecutionMessage() {
		ExerciseUpdateEvent eue = new ExerciseUpdateEvent(exercise, ExerciseUpdateEvent.START_OF_EXECUTION);
		exercise.sendExerciseUpdateMessages(eue);
	}
	
	/**
	 * Returns an actual instance of TimeSlider
	 * @return TimeSlider-Instance
	 */
	public static TimeSlider getInstance() {
		return instance;
	}
	
	private String getTimeString(int time) {
		time = time/(int)mauda.evaluation.TimeStampRecorder.timeExtend;
		int ms = time%1000;
		int sec = (time/1000)%60;
		int min = (time/60000)%60;
		int hour = (time/(60000*60))%60;
		String msstr = ms+"";
		while(msstr.length()<3) msstr = "0"+msstr;
		return hour+":"+(min<10?"0":"")+min+":"+(sec<10?"0":"")+sec+"."+msstr;
	}
}
