package org.squirrelframework.foundation.fsm.impl;

import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.squirrelframework.foundation.component.Observable;
import org.squirrelframework.foundation.component.SquirrelProvider;
import org.squirrelframework.foundation.component.impl.AbstractSubject;
import org.squirrelframework.foundation.event.AsyncEventListener;
import org.squirrelframework.foundation.event.ListenerMethod;
import org.squirrelframework.foundation.exception.ErrorCodes;
import org.squirrelframework.foundation.exception.TransitionException;
import org.squirrelframework.foundation.fsm.*;
import org.squirrelframework.foundation.fsm.ActionExecutionService.*;
import org.squirrelframework.foundation.fsm.annotation.*;
import org.squirrelframework.foundation.util.Pair;
import org.squirrelframework.foundation.util.ReflectUtils;
import org.squirrelframework.foundation.util.TypeReference;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import static com.google.common.base.Preconditions.checkState;

/**
 * The Abstract state machine provide several extension ability to cover different extension granularity. 
 * <ol>
 * <li>Method <b>beforeStateExit</b>/<b>afterStateEntry</b> is used to add custom logic on all kinds of state exit/entry.</li>
 * <li>Method <b>exit[stateName]</b>/<b>entry[stateName]</b> is extension method which is used to add custom logic on specific state.</li>
 * <li>Method <b>beforeTransitionBegin</b>/<b>afterTransitionComplete</b> is used to add custom logic on all kinds of transition 
 * accepted all conditions.</li>
 * <li>Method <b>transitFrom[fromStateName]To[toStateName]On[eventName]</b> is used to add custom logic on specific transition 
 * accepted all conditions.</li>
 * <li>Method <b>transitFromAnyTo[toStateName]On[eventName]</b> is used to add custom logic on any state transfer to specific target 
 * state on specific event happens, so as the <b>transitFrom[fromStateName]ToAnyOn[eventName]</b>, <b>transitFrom[fromState]To[ToStateName]</b>, 
 * and <b>on[EventName]</b>.</li>
 * </ol>
 * @author Henry.He
 *
 * @param <T> state machine type
 * @param <S> state type
 * @param <E> event type
 * @param <C> context type
 */
public abstract class AbstractStateMachine<T extends StateMachine<T, S, E, C>, S, E, C> extends AbstractSubject implements StateMachine<T, S, E, C> {
    
    private static final Logger logger = LoggerFactory.getLogger(AbstractStateMachine.class);
    
    private final ActionExecutionService<T, S, E, C> executor = SquirrelProvider.getInstance().newInstance(
            new TypeReference<ActionExecutionService<T, S, E, C>>(){});
    
    private StateMachineData<T, S, E, C> data;
    
    private volatile StateMachineStatus status = StateMachineStatus.INITIALIZED;
    
    private LinkedBlockingDeque<Pair<E, C>> queuedEvents = new LinkedBlockingDeque<Pair<E, C>>();
    
    private LinkedBlockingQueue<Pair<E, C>> queuedTestEvents = new LinkedBlockingQueue<Pair<E, C>>();
    
    private volatile boolean isProcessingTestEvent = false;
    
    private E startEvent, finishEvent, terminateEvent;
    
    protected final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    
    protected final Lock writeLock = rwLock.writeLock();
    
    protected final Lock readLock = rwLock.readLock();
    
    private MvelScriptManager scriptManager;
    
    // state machine options
    private boolean isAutoStartEnabled = true;
    
    private boolean isAutoTerminateEnabled = true;
    
    private boolean isDelegatorModeEnabled = false;

    private boolean isStartEventTriggerEntryActions = true;

    private boolean isTerminateEventTriggerExitActions = true;
    
    @SuppressWarnings("unused")
    private long transitionTimeout = -1;
    
    private boolean isDataIsolateEnabled = false;
    
    private boolean isDebugModeEnabled = false;
    
    private boolean isRemoteMonitorEnabled = false;
    
    private Class<?>[] extraParamTypes;
    
    private TransitionException lastException = null;
    
    void prePostConstruct(S initialStateId, Map<S, ? extends ImmutableState<T, S, E, C>> states,
            StateMachineConfiguration configuration, Runnable cb) {
        data = FSM.newStateMachineData(states);
        data.write().identifier(configuration.getIdProvider().get());       
 	    data.write().initialState(initialStateId);
        data.write().currentState(null);
        
        // retrieve options value from state machine configuration
        this.isAutoStartEnabled = configuration.isAutoStartEnabled();
        this.isAutoTerminateEnabled = configuration.isAutoTerminateEnabled();
        this.isDataIsolateEnabled = configuration.isDataIsolateEnabled();
        this.isDebugModeEnabled = configuration.isDebugModeEnabled();
        this.isDelegatorModeEnabled = configuration.isDelegatorModeEnabled();
        this.isStartEventTriggerEntryActions = configuration.isStartEventTriggerEntryActions();
        this.isTerminateEventTriggerExitActions = configuration.isTerminateEventTriggerExitActions();
        cb.run();
        
        prepare();
    }
    
    @Override
    public boolean isRemoteMonitorEnabled() {
        return isRemoteMonitorEnabled;
    }
    
    protected void prepare() {
        if(isDebugModeEnabled) {
            final StateMachineLogger logger = new StateMachineLogger(this);
            addStartListener(new StartListener<T, S, E, C>() {
                @Override
                public void started(StartEvent<T, S, E, C> event) {
                    logger.startLogging();
                }
            });
            addTerminateListener(new TerminateListener<T, S, E, C>() {
                @Override
                public void terminated(TerminateEvent<T, S, E, C> event) {
                    logger.stopLogging();
                }
            });
        }
    }
    
