/*
 * @(#)ControllerTracker.java  1.1  2004-12-26
 *
 * Copyright (c) 1999-2004 Werner Randelshofer, Immensee, Switzerland.
 * All rights reserved.
 *
 * You may not use, copy or modify this file, except in compliance with the
 * license agreement you entered into with Werner Randelshofer.
 * For details see accompanying license terms.
 */
package ch.randelshofer.util;

import javax.media.Controller;
import javax.media.ControllerListener;
import javax.media.ControllerEvent;
import javax.media.ControllerClosedEvent;
import javax.media.TransitionEvent;

/**
 * Tracks state changes in a Java Media Framework Controller.
 *
 * @author  Werner Randelshofer, Hausmatt 10, CH-6405 Immensee, Switzerland
 * @version    1.1 2004-12-26 Method waitForState returns immediately, if the
 * Controller is in unrealized state. This is to prevent a livelock on the 
 * Controller.
 * <br>1.0  1999-10-19
 */
public class ControllerTracker
implements ControllerListener {
    
    private Controller model_;
    private int[] targetStates_;
    private int targetStateIndex_;
    private String message_;
    
    public final static int STATE_NOT_REACHED = -1;
    private final static int CONTROLLER_CLOSED = -2;
    
    /**
     * Creates a ControllerTracker for the indicated Controller.
     *
     * @param model Controller to be tracked.
     */
    public ControllerTracker(Controller model) {
        setController(model);
    }
    
    /**
     * Sets the Controller.
     * Note: This method must not be called while one of the
     *       waitForState methods is working.
     * @param  model The Controller to be tracked.
     */
    public void setController(Controller model) {
        if (model_ != null) {
            model_.removeControllerListener(this);
        }
        
        model_ = model;
        
        if (model_ != null) {
            model_.addControllerListener(this);
        }
    }
    
    public String getMessage() {
        return message_;
    }
    
    /**
     * Waits until the Controller reaches the indicated
     * state.
     * Note: waitForState methods may not be called from
     *       multiple threads simoultaneously.
     *
     * @param  state  to wait for.
     */
    public int waitForState(int state) {
        int[] statelist = { state };
        return waitForState( statelist );
    }
    
    /**
     * Waits until the Controller reaches one of the indicated
     * states.
     *
     * Note: waitForState methods may not be called from
     *       multiple threads simoultaneously.
     *
     * @param  states  choice of states to wait for.
     * @return  The state or STATE_NOT_REACHED if the controller is closed.
     */
    public int waitForState(int[] states) {
        synchronized (this) {
            targetStates_ = states;
            targetStateIndex_ = STATE_NOT_REACHED;
            int state = model_.getState();
            
            // If the controller is in unrealized state, we return immediately
            if (state == Controller.Unrealized) {
                for (int i=0; i < targetStates_.length; i++) {
                    if (state == targetStates_[i]) {
                        return targetStates_[i];
                    }
                }
                return STATE_NOT_REACHED;
            }
            
            // Check if the controller has reached the desired state
            for (int i=0; i < targetStates_.length; i++) {
                if (state == targetStates_[i]) {
                    return targetStates_[i];
                }
            }
            
            // Wait until the controller reaches the desired state, or gets closed
            while (targetStateIndex_ == STATE_NOT_REACHED
            && targetStateIndex_ != CONTROLLER_CLOSED) {
                try { wait(); } catch (InterruptedException e) {}
            }
        }
        return (targetStateIndex_ >= 0) ? targetStates_[targetStateIndex_] : STATE_NOT_REACHED;
    }
    
    /**
     * XXX This method is public as an implementation side effect.
   " Do not call or override.
     */
  @Override
    public void controllerUpdate(ControllerEvent event) {
        synchronized (this) {
            if (event instanceof ControllerClosedEvent) {
                targetStateIndex_ = CONTROLLER_CLOSED;
                message_ = ((ControllerClosedEvent) event).getMessage();
                notifyAll();
            } else if (event instanceof TransitionEvent) {
                if (targetStates_ != null
                && targetStateIndex_ == -1) {
                    int state = ((TransitionEvent) event).getCurrentState();
                    
                    for (int i=0; i < targetStates_.length; i++) {
                        if (state == targetStates_[i]) {
                            targetStateIndex_ = i;
                            notifyAll();
                            break;
                        }
                    }
                }
            }
        }
    }
}
