/*
 * Copyright (C) 2013.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 or
 * version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 */

package uk.me.parabola.mkgmap.reader.osm;

import java.util.ArrayList;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import uk.me.parabola.imgfmt.app.Area;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.CoordNode;
import uk.me.parabola.imgfmt.app.net.AccessTagsAndBits;
import uk.me.parabola.imgfmt.app.net.GeneralRouteRestriction;
import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.general.MapCollector;


/**
 * Representation of an OSM turn restriction
 *
 * @author Mark Burton, GerdP
 */
public class RestrictionRelation extends Relation {
    private static final Logger log = Logger.getLogger(RestrictionRelation.class);

	private List<Long> fromWayIds = new ArrayList<>(2);
	private List<Long> toWayIds = new ArrayList<>(2);
	private List<Long> viaWayIds = new ArrayList<>(2);
	private List<Coord> viaPoints = new ArrayList<>(2);
	private Map<Long, List<Coord>> updatedViaWays = new HashMap<>();
	private Coord viaCoord;
	private String restriction;
	private byte exceptMask;
	private char dirIndicator;
	private String msgPrefix;
	private boolean valid;
	private boolean evalWasCalled;
    
	// These tags are not loaded by default but if they exist issue a warning
	private static final String[] unsupportedTags = { "day_on", "day_off", "hour_on", "hour_off" };
	
	private static final byte DEFAULT_EXCEPT_MASK = AccessTagsAndBits.FOOT  | AccessTagsAndBits.EMERGENCY;
	
	private static final List<String> supportedRestrictions = Arrays.asList(
		"no_right_turn", "no_left_turn", "no_u_turn", "no_straight_on", 
		"only_right_turn", "only_left_turn", "only_straight_on", 
		"no_entry", "no_exit"	     
	);
	
	/**
	 * Create an instance based on an existing relation.  We need to do
	 * this because the type of the relation is not known until after all
	 * its tags are read in.
	 * @param other The relation to base this one on.
	 */
	public RestrictionRelation(Relation other) {
		setId(other.getId());
		msgPrefix = "Turn restriction " + toBrowseURL();
		copyTags(other);
		for (Map.Entry<String, Element> pair : other.getElements()) {
			addElement(pair.getKey(), pair.getValue());
		}
	}

