package net.sf.fmj.ejmf.toolkit.gui.controls;

import java.awt.Component;
import java.util.EventListener;

import javax.media.Controller;
import javax.media.ControllerErrorEvent;
import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.Duration;
import javax.media.MediaTimeSetEvent;
import javax.media.PrefetchCompleteEvent;
import javax.media.RestartingEvent;
import javax.media.StartEvent;
import javax.media.StopEvent;
import javax.media.Time;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import net.sf.fmj.ejmf.toolkit.util.SourcedTimer;
import net.sf.fmj.ejmf.toolkit.util.SourcedTimerEvent;
import net.sf.fmj.ejmf.toolkit.util.SourcedTimerListener;
import net.sf.fmj.ejmf.toolkit.util.TimeSource;

/**
* Progress slider for StandardControlPanel.
* This control maintains an internal time which 
* 'ticks'. At each tick, StandardProgressControl maps
* media time into a slider value and updates the progress slider.
* <p>
* The timer is provided by ejmf.toolkit.util.SourcedTimer.
* StandardProgressControl receieves 'ticks' by virtue of 
* being a SourcedTimerListener.
*/

public class StandardProgressControl extends ChangeListenerControl 
			implements ControllerListener,
				   TimeSource,
				   SourcedTimerListener {

    // Once prefetched, set to false.
    private boolean		firstPrefetch = true;

    private SourcedTimer        controlTimer;
   
    // Duration of Controller. 
    private long		duration;
 
    // Timer will fire every TIMER_TICK milliseconds
    final private static int TIMER_TICK = 250;

    public StandardProgressControl(Skin skin, Controller controller) {
	this(skin);
	setController(controller);
    }

    public StandardProgressControl(Skin skin) {
    	super(skin);
    }

	/**	
	* Create ProgressSlider
	* @see net.sf.fmj.ejmf.toolkit.gui.controls.skins.ejmf.ProgressSlider
	*/
    @Override
    protected Component createControlComponent(Skin skin) {
	return skin.createProgressSlider();
    }

	/**
	* Augments setController by adding itself as
	* as ControllerListener on the Controller and forcing
	* operational state to false. Availability of progress	
	* bar is determined only after Controller is prefetch
	* and duration is determinate.
	*/
    @Override
	protected void setControllerHook(Controller controller) {
	setOperational(false);
	getController().addControllerListener(this);
	
	if (controller.getState() >= Controller.Prefetched)
	{	init();	// KAL: added to handle case where controller starts before our controllerUpdate handler gets registered.
	}
    }

    private void init() {
	Time d = getController().getDuration();
	// If duration is unknown or unbounded, slider
        // will not be operational.
	boolean flg = 
        	d != Duration.DURATION_UNBOUNDED &&
         	d != Duration.DURATION_UNKNOWN;

	// We have some know duration, is it zero?
	if (flg) {
	    duration = d.getNanoseconds();
	    flg = (duration != 0L);
	}
	setOperational(flg);
	// Duration is known and non-zero, all is well...
	if (flg) {
	    // Setup timer
	    controlTimer = new SourcedTimer(this, TIMER_TICK);
  	    controlTimer.addSourcedTimerListener(this);

	    Time mTime = getController().getMediaTime();
	    long mediaTime = mTime.getNanoseconds();

	    setValue(mediaTime);
	    
	    if (getController().getState() == Controller.Started)
	    	controlTimer.start();	// this handles the case where it is already started before we get here, in which case controllerUpdate will never get called with the initial state
	    
        } 
    }

	/**
	* Create ChangeListener. Tracks user movement	
	* of progress slider and update media time		
	* accordingly.
	*/
    @Override
	protected EventListener createControlListener() {
	return new ChangeListener() {
 	    public void stateChanged(ChangeEvent e) {
     	    	ProgressBar s = (ProgressBar) e.getSource();
     	    	int value = s.getValue();
     	    	long mediaNanos = ((value * duration) /
                      (s.getMaximum() - s.getMinimum()));

     	    	// Intel JMF: Setting media time will update video frame
     	    	// Sun JMF: Video does not re-render until controller is restarted
	 	Controller controller = getController();
		int priorState = controller.getState();
		if (priorState == Controller.Started) {
		    controller.stop();
	        }

     	    	controller.setMediaTime(new Time(mediaNanos));

		if (priorState == Controller.Started) {
		    Time now = controller.getTimeBase().getTime();
		    controller.syncStart(now);
		}
     	    }
        };
    }

    /** 
      * Position slider based on mediaTime
      */
    public void setValue(long mediaTime) {
        ProgressBar bar;
	if (!isOperational()) {
	    return;
        }
	bar = (ProgressBar) getControlComponent();
 	long diff = bar.getMaximum() - bar.getMinimum();
 	// Translate time to slider value
 	int value = (int) ((diff * mediaTime) / duration);

 	SwingUtilities.invokeLater(
		new SetProgressSliderValueThread(bar, value));
    }


	/**	
	* If the progress slider is operational, the
	* controllerUpdate method starts and stops its
	* time based on Start- and StopEvents from the Controller.
	* If response to MediaTimeSetEvent, the value of the
	* progress slider is explicitly set.
	* <p>
	* The controllerUpdate method is also responsible for
	* setting the operational state of the Control based	
	* on duration value. This is done in response to first
	* PrefetchCompleteEvent.
	*/
    public void controllerUpdate(ControllerEvent event) {
  	if (isOperational()) {
      	    if (event instanceof StartEvent ||
                event instanceof RestartingEvent) {
		   controlTimer.start();
      	     } else if (event instanceof StopEvent ||
          	event instanceof ControllerErrorEvent) {
                   controlTimer.stop();
             } else if (event instanceof MediaTimeSetEvent) {
		// This catches any direct setting of media time	
		// by application. Additionally, it catches
		// setMediaTime(0) by StandardStopControl.
		   setValue(getTime());
	     }
  	} else {
	    if ( firstPrefetch && event instanceof PrefetchCompleteEvent) {
		firstPrefetch = false;
	        init();
		//SwingUtilities.invokeLater(new EnableComponentThread());
	    }
        }
    }
   
    class EnableComponentThread implements Runnable {
	public void run() {
	    getControlComponent().setEnabled(isOperational());
	}
    }
    
     	/**
  	* This method implements the SourcedTimerListener interface.
  	* Each timer tick causes slider thumbnail to move if a ProgressBar
  	* was built for this control panel.
  	*
  	* @see            net.sf.fmj.ejmf.toolkit.util.SourcedTimer
  	*/
    public void timerUpdate(SourcedTimerEvent e) {
     	// Since we are also the TimeSource, we can get
     	// directly from StandardControls instance.
     	// Normally, one would call e.getTime().

	setValue(getTime());
    }

    class SetProgressSliderValueThread implements Runnable {
        int 		value;
	ProgressBar	bar;

        public SetProgressSliderValueThread(ProgressBar bar, int value) {
	    this.value = value;
	    this.bar = bar;
        }

        public void run() {
	    bar.setValue(value);
	}
    }

  // For TimeSource interface
	/**
	* As part of TimeSource interface, getTime returns
	* the current media time in nanoseconds.	
	*/
    public long getTime() {
	return getController().getMediaNanoseconds();
    }

	/**
	* This method is used as a divisor to  convert
	* getTime to seconds.
	*/
    public long getConversionDivisor() {
         return TimeSource.NANOS_PER_SEC;
    }
}
