/*
 * @(#)StateEngineImpl.java	1.22 10/03/23
 *
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package com.sun.corba.se.impl.orbutil.fsm ;

import java.util.HashMap ;
import java.util.HashSet ;
import java.util.Set ;
import java.util.Iterator ;

import org.omg.CORBA.INTERNAL ;

import com.sun.corba.se.impl.orbutil.ORBUtility ;

import com.sun.corba.se.spi.orbutil.fsm.Input ;
import com.sun.corba.se.spi.orbutil.fsm.Guard ;
import com.sun.corba.se.spi.orbutil.fsm.Action ;
import com.sun.corba.se.spi.orbutil.fsm.ActionBase ;
import com.sun.corba.se.spi.orbutil.fsm.State ;
import com.sun.corba.se.spi.orbutil.fsm.StateEngine ;
import com.sun.corba.se.spi.orbutil.fsm.StateImpl ;
import com.sun.corba.se.spi.orbutil.fsm.FSM ;
import com.sun.corba.se.spi.orbutil.fsm.FSMImpl ;

import com.sun.corba.se.impl.orbutil.fsm.GuardedAction ;

/**
 * Encodes the state transition function for a finite state machine.
 *
 * @author Ken Cavanaugh
 */
public class StateEngineImpl implements StateEngine
{
    // An action that does nothing at all.
    private static Action emptyAction = new ActionBase( "Empty" ) 
    {
	public void doIt( FSM fsm, Input in )
	{
	}
    } ;

    private boolean initializing ;
    private Action defaultAction ;

    public StateEngineImpl()
    {
	initializing = true ;
	defaultAction = new ActionBase("Invalid Transition")
	    {
		public void doIt( FSM fsm, Input in )
		{
		    throw new INTERNAL(
			"Invalid transition attempted from " + 
			    fsm.getState() + " under " + in ) ;
		}
	    } ;
    }

    public StateEngine add( State oldState, Input input, Guard guard, Action action,
	State newState ) throws IllegalArgumentException,
	IllegalStateException
    {
	mustBeInitializing() ;

	StateImpl oldStateImpl = (StateImpl)oldState ;
	GuardedAction ga = new GuardedAction( guard, action, newState ) ;
	oldStateImpl.addGuardedAction( input, ga ) ;

	return this ;
    }

    public StateEngine add( State oldState, Input input, Action action,
	State newState ) throws IllegalArgumentException,
	IllegalStateException
    {
	mustBeInitializing() ;

	StateImpl oldStateImpl = (StateImpl)oldState ;
	GuardedAction ta = new GuardedAction( action, newState ) ;
	oldStateImpl.addGuardedAction( input, ta ) ;

	return this ;
    }

    public StateEngine setDefault( State oldState, Action action, State newState )
	throws IllegalArgumentException, IllegalStateException
    {
	mustBeInitializing() ;

	StateImpl oldStateImpl = (StateImpl)oldState ;
	oldStateImpl.setDefaultAction( action ) ;
	oldStateImpl.setDefaultNextState( newState ) ;

	return this ;
    }

    public StateEngine setDefault( State oldState, State newState )
	throws IllegalArgumentException, IllegalStateException
    {
	return setDefault( oldState, emptyAction, newState ) ;
    }

    public StateEngine setDefault( State oldState )
	throws IllegalArgumentException, IllegalStateException
    {
	return setDefault( oldState, oldState ) ;
    }

    public void done() throws IllegalStateException
    {
	mustBeInitializing() ;

	// optimize FSM here if desired.  For example,
	// we could choose different strategies for implementing
	// the state transition function based on the distribution 
	// of values for states and input labels.

	initializing = false ;
    }

    public void setDefaultAction( Action act ) throws IllegalStateException
    {
	mustBeInitializing() ;
	defaultAction = act ;
    }

    public void doIt( FSM fsm, Input in, boolean debug )
    {
	// This method is present only for debugging.
	// innerDoIt does the actual transition.

	if (debug)
	    ORBUtility.dprint( this, "doIt enter: currentState = " + 
		fsm.getState() + " in = " + in ) ;

	try {
	    innerDoIt( fsm, in, debug ) ;
	} finally {
	    if (debug)
		ORBUtility.dprint( this, "doIt exit" ) ;
	}
    }

    private StateImpl getDefaultNextState( StateImpl currentState )
    {
	// Use the currentState defaults if 
	// set, otherwise use the state engine default.
	StateImpl nextState = (StateImpl)currentState.getDefaultNextState() ;
	if (nextState == null)
	    // The state engine default never changes the state
	    nextState = currentState ;

	return nextState ;
    }