	/**
	 * The evaluation should happen after style processing.
	 * Normally this is called from the {@link RelationStyleHook}
	 * Performs also diverse plausibility checks.
	 * @param bbox tile boundary
	 */
	public void eval(Area bbox) {
		if (evalWasCalled) {
			log.error(msgPrefix, "internal error: eval() was already called");
			fromWayIds.clear();
			toWayIds.clear();
			viaWayIds.clear();
		}
		evalWasCalled = true;
		if (getTag("type") == null) {
			// style removed the tag
			log.info(msgPrefix, "type tag was removed, relation is ignored");
			valid = false;
			return;
		}

		List<Way> fromWays = new ArrayList<>();
		List<Way> toWays = new ArrayList<>();
		List<Way> viaWays = new ArrayList<>();
		final String browseURL = toBrowseURL();
		valid = true;
		// find out what kind of restriction we have and to which vehicles it applies
		exceptMask = DEFAULT_EXCEPT_MASK;
		String specificType = getTag("restriction");
		int countUnknown = 0;
		Map<String, String> vehicles = getTagsWithPrefix("restriction:", true);
		if (!vehicles.isEmpty()) {
			exceptMask = (byte) 0xff;
			for (Entry<String, String> entry : vehicles.entrySet()) {
				if (!setExceptMask(entry.getKey(), false))
					countUnknown++;
				if (specificType == null)
					specificType = entry.getValue();
				else if (!specificType.equals(entry.getValue())) {
					log.warn(msgPrefix, "is invalid, it specifies different kinds of turns");
					valid = false;
					break;
				}
			}
			if (valid && vehicles.size() == countUnknown) {
				log.warn(msgPrefix, "no supported vehicle in turn restriction");
				valid = false;
				return;
			}
		}
		if (specificType == null) {
			// style removed the tag
			log.info(msgPrefix, "no valid restriction tag found");
			valid = false;
			return;
		}
		restriction = specificType.trim();

		msgPrefix = "Turn restriction (" + restriction + ") " + browseURL;
		if (!supportedRestrictions.contains(restriction)) {
			log.warn(msgPrefix, "ignoring unsupported restriction type '" + restriction + "'");
			valid = false;
			return;
		}

		dirIndicator = '?';
		if (restriction.contains("left"))
			dirIndicator = 'l';
		else if (restriction.contains("right"))
			dirIndicator = 'r';
		else if (restriction.contains("straight"))
			dirIndicator = 's';
		else if (restriction.endsWith("u_turn"))
			dirIndicator = 'u';
		String type = getTag("type");
		if (type.startsWith("restriction:")) {
			exceptMask = (byte) 0xff;
			String vehicle = type.substring("restriction:".length());
			if (!setExceptMask(vehicle, false)) {
				log.warn(msgPrefix, "ignoring unsupported '" + vehicle + "' in turn restriction");
				valid = false;
				return;
			}
		}
		
		String except = getTag("except");
		if (except != null) {
			for (String vehicle : except.split("[,;]")) { // be nice
				vehicle = vehicle.trim();
				setExceptMask(vehicle, true);
			}
		}

		for (String unsupportedTag : unsupportedTags) {
			if (getTag(unsupportedTag) != null) {
				log.warn(msgPrefix, "ignoring unsupported '" + unsupportedTag + "' tag");
			}
		}
		
		// evaluate members
		for (Map.Entry<String, Element> pair : getElements()) {
			String role = pair.getKey();
			Element el = pair.getValue();

			Coord location = null;

			if (viaCoord != null)
				location = viaCoord;
			else if (!fromWays.isEmpty() && !fromWays.get(0).getPoints().isEmpty())
				location = fromWays.get(0).getFirstPoint();
			else if (!toWays.isEmpty() && !toWays.get(0).getPoints().isEmpty())
				location = toWays.get(0).getFirstPoint();
			else if (!viaWays.isEmpty() && !viaWays.get(0).getPoints().isEmpty())
				location = viaWays.get(0).getFirstPoint();

			if (location != null)
				msgPrefix = "Turn restriction (" + restriction + ") " + browseURL + " (at " + location.toOSMURL() + ")";

			if ("to".equals(role)) {
				if (!(el instanceof Way)) {
					log.warn(msgPrefix, "'to' member", el.toBrowseURL(), "is not a way but it should be");
				} else if (((Way) el).getPoints().isEmpty()) {
					log.warn(msgPrefix, "ignoring empty 'to' way", el.toBrowseURL());
				} else {
					toWays.add((Way) el);
				}
			} else if ("from".equals(role)) {
				if (!(el instanceof Way)) {
					log.warn(msgPrefix, "'from' member", el.toBrowseURL(), "is not a way but it should be");
				} else if (((Way) el).getPoints().isEmpty()) {
					log.warn(msgPrefix, "ignoring empty 'from' way", el.toBrowseURL());
				} else {
					fromWays.add((Way) el);
				}
			} else if ("via".equals(role)) {
				if (el instanceof Node) {
					if (viaCoord != null) {
						log.warn(msgPrefix, "has extra 'via' node", el.toBrowseURL());
						valid = false;
					} else {
						viaCoord = ((Node) el).getLocation();
					}
				} else if (el instanceof Way) {
					if (viaCoord != null) {
						log.warn(msgPrefix, "has extra 'via' way", el.toBrowseURL());
						valid = false;
					} else {
						viaWays.add((Way) el);
					}
				} else {
					log.warn(msgPrefix, "'via' member", el.toBrowseURL(), "is not a node or way");
				}
			} else if ("location_hint".equals(role)) {
				// relax - we don't care about this
			} else {
				log.warn(msgPrefix, "unknown member role '" + role + "'");
			}
		}
		
		if (!valid)
			return;

		if (!"no_entry".equals(restriction) && fromWays.size() > 1) {
			log.warn(msgPrefix, "multiple 'from' members are only accepted for no_entry restrictions");
			valid = false;
			return;
		}
		if (!"no_exit".equals(restriction) && toWays.size() > 1) {
			log.warn(msgPrefix, "multiple 'to' members are only accepted for no_exit restrictions");
			valid = false;
			return;
		}
		if (viaWays.isEmpty() && viaCoord == null && fromWays.size() == 1 && toWays.size() == 1) {
			Way fromWay = fromWays.get(0);
			Way toWay = toWays.get(0);
			List<Coord> fromPoints = fromWay.getPoints();
			List<Coord> toPoints = toWay.getPoints();
			int countSame = 0;
			for (Coord fp : fromPoints) {
				for (Coord tp : toPoints) {
					if (fp == tp) {
						countSame++;
						viaCoord = fp;
					}
				}
			}
			if (countSame > 1) {
				log.warn(msgPrefix, "lacks 'via' node and way and the 'from' (", fromWay.toBrowseURL(),
						") and 'to' (", toWay.toBrowseURL(), ") ways connect in more than one place");
				valid = false;
			} else if (viaCoord == null) {
				log.warn(msgPrefix, "lacks 'via' node and the 'from' (" + fromWay.toBrowseURL() + ") and 'to' ("
						+ toWay.toBrowseURL() + ") ways don't connect");
				valid = false;
			} else {
				if (fromPoints.get(0) != viaCoord && fromPoints.get(fromPoints.size() - 1) != viaCoord
						|| toPoints.get(0) != viaCoord && toPoints.get(toPoints.size() - 1) != viaCoord) {
					log.warn(msgPrefix, "lacks 'via' node and the 'from' (" + fromWay.toBrowseURL() + ") and 'to' ("
							+ toWay.toBrowseURL() + ") ways don't connect at an end point");
					valid = false;
				} else {
					log.warn(msgPrefix, "lacks 'via' node (guessing it should be at",
							viaCoord.toOSMURL() + ", why don't you add it to the OSM data?)");
				}
			}
		}

		if (fromWays.isEmpty()) {
			log.warn(msgPrefix, "lacks 'from' way");
			valid = false;
		}

		if (toWays.isEmpty()) {
			log.warn(msgPrefix, "lacks 'to' way");
			valid = false;
		}

		if ((fromWays.size() > 1 || toWays.size() > 1) && !viaWays.isEmpty()) {
			log.warn(msgPrefix, "'via' way(s) are not supported with multiple 'from' or 'to' ways");
			valid = false;
		}
		if (!valid)
			return;
		for (List<Way> ways : Arrays.asList(fromWays,viaWays,toWays)){
			for (Way way : ways){
				if (way.getPoints().size() < 2){
					log.warn(msgPrefix,"way",way.toBrowseURL(),"has less than 2 points, restriction is ignored");
					valid = false;
				} else {
					if (way.hasIdenticalEndPoints()){
						if (ways == toWays && dirIndicator != '?')
							continue; // we try to determine the correct part in RoadNetwork 
						log.warn(msgPrefix, "way", way.toBrowseURL(), "starts and ends at same node, don't know which one to use");
						valid = false;
					}
				}
			}
		}
		if (!valid)
			return;
		if (!viaPoints.isEmpty())
			viaCoord = viaPoints.get(0);
		
		if(viaCoord == null && viaWays.isEmpty()) {
			valid = false;
			return;
		}
		
		viaPoints.clear();
		Coord v1 = viaCoord;
		Coord v2 = viaCoord;
		if (!viaWays.isEmpty()) {
			v1 = viaWays.get(0).getFirstPoint();
			v2 = viaWays.get(0).getLastPoint();
		}
		// check if all from ways are connected at the given via point or with the given via ways
		for (Way fromWay : fromWays) {
			Coord e1 = fromWay.getFirstPoint();
			Coord e2 = fromWay.getLastPoint();
			if (e1 == v1 || e2 == v1)
				viaCoord = v1;
			else if (e1 == v2 || e2 == v2)
				viaCoord = v2;
			else {
				log.warn(msgPrefix, "'from' way", fromWay.toBrowseURL(), "doesn't start or end at 'via' node or way");
				valid = false;
			}
		}
		if (!valid)
			return;
		viaPoints.add(viaCoord);
		// check if via ways are connected in the given order
		for (int i = 0; i < viaWays.size(); i++) {
			Way way = viaWays.get(i);
			Coord v = viaPoints.get(viaPoints.size() - 1);
			if (way.getFirstPoint() == v)
				v2 = way.getLastPoint();
			else if (way.getLastPoint() == v)
				v2 = way.getFirstPoint();
			else {
				log.warn(msgPrefix, "'via' way", way.toBrowseURL(), "doesn't start or end at", v);
				valid = false;
			}
			viaPoints.add(v2);
		}
		
		// check if all via points are inside the bounding box
		int countInside = 0;
		for (Coord via : viaPoints) {
			if (bbox.contains(via))
				++countInside;
		}
		if (countInside == 0)
			valid = false;
		else if (countInside > 0 && countInside < viaPoints.size()) {
			log.warn(msgPrefix, "via way crosses tile boundary. Don't know how to save that, ignoring it");
			valid = false;
		}

		if (!valid)
			return;
		// check if all to ways are connected to via point or last via way
		Coord lastVia = viaPoints.get(viaPoints.size() - 1);
		for (Way toWay : toWays) {
			Coord e1 = toWay.getFirstPoint();
			Coord e2 = toWay.getLastPoint();
			if (e1 != lastVia && e2 != lastVia) {
				log.warn(msgPrefix, "'to' way", toWay.toBrowseURL(), "doesn't start or end at 'via' node or way");
				valid = false;
			}
		}
		if (valid && !viaWays.isEmpty() && restriction.startsWith("only")) {
			log.warn(msgPrefix, "check: 'via' way(s) are used in", restriction, "restriction");
		}
		if (valid) {
			// make sure that via way(s) don't appear in the from or to lists
			for (Way w : viaWays) {
				if (fromWays.contains(w)) {
					log.warn(msgPrefix, "'via' way", w.toBrowseURL(), "appears also as 'from' way");
					valid = false;
				}
				if (toWays.contains(w)) {
					log.warn(msgPrefix, "'via' way", w.toBrowseURL(), "appears also as 'to' way");
					valid = false;
				}
			}
		}
		if (valid) {
			for (Way w : fromWays)
				fromWayIds.add(w.getId());
			for (Way w : toWays)
				toWayIds.add(w.getId());
			for (Way w : viaWays) {
				w.setViaWay(true);
				viaWayIds.add(w.getId());
			}
			for (Coord v : viaPoints)
				v.setViaNodeOfRestriction(true);
		}
	}