    private boolean processEvent(E event, C context, StateMachineData<T, S, E, C> originalData,
            ActionExecutionService<T, S, E, C> executionService, boolean isDataIsolateEnabled) {
        StateMachineData<T, S, E, C> localData = originalData;
        ImmutableState<T, S, E, C> fromState = localData.read().currentRawState();
        S fromStateId = fromState.getStateId(), toStateId = null;
        try {
            beforeTransitionBegin(fromStateId, event, context);
            fireEvent(new TransitionBeginEventImpl<T, S, E, C>(fromStateId, event, context, getThis()));
            
            if(isDataIsolateEnabled) {
                // use local data to isolation transition data write
                localData = FSM.newStateMachineData(originalData.read().originalStates());
                localData.dump(originalData.read());
            }
            
            TransitionResult<T, S, E, C> result = FSM.newResult(false, fromState, null);
            StateContext<T, S, E, C> stateContext = FSM.newStateContext(this, localData, 
                    fromState, event, context, result, executionService);
            fromState.internalFire(stateContext);
            toStateId = result.getTargetState().getStateId();
            
            if(result.isAccepted()) {
                executionService.execute();
                localData.write().lastState(fromStateId);
                localData.write().currentState(toStateId);
                if(isDataIsolateEnabled) { 
                    // import local data after transition accepted
                    originalData.dump(localData.read());
                }
                fireEvent(new TransitionCompleteEventImpl<T, S, E, C>(fromStateId, toStateId, 
                        event, context, getThis()));
                afterTransitionCompleted(fromStateId, getCurrentState(), event, context);
                return true;
            } else {
                fireEvent(new TransitionDeclinedEventImpl<T, S, E, C>(fromStateId, event, context, getThis()));
                afterTransitionDeclined(fromStateId, event, context);
            }
        } catch (Exception e) {
            // set state machine in error status first which means state machine cannot process event anymore 
            // unless this exception has been resolved and state machine status set back to normal again.
            setStatus(StateMachineStatus.ERROR);
            // wrap any exception into transition exception    
            lastException = (e instanceof TransitionException) ? (TransitionException) e :
                new TransitionException(e, ErrorCodes.FSM_TRANSITION_ERROR, 
                        new Object[]{fromStateId, toStateId, event, context, "UNKNOWN", e.getMessage()});
            fireEvent(new TransitionExceptionEventImpl<T, S, E, C>(lastException, fromStateId, 
                    localData.read().currentState(), event, context, getThis()));
            afterTransitionCausedException(fromStateId, toStateId, event, context);
        } finally {
            executionService.reset();
            fireEvent(new TransitionEndEventImpl<T, S, E, C>(fromStateId, toStateId, event, context, getThis()));
            afterTransitionEnd(fromStateId, getCurrentState(), event, context);
        }
        return false;
    }
    
    private void processEvents() {
        if (isIdle()) {
            writeLock.lock();
            setStatus(StateMachineStatus.BUSY);
            try {
                Pair<E, C> eventInfo;
                E event;
                C context = null;
                while ((eventInfo=queuedEvents.poll())!=null) {
                    // response to cancel operation
                    if(Thread.interrupted()) {
                        queuedEvents.clear();
                        break;
                    }
                    event = eventInfo.first();
                    context = eventInfo.second();
                    processEvent(event, context, data, executor, isDataIsolateEnabled);
                }
                ImmutableState<T, S, E, C> rawState = data.read().currentRawState();
                if(isAutoTerminateEnabled && rawState.isRootState() && rawState.isFinalState()) {
                    terminate(context);
                }
            } finally {
                if(getStatus()==StateMachineStatus.BUSY)
                    setStatus(StateMachineStatus.IDLE);
                writeLock.unlock();
            }
        }
    }
    
    private void internalFire(E event, C context, boolean insertAtFirst) {
        if(getStatus()==StateMachineStatus.INITIALIZED) {
            if(isAutoStartEnabled) {
                start(context);
            } else {
                throw new IllegalStateException("The state machine is not running.");
            }
        }
        if(getStatus()==StateMachineStatus.TERMINATED) {
            throw new IllegalStateException("The state machine is already terminated.");
        }
        if(getStatus()==StateMachineStatus.ERROR) {
            throw new IllegalStateException("The state machine is corruptted.");
        }
        if(insertAtFirst) {
            queuedEvents.addFirst(new Pair<E, C>(event, context));
        } else {
            queuedEvents.addLast(new Pair<E, C>(event, context));
        }
        processEvents();
    }
    
    private boolean isEntryPoint() {
        return StateMachineContext.currentInstance()==null;
    }
    
    private void fire(E event, C context, boolean insertAtFirst) {
        boolean isEntryPoint = isEntryPoint();
        if(isEntryPoint) {
            StateMachineContext.set(getThis());
        } else if(isDelegatorModeEnabled && StateMachineContext.currentInstance()!=this) {
            T currentInstance = StateMachineContext.currentInstance();
            currentInstance.fire(event, context);
            return;
        } 
        try {
            if(StateMachineContext.isTestEvent()) {
                internalTest(event, context);
            } else {
                internalFire(event, context, insertAtFirst);
            }
        } finally {
            if(isEntryPoint) {
                StateMachineContext.set(null);
            }
        }
    }
    
    @Override
    public void fire(E event, C context) {
        fire(event, context, false);
    }
    
    public void untypedFire(Object event, Object context) {
        fire(typeOfEvent().cast(event), typeOfContext().cast(context));
    }
    
    @Override
    public void fireImmediate(E event, C context) {
        fire(event, context, true);
    }
    
    @Override
    public void fire(E event) {
        fire(event, null);
    }
    
    @Override
    public void fireImmediate(E event) {
        fireImmediate(event, null);
    }
    
    /**
     * Clean all queued events
     */
    protected void cleanQueuedEvents() {
        queuedEvents.clear();
    }
    
    private ActionExecutionService<T, S, E, C> getDummyExecutor() {
        ActionExecutionService<T, S, E, C> dummyExecutor = SquirrelProvider.getInstance().newInstance(
                new TypeReference<ActionExecutionService<T, S, E, C>>(){});
        dummyExecutor.setDummyExecution(true);
        return dummyExecutor;
    }
    
    private S internalTest(E event, C context) {
        checkState(status!=StateMachineStatus.ERROR && status!=StateMachineStatus.TERMINATED,
                "Cannot test state machine under "+status+" status.");
        
        S testResult = null;
        queuedTestEvents.add(new Pair<E, C>(event, context));
        if(!isProcessingTestEvent) {
            isProcessingTestEvent = true;
            @SuppressWarnings("unchecked")
            StateMachineData<T, S, E, C> cloneData = (StateMachineData<T, S, E, C>)dumpSavedData();
            ActionExecutionService<T, S, E, C> dummyExecutor = getDummyExecutor();
            
            if(getStatus()==StateMachineStatus.INITIALIZED) {
                if(isAutoStartEnabled) {
                    internalStart(context, cloneData, dummyExecutor);
                } else {
                    throw new IllegalStateException("The state machine is not running.");
                }
            }
            try {
                Pair<E, C> eventInfo = null;
                while ((eventInfo=queuedTestEvents.poll())!=null) {
                    E testEvent = eventInfo.first();
                    C testContext = eventInfo.second();
                    processEvent(testEvent, testContext, cloneData, dummyExecutor, false);
                }
                testResult = resolveState(cloneData.read().currentState(), cloneData);
            } finally {
                isProcessingTestEvent = false;
            }
        }
        return testResult;
    }
    
