/*
 * Copyright 2004-2008 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.webflow.engine;

import java.util.Iterator;

import org.springframework.core.style.StylerUtils;
import org.springframework.core.style.ToStringCreator;
import org.springframework.webflow.definition.TransitionDefinition;
import org.springframework.webflow.definition.TransitionableStateDefinition;
import org.springframework.webflow.execution.RequestContext;

/**
 * Abstract superclass for states that can execute a transition in response to an event.
 * 
 * @see org.springframework.webflow.engine.Transition
 * @see org.springframework.webflow.engine.TransitionCriteria
 * 
 * @author Keith Donald
 * @author Erwin Vervaet
 */
public abstract class TransitionableState extends State implements TransitionableStateDefinition {

	/**
	 * The set of possible transitions out of this state.
	 */
	private TransitionSet transitions = new TransitionSet();

	/**
	 * An actions to execute when exiting this state.
	 */
	private ActionList exitActionList = new ActionList();

	/**
	 * Create a new transitionable state.
	 * @param flow the owning flow
	 * @param id the state identifier (must be unique to the flow)
	 * @throws IllegalArgumentException when this state cannot be added to given flow, for instance when the id is not
	 * unique
	 * @see State#State(Flow, String)
	 * @see #getTransitionSet()
	 */
	protected TransitionableState(Flow flow, String id) throws IllegalArgumentException {
		super(flow, id);
	}

	// implementing TranstionableStateDefinition

	public TransitionDefinition[] getTransitions() {
		return getTransitionSet().toArray();
	}

	public TransitionDefinition getTransition(String eventId) {
		for (Iterator it = transitions.iterator(); it.hasNext();) {
			Transition transition = (Transition) it.next();
			if (transition.getId().equals(eventId)) {
				return transition;
			}
		}
		return null;
	}

	// impl

	/**
	 * Returns the set of transitions. The returned set is mutable.
	 */
	public TransitionSet getTransitionSet() {
		return transitions;
	}

	/**
	 * Get a transition in this state for given flow execution request context. Throws and exception when there is no
	 * corresponding transition.
	 * @throws NoMatchingTransitionException when a matching transition cannot be found
	 */
	public Transition getRequiredTransition(RequestContext context) throws NoMatchingTransitionException {
		Transition transition = getTransitionSet().getTransition(context);
		if (transition == null) {
			throw new NoMatchingTransitionException(getFlow().getId(), getId(), context.getCurrentEvent(),
					"No transition found on occurence of event '" + context.getCurrentEvent() + "' in state '"
							+ getId() + "' of flow '" + getFlow().getId() + "' -- valid transitional criteria are "
							+ StylerUtils.style(getTransitionSet().getTransitionCriterias())
							+ " -- likely programmer error, check the set of TransitionCriteria for this state");
		}
		return transition;
	}

	/**
	 * Returns the list of actions executed by this state when it is exited. The returned list is mutable.
	 * @return the state exit action list
	 */
	public ActionList getExitActionList() {
		return exitActionList;
	}

	// behavioral methods

	/**
	 * Inform this state definition that an event was signaled in it. The signaled event is the last event available in
	 * given request context ({@link RequestContext#getCurrentEvent()}).
	 * @param context the flow execution control context
	 * @throws NoMatchingTransitionException when a matching transition cannot be found
	 */
	public boolean handleEvent(RequestControlContext context) throws NoMatchingTransitionException {
		return context.execute(getRequiredTransition(context));
	}

	/**
	 * Exit this state. This is typically called when a transition takes the flow out of this state into another state.
	 * By default just executes any registered exit actions.
	 * @param context the flow control context
	 */
	public void exit(RequestControlContext context) {
		exitActionList.execute(context);
	}

	protected void appendToString(ToStringCreator creator) {
		creator.append("transitions", transitions).append("exitActionList", exitActionList);
	}
}