	/** 
	 * Match the vehicle type in a restriction with the mkgmap type
	 * and modify the exceptMask 
	 * @param vehicle
	 * @param b true: restriction should not apply for vehicle, false: restriction should apply  
	 * @return true if vehicle has a matching flag in the garmin format
	 */
	private boolean setExceptMask(String vehicle, boolean b) {
		if (vehicle == null)
			return false;
		byte flag = 0;
		// inverted
		if ("vehicle".equals(vehicle))
			flag = (byte) ~(DEFAULT_EXCEPT_MASK);
		else if ("motor_vehicle".equals(vehicle))
			flag = (byte) ~(AccessTagsAndBits.BIKE | DEFAULT_EXCEPT_MASK);
		// normal
		else if ("psv".equals(vehicle))
			flag = (byte) (AccessTagsAndBits.TAXI | AccessTagsAndBits.BUS);
		else if ("bicycle".equals(vehicle))
			flag = AccessTagsAndBits.BIKE;
		else if ("motorcar".equals(vehicle))
			flag = AccessTagsAndBits.CAR;
		else if ("bus".equals(vehicle))
			flag = AccessTagsAndBits.BUS;
		else if ("taxi".equals(vehicle))
			flag = AccessTagsAndBits.TAXI;
		else if ("goods".equals(vehicle))
			flag = AccessTagsAndBits.DELIVERY;
		else if ("hgv".equals(vehicle) || "truck".equals(vehicle))
			flag = AccessTagsAndBits.TRUCK;
		else if ("emergency".equals(vehicle))
			flag = AccessTagsAndBits.EMERGENCY;
		else if ("foot".equals(vehicle))
			flag = AccessTagsAndBits.FOOT;
		if (flag == 0) {
			log.warn(msgPrefix, "ignoring unsupported vehicle class '" + vehicle + "' in turn restriction");
			return false;
		}

		if (b)
			exceptMask |= flag;
		else
			exceptMask &= ~flag;
		return true;
	}
	