    @Override
    public S test(E event, C context) {
        boolean isEntryPoint = isEntryPoint();
        if(isEntryPoint) {
            StateMachineContext.set(getThis(), true);
        }
        try {
            return internalTest(event, context);
        } finally {
            if(isEntryPoint) {
                StateMachineContext.set(null);
            }
        }
    }
    
    @Override
    public S test(E event) {
        return test(event, null);
    }
    
    @Override
    public boolean canAccept(E event) {
        ImmutableState<T, S, E, C> testRawState = getCurrentRawState();
        if(testRawState==null) {
            if(isAutoStartEnabled) {
                testRawState = getInitialRawState();
            } else {
                return false;
            }
        }
        return testRawState.getAcceptableEvents().contains(event);
    }
    
    protected boolean isIdle() {
        return getStatus()!=StateMachineStatus.BUSY;
    }
    
    protected void afterTransitionCausedException(S fromState, S toState, E event, C context) {
        if(getLastException().getTargetException()!=null)
            logger.error("Transition caused exception", getLastException().getTargetException());
        throw getLastException();
    }
    
    protected void beforeTransitionBegin(S fromState, E event, C context) {
    }
    
    protected void afterTransitionCompleted(S fromState, S toState, E event, C context) {
    }
    
    protected void afterTransitionEnd(S fromState, S toState, E event, C context) {
    }
    
    protected void afterTransitionDeclined(S fromState, E event, C context) {
    }
    
    protected void beforeActionInvoked(S fromState, S toState, E event, C context) {
    }
    
    protected void afterActionInvoked(S fromState, S toState, E event, C context) {
    }
    
    private ImmutableState<T, S, E, C> resolveRawState(ImmutableState<T, S, E, C> rawState) {
        ImmutableState<T, S, E, C> resolvedRawState = rawState;
        if(resolvedRawState instanceof ImmutableLinkedState) {
            @SuppressWarnings("unchecked")
            T linkedStateMachine = (T) ((ImmutableLinkedState<T, S, E, C>)rawState).
                getLinkedStateMachine(getThis());
            resolvedRawState = linkedStateMachine.getCurrentRawState();
        }
        return resolvedRawState;
    }
    
    @Override
    public ImmutableState<T, S, E, C> getCurrentRawState() {
        readLock.lock();
        try {
            ImmutableState<T, S, E, C> rawState = data.read().currentRawState();
            return resolveRawState(rawState);
        } finally {
            readLock.unlock();
        }
    }
    
    @Override
    public ImmutableState<T, S, E, C> getLastRawState() {
        readLock.lock();
        try {
            ImmutableState<T, S, E, C> lastRawState = data.read().lastRawState();
            return resolveRawState(lastRawState);
        } finally {
            readLock.unlock();
        }
    }
    
    @Override
    public ImmutableState<T, S, E, C> getInitialRawState() {
        return getRawStateFrom(getInitialState());
    }
    
    @Override
    public ImmutableState<T, S, E, C> getRawStateFrom(S stateId) {
        readLock.lock();
        try {
            return data.read().rawStateFrom(stateId);
        } finally {
            readLock.unlock();
        }
    }
    
    @Override
    public Collection<ImmutableState<T, S, E, C>> getAllRawStates() {
        readLock.lock();
        try {
            return data.read().rawStates();
        } finally {
            readLock.unlock();
        }
    }
    
    @Override
    public Collection<S> getAllStates() {
        readLock.lock();
        try {
            return data.read().states();
        } finally {
            readLock.unlock();
        }
    }
    
    private S resolveState(S state, StateMachineData<T, S, E, C> localData) {
        S resolvedState = state;
        ImmutableState<T, S, E, C> rawState = localData.read().rawStateFrom(resolvedState);
        if(rawState instanceof ImmutableLinkedState) {
            ImmutableLinkedState<T, S, E, C> linkedRawState = (ImmutableLinkedState<T, S, E, C>)rawState;
            resolvedState = linkedRawState.getLinkedStateMachine(getThis()).getCurrentState();
        }
        return resolvedState;
    }
    
    @Override
    public S getCurrentState() {
        readLock.lock();
        try {
            return resolveState(data.read().currentState(), data);
        } finally {
            readLock.unlock();
        }
    }
    
    @Override
    public S getLastState() {
        readLock.lock();
        try {
            return resolveState(data.read().lastState(), data);
        } finally {
            readLock.unlock();
        }
    }
    
    @Override
    public S getInitialState() {
        readLock.lock();
        try {
            return data.read().initialState();
        } finally {
            readLock.unlock();
        }
    }

    private void entryAll(ImmutableState<T, S, E, C> origin, StateContext<T, S, E, C> stateContext) {
        Stack<ImmutableState<T, S, E, C>> stack = new Stack<ImmutableState<T, S, E, C>>();

        ImmutableState<T, S, E, C> state = origin;
        while (state != null) {
            stack.push(state);
            state = state.getParentState();
        }
        while (stack.size() > 0) {
            state = stack.pop();
            state.entry(stateContext);
        }
    }
    
    @Override
    public synchronized void start(C context) {
        if(isStarted()) {
            return;
        }

        setStatus(StateMachineStatus.BUSY);
        internalStart(context, data, executor);
        setStatus(StateMachineStatus.IDLE);
        processEvents();
    }
    
    private void internalStart(C context, StateMachineData<T, S, E, C> localData,
            ActionExecutionService<T, S, E, C> executionService) {
        ImmutableState<T, S, E, C> initialRawState = localData.read().initialRawState();
        StateContext<T, S, E, C> stateContext = FSM.newStateContext(
                this, localData, initialRawState, getStartEvent(), context, null, executionService);
        
        entryAll(initialRawState, stateContext);
        ImmutableState<T, S, E, C> historyState = initialRawState.enterByHistory(stateContext);
        if (isStartEventTriggerEntryActions) {
            executionService.execute();
        }
        localData.write().currentState(historyState.getStateId());
        localData.write().startContext(context);
        fireEvent(new StartEventImpl<T, S, E, C>(getThis()));
    }
    
    @Override
    public void start() {
        start(null);
    }
    
    @Override
    public boolean isStarted() {
        return getStatus()==StateMachineStatus.IDLE || getStatus()==StateMachineStatus.BUSY;
    }
    
    @Override
    public boolean isTerminated() {
        return getStatus()==StateMachineStatus.TERMINATED;
    }
    
    @Override
    public boolean isError() {
        return getStatus()==StateMachineStatus.ERROR;
    }
    
