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

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import javax.media.Controller;
import javax.media.ControllerErrorEvent;
import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.NotRealizedError;
import javax.media.Player;
import javax.media.StartEvent;
import javax.media.StopEvent;
import javax.swing.SwingUtilities;

import net.sf.fmj.ejmf.toolkit.gui.controls.AbstractListenerControl;
import net.sf.fmj.ejmf.toolkit.gui.controls.Skin;


/**  
  * The AbstractControls class:
  * <ul>
  * <li>maintains a Player reference
  * <li>maintains a Hashtable of Controls
  * <li>invokes setController on all of its Controls
  * <li>registers itself on a Player as a ControllerListener
  * </ul> 
  * The controllerUpdate method implements default behavior for
  * changing control component state to reflect Player state. 
  * <p>
  * Subclasses must supply definitions of the following methods:
  * <ul>
  *
  * <li>void makeControls();
  * <li>void setControlComponentState(int state);
  * </ul>
  */

abstract class AbstractControls_1 implements ControllerListener {
    private Player 			player;
    private Hashtable			controlTable = new Hashtable();

    private final Skin skin;
    
    // Make explicit requirement of having subclass call 
    // single argument constructor.
    private AbstractControls_1(Skin skin) {
    	this.skin = skin;
    }

    protected AbstractControls_1(Skin skin, Player player) {
    	this.skin = skin;

	if (player.getState() < Controller.Realized)
	    throw new NotRealizedError("Player must be realized");

	this.player = player;
        makeControls(skin);
        setControlsPlayer(player);
	player.addControllerListener(this);
	setControlComponentState(player.getState());	// KAL: added because there seems to be a race condition where the player can change states before our listener gets registered.
    }

	/**
	* Returns a Control with a given name.
	* @param name String identifying an AbstractControlListener
	* @return an AbstractControlListener identified by the	
	* name argument.
	*/
    public AbstractListenerControl getControl(String name) {
	return (AbstractListenerControl) controlTable.get(name);
    }

	/**	
	* Returns an array of Controls.
	* @return an array of AbstractListenerControls
	* associated with this Control Panel.
	*/
    public AbstractListenerControl[] getControls() {
	Vector	v = new Vector();
	Enumeration elements = controlTable.elements();
	while (elements.hasMoreElements()) {
	    v.addElement(elements.nextElement());
	}
	AbstractListenerControl[] controls = 
			new AbstractListenerControl[v.size()];
	v.copyInto(controls);
        return controls;
    }

    /**
      	* For each control, calls its setPlayer method to
      	* establish the controls association with a Player.
	* @param player Player associated with this set of control.
      */
    private void setControlsPlayer(Player player) {
	Controller c = player;
	Enumeration e = controlTable.elements();
        while (e.hasMoreElements()) {
	    AbstractListenerControl alc = 
		(AbstractListenerControl) e.nextElement();
	    alc.setController(c);
	}
    }

	/** 
	* Add a Control to this AbstractControls object.
	* @param name Name of control	
	* @param alc An AbstractListenerControl reference.
        */
    protected void addControl(String name, AbstractListenerControl alc) {
	controlTable.put(name, alc);
    }
  
	/**
	* Return the Player associated with this AbstractControls
	* object.
	* @return The Player associated with these controls.
	*/
    public Player getPlayer() {
	return player;
    }

    /** 
      * This method fields ControllerEvents. Specifically,
      * it looks for start, stop and error events and calls
      * setControlComponentState with either Controller.Started
      * or Controller.Prefetched to signal whether Controller
      * is started or not.
      * <p>
      * Subclasses that override this method should
      * invoke super.controllerUpdate() if default
      * behavior is desired. 
      * to do anything meaningful.
	* @param event a ControllerEvent
      */
    public void controllerUpdate(ControllerEvent event) {
	if (event instanceof StartEvent) {
	    SwingUtilities.invokeLater(
		new ControllerEventThread(Controller.Started));

	} else if (event instanceof StopEvent ||
	           event instanceof ControllerErrorEvent) {

	    SwingUtilities.invokeLater(
		new ControllerEventThread(Controller.Prefetched));
	}
    }

    class ControllerEventThread implements Runnable {
        private int state;
	public ControllerEventThread(int state) {
	       this.state = state;
        }
   
	public void run() {
	    setControlComponentState(state);
        }
     }

	/**
	* Build the controls managed by this AbstractControls
 	* object.
	*/
    protected abstract void makeControls(Skin skin);
	/** 
	* Set the display state of control components based on the
	* state of the Player.
	* @param state The current state of the Player.
	*/
    protected abstract void setControlComponentState(int state);
}