	public boolean isFromWay(long wayId) {
		return fromWayIds.contains(wayId);
	}

	public boolean isToWay(long wayId) {
		return toWayIds.contains(wayId);
	}

	public void replaceViaCoord(Coord oldP, Coord newP) {
		for (int i = 0; i < viaPoints.size(); i++){
			if (viaPoints.get(i) == oldP){
				viaPoints.set(i, newP);
				if (log.isDebugEnabled()){
					log.debug(msgPrefix, restriction, "'via' coord redefined from",
							oldP.toOSMURL(), "to", newP.toOSMURL());
				}
				return;
			}
		}
	}
	
	public void addRestriction(MapCollector collector, IdentityHashMap<Coord, CoordNode> nodeIdMap) {
		if (!valid)
			return;
	    List<CoordNode> viaNodes = new ArrayList<>();
		for (Coord v: viaPoints){
			CoordNode vn = nodeIdMap.get(v);
			if (vn == null){
				log.warn(msgPrefix,"via node is not a routing node, restriction relation is ignored");
				return;
			}
			viaNodes.add(vn);
		}

		if (viaNodes.size() > 6){
			log.warn(msgPrefix,"has more than 6 via nodes, this is not supported");
			return;
		}
		if(restriction == null){
			log.error("internal error: can't add valid restriction relation", this.getId(), "type", restriction);
			return;
		}
		int addedRestrictions = 0;
		GeneralRouteRestriction grr;
		if(restriction.startsWith("no_")){
			for (long fromWayId : fromWayIds){
				for (long toWayId : toWayIds){
					grr = new GeneralRouteRestriction("not", exceptMask, msgPrefix);
					grr.setFromWayId(fromWayId);
					grr.setToWayId(toWayId);
					grr.setViaNodes(viaNodes);
					grr.setViaWayIds(viaWayIds);
					grr.setDirIndicator(dirIndicator);
					addedRestrictions += collector.addRestriction(grr);
				}
			}
			if (log.isInfoEnabled())
				log.info(msgPrefix, restriction, "translated to",addedRestrictions,"img file restrictions");
		}
		else if(restriction.startsWith("only_")){
			grr = new GeneralRouteRestriction("only", exceptMask, msgPrefix);
			grr.setFromWayId(fromWayIds.get(0));
			grr.setToWayId(toWayIds.get(0));
			grr.setViaNodes(viaNodes);
			grr.setViaWayIds(viaWayIds);
			grr.setDirIndicator(dirIndicator);
			int numAdded = collector.addRestriction(grr);
			if (numAdded > 0)
				log.info(msgPrefix, restriction, "added - allows routing to way", toWayIds.get(0));

		}
		else {
			log.error("mkgmap internal error: unknown restriction", restriction);
		}
	}

