/*
 * Copyright (c) 2002, 2003, 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
