package aimax.osm.routing;

import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import aima.core.agent.Action;
import aima.core.search.framework.problem.ActionsFunction;
import aimax.osm.data.MapWayFilter;
import aimax.osm.data.entities.MapNode;
import aimax.osm.data.entities.MapWay;
import aimax.osm.data.entities.WayRef;

/**
 * Generates {@link aimax.osm.routing.OsmMoveAction}s for states which are
 * assumed to be of class {@link aimax.osm.data.entities.MapNode}. If a goal is
 * specified, all generated actions lead to road crossings, road ends, or the
 * specified goal. Otherwise, they lead to directly linked neighbor nodes.
 * @author Ruediger Lunde
 */
public class OsmActionsFunction implements ActionsFunction {

	public static enum OneWayMode {IGNORE, TRAVEL_FORWARD, TRAVEL_BACKWARDS}

	protected MapWayFilter filter;
	private OneWayMode oneWayMode;
	/**
	 * Goal node, possibly null. If a goal is specified, travel actions will
	 * include paths with size greater one.
	 */
	protected MapNode goal;

	public OsmActionsFunction(MapWayFilter filter, OneWayMode oneWayMode,
			MapNode goal) {
		this.filter = filter;
		this.oneWayMode = oneWayMode;
		this.goal = goal;
	}

	/** Expects a <code>MapNode</code> as argument. */
	@Override
	public Set<Action> actions(Object s) {
		Set<Action> result = new LinkedHashSet<Action>();
		MapNode from = (MapNode) s;
		for (WayRef wref : from.getWayRefs()) {
			if (filter == null || filter.isAccepted(wref.getWay())) {
				MapWay way = wref.getWay();
				int nodeIdx = wref.getNodeIdx();
				List<MapNode> wayNodes = way.getNodes();
				MapNode to;
				if (oneWayMode != OneWayMode.TRAVEL_BACKWARDS || !way.isOneway())
				for (int idx = nodeIdx + 1; idx < wayNodes.size(); idx++) {
					to = wayNodes.get(idx);
					if (goal == null || goal == to
							|| to.getWayRefs().size() > 1
							|| idx == wayNodes.size() - 1) {
						result.add(new OsmMoveAction(way, nodeIdx, idx));
						break;
					}
				}
				if (oneWayMode != OneWayMode.TRAVEL_FORWARD || !way.isOneway()) {
					for (int idx = nodeIdx - 1; idx >= 0; idx--) {
						to = wayNodes.get(idx);
						if (goal == null || goal == to
								|| to.getWayRefs().size() > 1 || idx == 0) {
							result.add(new OsmMoveAction(way, nodeIdx, idx));
							break;
						}
					}
				}
			}
		}
		return result;
	}
}