	/** Process the members in this relation.
	 */
	public void processElements() {
		// relax
	}

	@Override
	public String toString() {
		String s = "[restriction id = " + getId() + "(" + restriction + ")";
		if (!fromWayIds.isEmpty() && !toWayIds.isEmpty() && viaCoord != null )
			s += ", from = " + fromWayIds.get(0) + ", to = " + toWayIds.get(0) + ", via = " + viaCoord.toOSMURL() + "]";
		else 
			s += "]";
		return s;
	}
	
	/**
	 * @return true if restriction is usable
	 */
	public boolean isValid() {
		assert evalWasCalled;
		return valid;
	}

	public void setInvalid() {
		valid = false;
	}
	
	public List<Coord> getViaCoords() {
		assert evalWasCalled;
		return viaPoints;
	}

	/**
	 * 
	 * @return a Set with the OSM IDs of all ways used in the restriction 
	 */
	public Set<Long> getWayIds(){
		assert evalWasCalled;
		Set<Long> wayIds = new HashSet<>();
		wayIds.addAll(fromWayIds);
		wayIds.addAll(viaWayIds);
		wayIds.addAll(toWayIds);
		return wayIds;
	}
	
	public byte getExceptMask(){
		assert evalWasCalled;
		return exceptMask;
	}
	