    private Action getDefaultAction( StateImpl currentState ) 
    {
	Action action = currentState.getDefaultAction() ;
	if (action == null)
	    action = defaultAction ;

	return action ;
    }

    private void innerDoIt( FSM fsm, Input in, boolean debug )
    {
	if (debug) {
	    ORBUtility.dprint( this, "Calling innerDoIt with input " + in ) ;
	}

	// Locals needed for performing the state transition, once we determine
	// the required transition.
	StateImpl currentState = null ;
	StateImpl nextState = null ;
	Action action = null ;

	// Do until no guard has deferred. 
	boolean deferral = false ;
	do {
	    deferral = false ; // clear this after each deferral!
	    currentState = (StateImpl)fsm.getState() ;
	    nextState = getDefaultNextState( currentState ) ;
	    action = getDefaultAction( currentState ) ;

	    if (debug) {
		ORBUtility.dprint( this, "currentState      = " + currentState ) ;
		ORBUtility.dprint( this, "in                = " + in ) ;
		ORBUtility.dprint( this, "default nextState = " + nextState    ) ;
		ORBUtility.dprint( this, "default action    = " + action ) ;
	    }

	    Set gas = currentState.getGuardedActions(in) ;
	    if (gas != null) {
		Iterator iter = gas.iterator() ;

		// Search for a guard that is not DISABLED.  
		// All DISABLED means use defaults.
		while (iter.hasNext()) {
		    GuardedAction ga = (GuardedAction)iter.next() ;
		    Guard.Result gr = ga.getGuard().evaluate( fsm, in ) ;
		    if (debug)
			ORBUtility.dprint( this, 
			    "doIt: evaluated " + ga + " with result " + gr ) ;

		    if (gr == Guard.Result.ENABLED) {
			// ga has the next state and action.
			nextState = (StateImpl)ga.getNextState() ;
			action = ga.getAction() ;
			if (debug) {
			    ORBUtility.dprint( this, "nextState = " + nextState ) ;
			    ORBUtility.dprint( this, "action    = " + action ) ;
			}
			break ;
		    } else if (gr == Guard.Result.DEFERED) {
			deferral = true ;
			break ;
		    }
		}
	    }
	} while (deferral) ;

	performStateTransition( fsm, in, nextState, action, debug ) ;
    }

    private void performStateTransition( FSM fsm, Input in, 
	StateImpl nextState, Action action, boolean debug )
    {
	StateImpl currentState = (StateImpl)fsm.getState() ;

	// Perform the state transition.  Pre and post actions are only
	// performed if the state changes (see UML hidden transitions).

	boolean different = !currentState.equals( nextState ) ;

	if (different) {
	    if (debug)
		ORBUtility.dprint( this, 
		    "doIt: executing postAction for state " + currentState ) ;
	    try {
		currentState.postAction( fsm ) ;
	    } catch (Throwable thr) {
		if (debug)
		    ORBUtility.dprint( this, 
			"doIt: postAction threw " + thr ) ;

		if (thr instanceof ThreadDeath)
		    throw (ThreadDeath)thr ;
	    }
	}

	try {
	    // Note that action may be null in a transition, which simply
	    // means that no action is needed.  Note that action.doIt may
	    // throw an exception, in which case the exception is
	    // propagated after making sure that the transition is properly
	    // completed.
	    if (action != null)
		action.doIt( fsm, in ) ;
	} finally {
	    if (different) {
		if (debug)
		    ORBUtility.dprint( this, 
			"doIt: executing preAction for state " + nextState ) ;

		try {
		    nextState.preAction( fsm ) ;
		} catch (Throwable thr) {
		    if (debug)
			ORBUtility.dprint( this, 
			    "doIt: preAction threw " + thr ) ;

		    if (thr instanceof ThreadDeath)
			throw (ThreadDeath)thr ;
		}

		((FSMImpl)fsm).internalSetState( nextState ) ;
	    }

	    if (debug)
		ORBUtility.dprint( this, "doIt: state is now " + nextState ) ;
	}
    }

    public FSM makeFSM( State startState ) throws IllegalStateException
    {
	mustNotBeInitializing() ;

	return new FSMImpl( this, startState ) ;
    }

    private void mustBeInitializing() throws IllegalStateException
    {
	if (!initializing)
	    throw new IllegalStateException( 
		"Invalid method call after initialization completed" ) ;
    }

    private void mustNotBeInitializing() throws IllegalStateException
    {
	if (initializing)
	    throw new IllegalStateException( 
		"Invalid method call before initialization completed" ) ;
    }
}

// end of StateEngineImpl.java