    @Override
    public StateMachineStatus getStatus() {
        return status;
    }
    
    protected void setStatus(StateMachineStatus status) {
        this.status = status;
    }
    
    @Override
    public S getLastActiveChildStateOf(S parentStateId) {
        readLock.lock();
        try {
            return data.read().lastActiveChildStateOf(parentStateId);
        } finally {
            readLock.unlock();
        }
    }
    
    @Override
    public List<S> getSubStatesOn(S parentStateId) {
        readLock.lock();
        try {
            return data.read().subStatesOn(parentStateId);
        } finally {
            readLock.unlock();
        }
    }
    
    @Override
    public synchronized void terminate(C context) {
        if(isTerminated()) {
            return;
        }
        
        StateContext<T, S, E, C> stateContext = FSM.newStateContext(
                this, data, data.read().currentRawState(), getTerminateEvent(), 
                context, null, executor);
        exitAll(data.read().currentRawState(), stateContext);
        if (isTerminateEventTriggerExitActions) {
            executor.execute();
        }

        setStatus(StateMachineStatus.TERMINATED);
        fireEvent(new TerminateEventImpl<T, S, E, C>(getThis()));
    }
    
    @Override
    public void terminate() {
        terminate(null);
    }
    
    private void exitAll(ImmutableState<T, S, E, C> current, StateContext<T, S, E, C> stateContext) {
        ImmutableState<T, S, E, C> state = current;
        while (state != null) {
            state.exit(stateContext);
            state = state.getParentState();
        }
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public T getThis() {
        return (T)this;
    }
    
    @Override
    public void accept(Visitor visitor) {
        visitor.visitOnEntry(this);
        for(ImmutableState<T, S, E, C> state : getAllRawStates()) {
            if(state.getParentState()==null)
                state.accept(visitor);
        }
        visitor.visitOnExit(this);
    }
    
    void setTypeOfStateMachine(Class<? extends T> stateMachineType) {
        data.write().typeOfStateMachine(stateMachineType);
    }
    
    void setTypeOfState(Class<S> stateType) {
        data.write().typeOfState(stateType);
    }
    
    void setTypeOfEvent(Class<E> eventType) {
        data.write().typeOfEvent(eventType);
    }
    
    void setTypeOfContext(Class<C> contextType) {
        data.write().typeOfContext(contextType);
    }
    
    void setScriptManager(MvelScriptManager scriptManager) {
        checkState(this.scriptManager==null);
        this.scriptManager = scriptManager;
    }
    
    void setStartEvent(E startEvent) {
        checkState(this.startEvent==null);
        this.startEvent=startEvent;
    }
    
    E getStartEvent() {
        return startEvent;
    }
    
    void setTerminateEvent(E terminateEvent) {
        checkState(this.terminateEvent==null);
        this.terminateEvent=terminateEvent;
    }
    
    E getTerminateEvent() {
        return terminateEvent;
    }
    
    void setFinishEvent(E finishEvent) {
        checkState(this.finishEvent==null);
        this.finishEvent=finishEvent;
    }
    
    E getFinishEvent() {
        return finishEvent;
    }
    
    void setExtraParamTypes(Class<?>[] extraParamTypes) {
        checkState(this.extraParamTypes==null);
        this.extraParamTypes = extraParamTypes;
    }
    
    @Override
    public StateMachineData.Reader<T, S, E, C> dumpSavedData() {
        readLock.lock();
        try {
            StateMachineData<T, S, E, C> savedData = 
                    FSM.newStateMachineData(data.read().originalStates());
            savedData.dump(data.read());
            
            // process linked state if any
            saveLinkedStateData(data.read(), savedData.write());
            return savedData.read();
        } finally {
            readLock.unlock();
        }
    }
    
    private void saveLinkedStateData(StateMachineData.Reader<T, S, E, C> src, StateMachineData.Writer<T, S, E, C> target) {
        dumpLinkedStateFor(src.currentRawState(), target);
//        dumpLinkedStateFor(src.lastRawState(), target);
        // TODO-hhe: dump linked state info for last active child state
        // TODO-hhe: dump linked state info for parallel state
    }
    
    private void dumpLinkedStateFor(ImmutableState<T, S, E, C> rawState, StateMachineData.Writer<T, S, E, C> target) {
        if(rawState!=null && rawState instanceof ImmutableLinkedState) {
            ImmutableLinkedState<T, S, E, C> linkedRawState = (ImmutableLinkedState<T, S, E, C>)rawState;
            StateMachineData.Reader<? extends StateMachine<?, S, E, C>, S, E, C> linkStateData = 
                    linkedRawState.getLinkedStateMachine(getThis()).dumpSavedData();
            target.linkedStateDataOn(rawState.getStateId(), linkStateData);
        }
    }
    
    @SuppressWarnings({"unchecked", "rawtypes"})
    @Override
    public boolean loadSavedData(StateMachineData.Reader<T, S, E, C> savedData) {
        Preconditions.checkNotNull(savedData, "Saved data cannot be null");
        if(writeLock.tryLock()) {
            try {
                data.dump(savedData);
                // process linked state if any
                for(S linkedState : savedData.linkedStates()) {
                    StateMachineData.Reader linkedStateData = savedData.linkedStateDataOf(linkedState);
                    ImmutableState<T, S, E, C> rawState = data.read().rawStateFrom(linkedState);
                    if(linkedStateData!=null && rawState instanceof ImmutableLinkedState) {
                        ImmutableLinkedState<T, S, E, C> linkedRawState = (ImmutableLinkedState<T, S, E, C>)rawState;
                        linkedRawState.getLinkedStateMachine(getThis()).loadSavedData(linkedStateData);
                    }
                }
                setStatus(StateMachineStatus.IDLE);
                return true;
            } finally {
                writeLock.unlock();
            }
        }
        return false;
    }
    
    @Override
    public boolean isContextSensitive() {
        return true;
    }
    
    @Override
    public Class<C> typeOfContext() {
        return data.read().typeOfContext();
    }
    
    @Override
    public Class<E> typeOfEvent() {
        return data.read().typeOfEvent();
    }
    
    @Override
    public Class<S> typeOfState() {
        return data.read().typeOfState();
    }
    
    @Override
    public TransitionException getLastException() {
        return lastException;
    }
    
    protected void setLastException(TransitionException lastException) {
        this.lastException = lastException;
    }
    
    private interface DeclarativeListener {
        Object getListenTarget();
    }

    private Object newListenerMethodProxy(final Object listenTarget,
            final Method listenerMethod, final Class<?> listenerInterface, final String condition) {
        final String listenerMethodName = ReflectUtils.getStatic(
                ReflectUtils.getField(listenerInterface, "METHOD_NAME")).toString();
        
        AsyncExecute asyncAnnotation = ReflectUtils.getAnnotation(listenTarget.getClass(), AsyncExecute.class);
        if(asyncAnnotation==null) {
            asyncAnnotation = listenerMethod.getAnnotation(AsyncExecute.class);
        }
        final boolean isAsync = asyncAnnotation!=null;
        final long timeout = asyncAnnotation!=null ? asyncAnnotation.timeout() : -1;
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if(method.getName().equals("getListenTarget")) {
                    return listenTarget;
                } else if(method.getName().equals(listenerMethodName)) {
                    if(args[0] instanceof TransitionEvent) {
                        @SuppressWarnings("unchecked")
                        TransitionEvent<T, S, E, C> event = (TransitionEvent<T, S, E, C>)args[0];
                        return invokeTransitionListenerMethod(listenTarget, listenerMethod, condition, event);
                    } else if(args[0] instanceof ActionEvent) {
                        @SuppressWarnings("unchecked")
                        ActionEvent<T, S, E, C> event = (ActionEvent<T, S, E, C>)args[0];
                        return invokeActionListenerMethod(listenTarget, listenerMethod, condition, event);
                    } else if(args[0] instanceof StartEvent || args[0] instanceof TerminateEvent) {
                        @SuppressWarnings("unchecked")
                        StateMachineEvent<T, S, E, C> event = (StateMachineEvent<T, S, E, C>)args[0];
                        return invokeStateMachineListenerMethod(listenTarget, listenerMethod, condition, event);
                    } else {
                        throw new IllegalArgumentException("Unable to recognize argument type "+args[0].getClass().getName()+".");
                    }
                } else if(method.getName().equals("equals")) {
                    return super.equals(args[0]);
                } else if(method.getName().equals("hashCode")) {
                    return super.hashCode();
                } else if(method.getName().equals("toString")) {
                    return super.toString();
                } else if(isAsync && method.getName().equals("timeout")) {
                    return timeout;
                }
                throw new UnsupportedOperationException("Cannot invoke method "+method.getName()+".");
            }
        };
        