	/**
	 * Replace 
	 * @param oldWayId
	 * @param newWayId
	 * @return
	 */
	public boolean replaceWay(long oldWayId, long newWayId) {
		assert evalWasCalled;
		boolean matched = false;
		for (List<Long> ways : Arrays.asList(fromWayIds, viaWayIds, toWayIds)) {
			for (int i = 0; i < ways.size(); i++) {
				if (ways.get(i) == oldWayId) {
					ways.set(i, newWayId);
					matched = true;
				}
			}
		}
		return matched;
	}

	/**
	 * Check if restriction is still valid if the way with the given id is not in the map.
	 * Can be true if the restriction is a no_exit or no_entry restriction.
	 * @param wayId the id of the way that was removed 
	 * @return true if the restriction is still valid without this way
	 */
	public boolean isValidWithoutWay(long wayId) {
		assert evalWasCalled;
		if (viaWayIds.contains(wayId))
			return false;
		fromWayIds.remove(wayId);
		toWayIds.remove(wayId);
		return (!fromWayIds.isEmpty() && !toWayIds.isEmpty());
	}

	/**
	 * A via way may be connected to other ways between the end points.
	 * We have to create a complete path for that. 
	 * @param way
	 * @param nodeIndices
	 */
	public void updateViaWay(Way way, List<Integer> nodeIndices) {
		if (!valid || !viaWayIds.contains(way.getId()))
			return;
		List<Coord> wayViaPoints = new ArrayList<>();
		for (int i : nodeIndices) {
			wayViaPoints.add(way.getPoints().get(i));
		}
		List<Coord> prevViaPoints = updatedViaWays.get(way.getId());
		if (prevViaPoints != null) {
			// we may get here when the style adds multiple routable ways for the
			// OSM way
			if (prevViaPoints.equals(wayViaPoints)) {
				// already up to date
				return;
			} else {
				log.error(msgPrefix, "internal error: via way is updated again with different nodes");
			}
		}
		Coord first = wayViaPoints.get(0);
		Coord last = wayViaPoints.get(wayViaPoints.size() - 1);
		int posFirst = -1;
		int posLast = -1;
		for (int i = 0; i < viaPoints.size(); i++) {
			if (first == viaPoints.get(i))
				posFirst = i;
			if (last == viaPoints.get(i))
				posLast = i;
			if (posFirst >= 0 && posLast >= 0) {
				if (Math.abs(posLast - posFirst) == 1) {
					break;
				} else {
//					check self intersection ?
				}
			}
		}
		if (posFirst < 0 || posLast < 0) {
			log.error(msgPrefix, "internal error: via way doesn't contain expected points");
			valid = false;
			return;
		}
		if (Math.abs(posLast - posFirst) != 1) {
			log.error(msgPrefix, "internal error: via way doesn't contain points in expected position");
			valid = false;
			return;
		}
		List<Coord> midPoints = new ArrayList<>(wayViaPoints.subList(1, wayViaPoints.size() - 1));
		if (posFirst < posLast) {
			if (posLast - posFirst > 1)
				viaPoints.subList(posFirst + 1, posLast).clear();
			viaPoints.addAll(posFirst + 1, midPoints);
		} else {
			if (posFirst - posLast > 1)
				viaPoints.subList(posLast + 1, posFirst).clear();
			Collections.reverse(midPoints);
			viaPoints.addAll(posLast + 1, midPoints);
		}
		
		int wayPos = viaWayIds.indexOf(way.getId());
		while (viaWayIds.size() > wayPos + 1 && viaWayIds.get(wayPos + 1) == way.getId())
			viaWayIds.remove(wayPos);
		for (int i = 0; i < midPoints.size(); i++) {
			viaWayIds.add(wayPos + 1, way.getId());
		}
		if (viaPoints.size() != viaWayIds.size() + 1) {
			log.error(msgPrefix, "internal error: number of via points and via ways no longer fits");
			valid = false;
		} else if (viaPoints.size() > 6) {
			log.warn(msgPrefix, "has more than 6 via nodes, this is not supported");
			valid = false;
		}
		updatedViaWays.put(way.getId(), wayViaPoints);
	}
}