        Class<?>[] implementedInterfaces = isAsync ? 
                new Class<?>[]{listenerInterface, DeclarativeListener.class, AsyncEventListener.class} :
                new Class<?>[]{listenerInterface, DeclarativeListener.class};
        Object proxyListener = Proxy.newProxyInstance(StateMachine.class.getClassLoader(), 
                implementedInterfaces, invocationHandler);
        return proxyListener;
    }
    
    private Object invokeStateMachineListenerMethod(final Object listenTarget, 
            final Method listenerMethod, final String condition, 
            final StateMachineEvent<T, S, E, C> event) {
        Class<?>[] parameterTypes = listenerMethod.getParameterTypes();
        final Map<String, Object> variables = Maps.newHashMap();
        variables.put(MvelScriptManager.VAR_STATE_MACHINE, event.getStateMachine());
        
        boolean isSatisfied = true;
        if(condition!=null && condition.length()>0) {
            isSatisfied = scriptManager.evalBoolean(condition, variables);
        }
        if(!isSatisfied) return null;
        
        if(parameterTypes.length == 0) {
            return ReflectUtils.invoke(listenerMethod, listenTarget);
        }
        // parameter values infer
        List<Object> parameterValues = Lists.newArrayList();
        for(Class<?> parameterType : parameterTypes) {
            if(parameterType.isAssignableFrom(AbstractStateMachine.this.getClass())) {
                parameterValues.add(event.getStateMachine());
            } else {
                parameterValues.add(null);
            }
        }
        return ReflectUtils.invoke(listenerMethod, listenTarget, parameterValues.toArray());
    }
    
    private Object invokeActionListenerMethod(final Object listenTarget, 
            final Method listenerMethod, final String condition, 
            final ActionEvent<T, S, E, C> event) {
        Class<?>[] parameterTypes = listenerMethod.getParameterTypes();
        
        final Map<String, Object> variables = Maps.newHashMap();
        variables.put(MvelScriptManager.VAR_FROM, event.getFrom());
        variables.put(MvelScriptManager.VAR_TO, event.getTo());
        variables.put(MvelScriptManager.VAR_EVENT, event.getEvent());
        variables.put(MvelScriptManager.VAR_CONTEXT, event.getContext());
        variables.put(MvelScriptManager.VAR_STATE_MACHINE, event.getStateMachine());
        if(event instanceof ExecActionExceptionEvent) {
            Exception e = ((ExecActionExceptionEvent<T, S, E, C>)event).getException();
            variables.put(MvelScriptManager.VAR_EXCEPTION, e);
        }
        
        boolean isSatisfied = true;
        if(condition!=null && condition.length()>0) {
            isSatisfied = scriptManager.evalBoolean(condition, variables);
        }
        if(!isSatisfied) return null;
        
        if(parameterTypes.length == 0) {
            return ReflectUtils.invoke(listenerMethod, listenTarget);
        }
        // parameter values infer
        List<Object> parameterValues = Lists.newArrayList();
        boolean isSourceStateSet = false, isTargetStateSet=false, isEventSet=false, isContextSet=false;
        for(Class<?> parameterType : parameterTypes) {
            if(!isSourceStateSet && parameterType.isAssignableFrom(typeOfState())) {
                parameterValues.add(event.getFrom());
                isSourceStateSet = true;
            } else if(!isTargetStateSet && parameterType.isAssignableFrom(typeOfState())) {
                parameterValues.add(event.getTo());
                isTargetStateSet = true;
            } else if(!isEventSet && parameterType.isAssignableFrom(typeOfEvent())) {
                parameterValues.add(event.getEvent());
                isEventSet = true;
            } else if(!isContextSet && parameterType.isAssignableFrom(typeOfContext())) {
                parameterValues.add(event.getContext());
                isContextSet = true;
            } else if(parameterType.isAssignableFrom(AbstractStateMachine.this.getClass())) {
                parameterValues.add(event.getStateMachine());
            } else if(parameterType.isAssignableFrom(Action.class)) {
                parameterValues.add(event.getExecutionTarget());
            } else if(parameterType==int[].class) {
                parameterValues.add(event.getMOfN());
            } else if(event instanceof ExecActionExceptionEvent && parameterType.isAssignableFrom(TransitionException.class)) {
                parameterValues.add(((ExecActionExceptionEvent<T, S, E, C>)event).getException());
            } else {
                parameterValues.add(null);
            }
        }
        return ReflectUtils.invoke(listenerMethod, listenTarget, parameterValues.toArray());
    }
    
    private Object invokeTransitionListenerMethod(final Object listenTarget, 
            final Method listenerMethod, final String condition, 
            final TransitionEvent<T, S, E, C> event) {
        Class<?>[] parameterTypes = listenerMethod.getParameterTypes();
        
        final Map<String, Object> variables = Maps.newHashMap();
        variables.put(MvelScriptManager.VAR_FROM, event.getSourceState());
        variables.put(MvelScriptManager.VAR_EVENT, event.getCause());
        variables.put(MvelScriptManager.VAR_CONTEXT, event.getContext());
        variables.put(MvelScriptManager.VAR_STATE_MACHINE, event.getStateMachine());
        if(event instanceof TransitionCompleteEvent) {
            variables.put(MvelScriptManager.VAR_TO, ((TransitionCompleteEvent<T, S, E, C>)event).getTargetState());
        } else if(event instanceof TransitionExceptionEvent) {
            variables.put(MvelScriptManager.VAR_TO, ((TransitionExceptionEvent<T, S, E, C>)event).getTargetState());
            variables.put(MvelScriptManager.VAR_EXCEPTION, ((TransitionExceptionEvent<T, S, E, C>)event).getException());
        }
        
        boolean isSatisfied = true;
        if(condition!=null && condition.length()>0) {
            isSatisfied = scriptManager.evalBoolean(condition, variables);
        }
        if(!isSatisfied) return null;
        
        if(parameterTypes.length == 0) {
            return ReflectUtils.invoke(listenerMethod, listenTarget);
        }
        // parameter values infer
        List<Object> parameterValues = Lists.newArrayList();
        boolean isSourceStateSet = false, isTargetStateSet=false, isEventSet=false, isContextSet=false;
        for(Class<?> parameterType : parameterTypes) {
            if(!isSourceStateSet && parameterType.isAssignableFrom(typeOfState())) {
                parameterValues.add(event.getSourceState());
                isSourceStateSet = true;
            } else if(!isTargetStateSet && event instanceof TransitionEndEvent && 
                    parameterType.isAssignableFrom(typeOfState())) {
                parameterValues.add(((TransitionEndEvent<T, S, E, C>)event).getTargetState());
                isTargetStateSet = true;
            } else if(!isTargetStateSet && event instanceof TransitionCompleteEvent && 
                    parameterType.isAssignableFrom(typeOfState())) {
                parameterValues.add(((TransitionCompleteEvent<T, S, E, C>)event).getTargetState());
                isTargetStateSet = true;
            } else if(!isTargetStateSet && event instanceof TransitionExceptionEvent && 
                    parameterType.isAssignableFrom(typeOfState())) {
                parameterValues.add(((TransitionExceptionEvent<T, S, E, C>)event).getTargetState());
                isTargetStateSet = true;
            } else if(!isEventSet && parameterType.isAssignableFrom(typeOfEvent())) {
                parameterValues.add(event.getCause());
                isEventSet = true;
            } else if(!isContextSet && parameterType.isAssignableFrom(typeOfContext())) {
                parameterValues.add(event.getContext());
                isContextSet = true;
            } else if(parameterType.isAssignableFrom(AbstractStateMachine.this.getClass())) {
                parameterValues.add(event.getStateMachine());
            } else if(event instanceof TransitionExceptionEvent && 
                    parameterType.isAssignableFrom(TransitionException.class)) {
                parameterValues.add(((TransitionExceptionEvent<T, S, E, C>)event).getException());
            } else {
                parameterValues.add(null);
            }
        }
        return ReflectUtils.invoke(listenerMethod, listenTarget, parameterValues.toArray());
    }
    
    private void registerDeclarativeListener(final Object listenerMethodProvider, Method listenerMethod, 
            Observable listenTarget, Class<? extends Annotation> annotationClass, Class<?> listenerClass, 
            Class<?> eventClass) {
        Annotation anno = listenerMethod.getAnnotation(annotationClass);
        if(anno!=null) {
            Method whenMethod = ReflectUtils.getMethod(anno.getClass(), "when", new Class[0]);
            String whenCondition = StringUtils.EMPTY;
            if(whenMethod!=null) {
                whenCondition = (String)ReflectUtils.invoke(whenMethod, anno);
            }
            Field methodField = ReflectUtils.getField(listenerClass, "METHOD");
            if(methodField!=null && Modifier.isStatic(methodField.getModifiers())) {
                Method method = (Method)ReflectUtils.getStatic(methodField);
                Object proxyListener = newListenerMethodProxy(listenerMethodProvider, 
                        listenerMethod, listenerClass, whenCondition);
                listenTarget.addListener(eventClass, proxyListener, method);
            } else {
                logger.info("Cannot find static field named 'METHOD' on listener class '"+listenerClass+"'.");
            }
        }
    }
    
    private static final Class<?>[][] stateMachineListenerMapping = {
        {OnTransitionBegin.class,       TransitionBeginListener.class,      TransitionBeginEvent.class},
        {OnTransitionComplete.class,    TransitionCompleteListener.class,   TransitionCompleteEvent.class},
        {OnTransitionDecline.class,     TransitionDeclinedListener.class,   TransitionDeclinedEvent.class},
        {OnTransitionEnd.class,         TransitionEndListener.class,        TransitionEndEvent.class},
        {OnTransitionException.class,   TransitionExceptionListener.class,  TransitionExceptionEvent.class},
        {OnStateMachineStart.class,     StartListener.class,                StartEvent.class},
        {OnStateMachineTerminate.class, TerminateListener.class,            TerminateEvent.class}
    };
    
    private static final Class<?>[][] actionExecutorListenerMapping = {
        {OnBeforeActionExecuted.class,  BeforeExecActionListener.class,     BeforeExecActionEvent.class},
        {OnAfterActionExecuted.class,   AfterExecActionListener.class,      AfterExecActionEvent.class},
        {OnActionExecException.class,   ExecActionExceptionListener.class,  ExecActionExceptionEvent.class},
    };
    
    @Override
    @SuppressWarnings("unchecked")
    public void addDeclarativeListener(final Object listenerMethodProvider) {
        // If no declarative listener was register, please make sure your listener was public method
        List<String> visitedMethods = Lists.newArrayList();
        Method[] methods = listenerMethodProvider.getClass().getMethods();
        Arrays.sort(methods, new Comparator<Method>() {
            @Override
            public int compare(Method o1, Method o2) {
                ListenerOrder or1 = o1.getAnnotation(ListenerOrder.class);
                ListenerOrder or2 = o2.getAnnotation(ListenerOrder.class);
                // nulls last
                if (or1 != null && or2 != null) {
                    return or1.value() - or2.value();
                } else if (or1 != null && or2 == null) {
                    return -1;
                } else if (or1 == null && or2 != null) {
                    return 1;
                }
                return o1.getName().compareTo(o2.getName());
            }
        });
        
        for(final Method listenerMethod : methods) {
            if("--wait-notify-notifyAll-toString-equals-hashCode-getClass--".
                    indexOf("-"+listenerMethod.getName()+"-")>0) continue;
            String methodSignature = listenerMethod.toString();
            if(visitedMethods.contains(methodSignature)) continue;
            visitedMethods.add(methodSignature);
            for(int i=0; i<stateMachineListenerMapping.length; ++i) {
                registerDeclarativeListener(listenerMethodProvider, listenerMethod, this, 
                        (Class<? extends Annotation>)stateMachineListenerMapping[i][0], 
                        stateMachineListenerMapping[i][1], stateMachineListenerMapping[i][2]);
            }
            
            for(int i=0; i<actionExecutorListenerMapping.length; ++i) {
                registerDeclarativeListener(listenerMethodProvider, listenerMethod, executor, 
                        (Class<? extends Annotation>)actionExecutorListenerMapping[i][0], 
                        actionExecutorListenerMapping[i][1], actionExecutorListenerMapping[i][2]);
            }
        }
    }
    
    @Override
    public void removeDeclarativeListener(final Object listenerMethodProvider) {
        removeDeclarativeListener(this, listenerMethodProvider);
        removeDeclarativeListener(executor, listenerMethodProvider);
    }
    
    /**
     * Internal use only
     * @return ActionExecutionService
     */
    public int getExecutorListenerSize() {
        return executor.getListenerSize();
    }
    
    @Override
    public String getIdentifier() {
        return data.read().identifier();
    }
    
    @Override
    public String getDescription() {
        StringBuilder builder = new StringBuilder();
        builder.append("id=\"").append(getIdentifier()).append("\" ");
        builder.append("fsm-type=\"").append(getClass().getName()).append("\" ");
        builder.append("state-type=\"").append(typeOfState().getName()).append("\" ");
        builder.append("event-type=\"").append(typeOfEvent().getName()).append("\" ");
        builder.append("context-type=\"").append(typeOfContext().getName()).append("\" ");
        
        Converter<E> eventConverter = ConverterProvider.INSTANCE.getConverter(typeOfEvent());
        if(getStartEvent()!=null) {
            builder.append("start-event=\"");
            builder.append(eventConverter.convertToString(getStartEvent()));
            builder.append("\" ");
        }
        if(getTerminateEvent()!=null) {
            builder.append("terminate-event=\"");
            builder.append(eventConverter.convertToString(getTerminateEvent()));
            builder.append("\" ");
        }
        if(getFinishEvent()!=null) {
            builder.append("finish-event=\"");
            builder.append(eventConverter.convertToString(getFinishEvent()));
            builder.append("\" ");
        }
        builder.append("context-insensitive=\"").append(isContextSensitive()).append("\" ");
        
        if(extraParamTypes!=null && extraParamTypes.length>0) {
            builder.append("extra-parameters=\"[");
            for(int i=0; i<extraParamTypes.length; ++i) {
                if(i>0) builder.append(",");
                builder.append(extraParamTypes[i].getName());
            }
            builder.append("]\" ");
        }
        return builder.toString();
    }
    
    @Override
    public String exportXMLDefinition(boolean beautifyXml) {
        SCXMLVisitor visitor = SquirrelProvider.getInstance().newInstance(SCXMLVisitor.class);
        accept(visitor);
        return visitor.getScxml(beautifyXml);
    }
    
    private void removeDeclarativeListener(Observable observable, final Object listenTarget) {
        observable.removeListener(new Predicate<ListenerMethod>() {
            @Override
            public boolean apply(ListenerMethod input) {
                return (input.getTarget() instanceof DeclarativeListener) &&
                      ((DeclarativeListener)input.getTarget()).getListenTarget()==listenTarget;
            }
        });
    }
    
    @Override
    public void addStateMachineListener(StateMachineListener<T, S, E, C> listener) {
        addListener(StateMachineEvent.class, listener, StateMachineListener.METHOD);
    }
    
    @Override
    public void removeStateMachineListener(StateMachineListener<T, S, E, C> listener) {
        removeListener(StateMachineEvent.class, listener, StateMachineListener.METHOD);
    }
    
    @Override
    public void addStartListener(StartListener<T, S, E, C> listener) {
        addListener(StartEvent.class, listener, StartListener.METHOD);
    }
    
    @Override
    public void removeStartListener(StartListener<T, S, E, C> listener) {
        removeListener(StartEvent.class, listener, StartListener.METHOD);
    }
    
    @Override
    public void addTerminateListener(TerminateListener<T, S, E, C> listener) {
        addListener(TerminateEvent.class, listener, TerminateListener.METHOD);
    }
    
    @Override
    public void removeTerminateListener(TerminateListener<T, S, E, C> listener) {
        removeListener(TerminateEvent.class, listener, TerminateListener.METHOD);
    }
    
    @Override
    public void addStateMachineExceptionListener(StateMachineExceptionListener<T, S, E, C> listener) {
        addListener(StateMachineExceptionEvent.class, listener, StateMachineExceptionListener.METHOD);
    }
    
    @Override
    public void removeStateMachineExceptionListener(StateMachineExceptionListener<T, S, E, C> listener) {
        removeListener(StateMachineExceptionEvent.class, listener, StateMachineExceptionListener.METHOD);
    }
    
    @Override
    public void addTransitionBeginListener(TransitionBeginListener<T, S, E, C> listener) {
        addListener(TransitionBeginEvent.class, listener, TransitionBeginListener.METHOD);
    }
    
    @Override
    public void removeTransitionBeginListener(TransitionBeginListener<T, S, E, C> listener) {
        removeListener(TransitionBeginEvent.class, listener, TransitionBeginListener.METHOD);
    }
    
    @Override
    public void addTransitionCompleteListener(TransitionCompleteListener<T, S, E, C> listener) {
        addListener(TransitionCompleteEvent.class, listener, TransitionCompleteListener.METHOD);
    }
    
    @Override
    public void removeTransitionCompleteListener(TransitionCompleteListener<T, S, E, C> listener) {
        removeListener(TransitionCompleteEvent.class, listener, TransitionCompleteListener.METHOD);
    }
    
    @Override
    public void addTransitionExceptionListener(TransitionExceptionListener<T, S, E, C> listener) {
        addListener(TransitionExceptionEvent.class, listener, TransitionExceptionListener.METHOD);
    }
    
    @Override
    public void removeTransitionExceptionListener(TransitionExceptionListener<T, S, E, C> listener) {
        removeListener(TransitionExceptionEvent.class, listener, TransitionExceptionListener.METHOD);
    }
    
    @Override
    public void addTransitionDeclinedListener(TransitionDeclinedListener<T, S, E, C> listener) {
        addListener(TransitionDeclinedEvent.class, listener, TransitionDeclinedListener.METHOD);
    }

    @Override
    public void removeTransitionDeclinedListener(TransitionDeclinedListener<T, S, E, C> listener) {
        removeListener(TransitionDeclinedEvent.class, listener, TransitionDeclinedListener.METHOD);
    }
    
    @Override
    @Deprecated
    public void removeTransitionDecleindListener(TransitionDeclinedListener<T, S, E, C> listener) {
        removeListener(TransitionDeclinedEvent.class, listener, TransitionDeclinedListener.METHOD);
    }
    
    @Override
    public void addTransitionEndListener(TransitionEndListener<T, S, E, C> listener) {
        addListener(TransitionEndEvent.class, listener, TransitionEndListener.METHOD);
    }
    
    @Override
    public void removeTransitionEndListener(TransitionEndListener<T, S, E, C> listener) {
        removeListener(TransitionEndEvent.class, listener, TransitionEndListener.METHOD);
    }
    
    @Override
    public void addExecActionListener(BeforeExecActionListener<T, S, E, C> listener) {
        executor.addExecActionListener(listener);
    }

    @Override
    public void removeExecActionListener(BeforeExecActionListener<T, S, E, C> listener) {
        executor.removeExecActionListener(listener);
    }
    
    public static abstract class AbstractStateMachineEvent<T extends StateMachine<T, S, E, C>, S, E, C> 
            implements StateMachine.StateMachineEvent<T, S, E, C> {
        private final T stateMachine;
        public AbstractStateMachineEvent(T stateMachine) {
            this.stateMachine = stateMachine;
        }
        
        @Override
        public T getStateMachine() {
            return stateMachine;
        }
    }
    
    public static class StartEventImpl<T extends StateMachine<T, S, E, C>, S, E, C> 
            extends AbstractStateMachineEvent<T, S, E, C> 
            implements StateMachine.StartEvent<T, S, E, C> {
        public StartEventImpl(T source) {
            super(source);
        }
    }
    
    public static class TerminateEventImpl<T extends StateMachine<T, S, E, C>, S, E, C> 
            extends AbstractStateMachineEvent<T, S, E, C> 
            implements StateMachine.TerminateEvent<T, S, E, C> {
        public TerminateEventImpl(T source) {
            super(source);
        }
    }
    
    public static class StateMachineExceptionEventImpl<T extends StateMachine<T, S, E, C>, S, E, C> 
            extends AbstractStateMachineEvent<T, S, E, C> 
            implements StateMachine.StateMachineExceptionEvent<T, S, E, C> {
        private final Exception e;
        public StateMachineExceptionEventImpl(Exception e, T source) {
            super(source);
            this.e = e;
        }
        
        @Override
        public Exception getException() {
            return e;
        }
    }
    
    public static abstract class AbstractTransitionEvent<T extends StateMachine<T, S, E, C>, S, E, C> 
            extends AbstractStateMachineEvent<T, S, E, C> 
            implements StateMachine.TransitionEvent<T, S, E, C> {
        private final S sourceState;
        private final E event;
        private final C context;
        public AbstractTransitionEvent(S sourceState, E event, C context, T stateMachine) {
            super(stateMachine);
            this.sourceState = sourceState;
            this.event = event;
            this.context = context;
        }
        
        @Override
        public S getSourceState() {
            return sourceState;
        }
        
        @Override
        public E getCause() {
            return event;
        }
        
        @Override
        public C getContext() {
            return context;
        }
    }
    
    public static class TransitionBeginEventImpl<T extends StateMachine<T, S, E, C>, S, E, C> 
            extends AbstractTransitionEvent<T, S, E, C> 
            implements StateMachine.TransitionBeginEvent<T, S, E, C> {
        public TransitionBeginEventImpl(S sourceState, E event, C context,T stateMachine) {
            super(sourceState, event, context, stateMachine);
        }
    }
    
    public static class TransitionCompleteEventImpl<T extends StateMachine<T, S, E, C>, S, E, C> 
            extends AbstractTransitionEvent<T, S, E, C> 
            implements StateMachine.TransitionCompleteEvent<T, S, E, C> {
        private final S targetState;
        public TransitionCompleteEventImpl(S sourceState, S targetState, E event, C context,T stateMachine) {
            super(sourceState, event, context, stateMachine);
            this.targetState = targetState;
        }
        
        @Override
        public S getTargetState() {
            return targetState;
        }
    }
    
    public static class TransitionExceptionEventImpl<T extends StateMachine<T, S, E, C>, S, E, C> 
            extends AbstractTransitionEvent<T, S, E, C> 
            implements StateMachine.TransitionExceptionEvent<T, S, E, C> {
        private final S targetState;
        private final TransitionException e;
        public TransitionExceptionEventImpl(TransitionException e, 
                S sourceState, S targetState, E event, C context,T stateMachine) {
            super(sourceState, event, context, stateMachine);
            this.targetState = targetState;
            this.e = e;
        }
        
        @Override
        public S getTargetState() {
            return targetState;
        }
        
        @Override
        public TransitionException getException() {
            return e;
        }
    }
    
    public static class TransitionDeclinedEventImpl<T extends StateMachine<T, S, E, C>, S, E, C> 
            extends AbstractTransitionEvent<T, S, E, C> 
            implements StateMachine.TransitionDeclinedEvent<T, S, E, C> {
        public TransitionDeclinedEventImpl(S sourceState, E event, C context,T stateMachine) {
            super(sourceState, event, context, stateMachine);
        }
    }
    
    public static class TransitionEndEventImpl<T extends StateMachine<T, S, E, C>, S, E, C> 
            extends AbstractTransitionEvent<T, S, E, C> 
            implements StateMachine.TransitionEndEvent<T, S, E, C> {
        private final S targetState;
        public TransitionEndEventImpl(S sourceState, S targetState, E event, C context,T stateMachine) {
            super(sourceState, event, context, stateMachine);
            this.targetState = targetState;
        }
        @Override
        public S getTargetState() {
            return targetState;
        }
    }
}
