/* *********************************************************************** *
 * project: org.matsim.*
 * *********************************************************************** *
 *                                                                         *
 * copyright       : (C) 2016 by the members listed in the COPYING,        *
 *                   LICENSE and WARRANTY file.                            *
 * email           : info at matsim dot org                                *
 *                                                                         *
 * *********************************************************************** *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *   See also COPYING, LICENSE and WARRANTY file                           *
 *                                                                         *
 * *********************************************************************** */

package org.matsim.pt2matsim.editor;

import com.opencsv.CSVReader;
import org.apache.log4j.Logger;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
import org.matsim.api.core.v01.network.NetworkFactory;
import org.matsim.api.core.v01.network.Node;
import org.matsim.core.population.routes.NetworkRoute;
import org.matsim.core.population.routes.RouteUtils;
import org.matsim.core.router.util.LeastCostPathCalculator;
import org.matsim.core.utils.collections.CollectionUtils;
import org.matsim.core.utils.collections.Tuple;
import org.matsim.pt.transitSchedule.api.*;
import org.matsim.pt2matsim.mapping.networkRouter.ScheduleRouters;
import org.matsim.pt2matsim.tools.NetworkTools;
import org.matsim.pt2matsim.tools.PTMapperTools;
import org.matsim.pt2matsim.tools.ScheduleTools;

import java.io.FileReader;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Implementation of a schedule editor. Provides methods for
 * rerouting and adapting schedules via a csv "command file".
 *
 * @author polettif
 */
public class BasicScheduleEditor implements ScheduleEditor {

	protected static Logger log = Logger.getLogger(RunScheduleEditor.class);
	// fields
	private final Network network;
	private final TransitSchedule schedule;
	private final TransitScheduleFactory scheduleFactory;
	private final NetworkFactory networkFactory;
	private final ScheduleRouters routers;
	private final ParentStops parentStops;

	public BasicScheduleEditor(TransitSchedule schedule, Network network, ScheduleRouters routers) {
		this.schedule = schedule;
		this.network = network;
		this.scheduleFactory = schedule.getFactory();
		this.networkFactory = network.getFactory();
		this.routers = routers;
		this.parentStops = new ParentStops();
	}


	public BasicScheduleEditor(TransitSchedule schedule, Network network) {
		this.schedule = schedule;
		this.network = network;
		this.scheduleFactory = schedule.getFactory();
		this.networkFactory = network.getFactory();
		this.parentStops = new ParentStops();

		log.info("Guessing routers based on schedule transport modes and used network transport modes.");
		this.routers = NetworkTools.guessRouters(schedule, network).createInstance();
	}

	public Network getNetwork() {
		return network;
	}

	public TransitSchedule getSchedule() {
		return schedule;
	}

	/**
	 * Parses a command file (csv) and runs the commands specified
	 */
	@Override
	public void parseCommandCsv(String filePath) throws IOException {
		CSVReader reader = new CSVReader(new FileReader(filePath), ';');

		String[] line = reader.readNext();
		while(line != null) {
			log.info(CollectionUtils.arrayToString(line));
			executeCmdLine(line);
			line = reader.readNext();
		}
		reader.close();
	}

	/**
	 * executes a command line
	 */
	@Override
	public void executeCmdLine(String[] cmd) {
		/*
		  Reroute TransitRoute via new Link
		  ["rerouteViaLink"] [TransitLineId] [TransitRouteId] [oldLinkId] [newLinkId]
		 */
		if(RR_VIA_LINK.equals(cmd[0])) {
			if(cmd.length == 5) {
				rerouteViaLink(getTransitLine(cmd[1]), getTransitRoute(cmd[1], cmd[2]), Id.createLinkId(cmd[3]), Id.createLinkId(cmd[4]));
			} else {
				throw new IllegalArgumentException("Incorrect number of arguments for " + cmd[0] + "! 5 needed, " + cmd.length + " given");
			}
		}

		/*
		  Reroute TransitRoute from a given stop facility
		  ["rerouteFromStop"] [TransitLineId] [TransitRouteId] [fromStopId] [newLinkId]
		 */
		else if(RR_FROM_STOP.equals(cmd[0])) {
			if(cmd.length == 5) {
				rerouteFromStop(getTransitLine(cmd[1]), getTransitRoute(cmd[1], cmd[2]), cmd[3], cmd[4]);
			} else {
				throw new IllegalArgumentException("Incorrect number of arguments for " + cmd[0] + "! 5 needed, " + cmd.length + " given");
			}
		}
		/*
		  Change the referenced link of a stopfacility. Effectively creates a new child stop facility.
		  ["changeRefLink"] [StopFacilityId] [newlinkId]
		  ["changeRefLink"] [TransitLineId] [TransitRouteId] [ParentId] [newlinkId]
		  ["changeRefLink"] ["allTransitRoutesOnLink"] [linkId] [ParentId] [newlinkId]
		 */
		else if(CHANGE_REF_LINK.equals(cmd[0])) {
			if(cmd.length == 3 || "".equals(cmd[3])) {
				changeRefLink(cmd[1], cmd[2]);
			} else if(cmd.length == 5) {
				switch (cmd[1]) {
					case ALL_TRANSIT_ROUTES_ON_LINK:
						Set<Tuple<TransitLine, TransitRoute>> tmpTransitRoutes = getTransitRoutesOnLink(Id.createLinkId(cmd[2]));
						for(Tuple<TransitLine, TransitRoute> tpl : tmpTransitRoutes) {
							changeRefLink(tpl.getFirst(), tpl.getSecond(), cmd[3], cmd[4]);
						}
						break;
					default:
						changeRefLink(getTransitLine(cmd[1]), getTransitRoute(cmd[1], cmd[2]), cmd[3], cmd[4]);
				}
			} else {
				throw new IllegalArgumentException("Incorrect number of arguments for " + cmd[0] + "! 3 or 5 needed, " + cmd.length + " given");
			}
		}

		/*
		  Adds a link to the network. Uses the attributes (freespeed, nr of lanes, transportModes)
		  of the attributeLink.
		  [addLink] [linkId] [fromNodeId] [toNodeId] [attributeLinkId]
		 */
		 else if(ADD_LINK.equals(cmd[0])) {
			if(cmd.length == 5) {
				addLink(cmd[1], cmd[2], cmd[3], cmd[4]);
				refreshSchedule();
			} else {
				throw new IllegalArgumentException("Incorrect number of arguments for " + cmd[0] + "! 5 needed, " + cmd.length + " given");
			}
		}

		/*
		  Refreshes the given transit route (reroute all paths between referenced stop facility links)
		  [refreshTransitRoute] [transitLineId] [transitRouteId]
		 */
		else if(REFRESH_TRANSIT_ROUTE.equals(cmd[0])) {
			if(cmd.length >= 3) {
				refreshTransitRoute(getTransitLine(cmd[1]), getTransitRoute(cmd[1], cmd[2]));
			} else {
				throw new IllegalArgumentException("Incorrect number of arguments for " + cmd[0] + "! 3 needed, " + cmd.length + " given");
			}
		}

		/*
		  comment
		 */
		else if(COMMENT_START.equals(cmd[0].substring(0, 2))) {
			// comment
		} else {
			throw new IllegalArgumentException("Invalid command \"" + cmd[0] + "\"");
		}
	}

	/**
	 * @return the TransitLine of the schedule
	 */
	private TransitLine getTransitLine(String transitLineStr) {
		TransitLine transitLine = schedule.getTransitLines().get(Id.create(transitLineStr, TransitLine.class));
		if(transitLine == null) {
			throw new IllegalArgumentException("TransitLine " + transitLineStr + " not found!");
		}
		return transitLine;
	}


	/**
	 * @return the TransitRoute of the schedule based on transit line and transit route as strings
	 */
	private TransitRoute getTransitRoute(String transitLineStr, String transitRouteStr) {
		TransitLine transitLine = schedule.getTransitLines().get(Id.create(transitLineStr, TransitLine.class));
		if(transitLine == null) {
			throw new IllegalArgumentException("TransitLine " + transitLineStr + " not found!");
		}
		Id<TransitRoute> transitRouteId = Id.create(transitRouteStr, TransitRoute.class);
		if(!transitLine.getRoutes().containsKey(transitRouteId)) {
			throw new IllegalArgumentException("TransitRoute " + transitRouteStr + " not found in Transitline " + transitLineStr + "!");
		}
		return transitLine.getRoutes().get(transitRouteId);
	}

	/**
	 * Reroutes the section between two stops that passes the oldlink via the new link
	 * @param transitRoute the transit route
	 * @param oldLinkId the section between two route stops where this link appears is rerouted
	 * @param newLinkId the section is routed via this link
	 */
	@Override
	public void rerouteViaLink(TransitLine transitLine, TransitRoute transitRoute, Id<Link> oldLinkId, Id<Link> newLinkId) {
		List<TransitRouteStop> stopSequence = transitRoute.getStops();
		List<Id<Link>> linkSequence = transitRoute.getRoute().getLinkIds();

		List<Id<Link>> refLinkIds = stopSequence.stream().map(routeStop -> routeStop.getStopFacility().getLinkId()).collect(Collectors.toList());

		if(refLinkIds.contains(oldLinkId)) {
			throw new IllegalArgumentException("Link is referenced to a stop facility, rerouteViaLink cannot be performed. Use changeRefLink instead.");
		} else {
			int i = 0;
			TransitRouteStop fromRouteStop = stopSequence.get(i);
			for(Id<Link> linkId : linkSequence) {
				if(linkId.equals(oldLinkId)) {
					rerouteFromStop(transitLine, transitRoute, fromRouteStop, newLinkId);
					break;
				}
				if(linkId.equals(refLinkIds.get(i))) {
					fromRouteStop = stopSequence.get(i++);
					i++;
				}
			}
		}
	}

	/**
	 *
	 * @param transitRoute  the transit route
	 * @param fromRouteStop the section of the route from this routeStop to the subsequent
	 *                      routeStop is rerouted
	 * @param viaLinkId		the section is routed via this link
	 */
	@Override
	public void rerouteFromStop(TransitLine transitLine, TransitRoute transitRoute, TransitRouteStop fromRouteStop, Id<Link> viaLinkId) {
		List<TransitRouteStop> routeStops = transitRoute.getStops();
		TransitRouteStop toRouteStop = routeStops.get(routeStops.indexOf(fromRouteStop) + 1);

		Id<Link> cutFromLinkId = fromRouteStop.getStopFacility().getLinkId();
		Link cutFromLink = network.getLinks().get(cutFromLinkId);
		Id<Link> cutToLinkId = toRouteStop.getStopFacility().getLinkId();
		Link cutToLink = network.getLinks().get(cutToLinkId);
		Link viaLink = network.getLinks().get(viaLinkId);

		NetworkRoute routeBeforeCut = transitRoute.getRoute().getSubRoute(transitRoute.getRoute().getStartLinkId(), cutFromLinkId);
		NetworkRoute routeAfterCut = transitRoute.getRoute().getSubRoute(cutToLinkId, transitRoute.getRoute().getEndLinkId());

		LeastCostPathCalculator.Path path1 = routers.calcLeastCostPath(cutFromLink.getToNode().getId(), viaLink.getFromNode().getId(), transitLine, transitRoute);
		LeastCostPathCalculator.Path path2 = routers.calcLeastCostPath(viaLink.getToNode().getId(), cutToLink.getFromNode().getId(), transitLine, transitRoute);

		if(path1 != null && path2 != null) {
			List<Id<Link>> newLinkSequence = new ArrayList<>(routeBeforeCut.getLinkIds());
			newLinkSequence.add(routeBeforeCut.getEndLinkId());
			newLinkSequence.addAll(PTMapperTools.getLinkIdsFromPath(path1));
			newLinkSequence.add(viaLinkId);
			newLinkSequence.addAll(PTMapperTools.getLinkIdsFromPath(path2));
			newLinkSequence.add(routeAfterCut.getStartLinkId());
			newLinkSequence.addAll(routeAfterCut.getLinkIds());
			newLinkSequence.add(routeAfterCut.getEndLinkId());
			transitRoute.setRoute(RouteUtils.createNetworkRoute(newLinkSequence, network));
		}
	}
	private void rerouteFromStop(TransitLine transitLine, TransitRoute transitRoute, String fromStopFacilityId, String viaLinkId) {
		rerouteFromStop(transitLine, transitRoute, getRouteStop(transitRoute, fromStopFacilityId), Id.createLinkId(viaLinkId));
	}


	/**
	 * @return the stop facility of a transit route that has the given parentId
	 */
	private TransitStopFacility getChildStopInRoute(TransitRoute transitRoute, String parentId) {
		for(TransitRouteStop routeStop : transitRoute.getStops()) {
			if(parentId.equals(ScheduleTools.createParentStopFacilityId(routeStop.getStopFacility()))) {
				return routeStop.getStopFacility();
			}
		}
		throw new IllegalArgumentException("No child facility for " + parentId + " found in Transit Route " + transitRoute + ".");
	}

	/**
	 * @return the stop facility of a transit route that has the given id
	 */
	private TransitStopFacility getStopFacilityInRoute(TransitRoute transitRoute, String stopFacilityId) {
		return getRouteStop(transitRoute, stopFacilityId).getStopFacility();
	}

	/**
	 * @return the TransitRouteStop with that contain the given stop facility
	 */
	public TransitRouteStop getRouteStop(TransitRoute transitRoute, Id<TransitStopFacility> stopFacilityId) {
		for(TransitRouteStop routeStop : transitRoute.getStops()) {
			if(stopFacilityId.equals(routeStop.getStopFacility().getId())) {
				return routeStop;
			}
		}
		throw new IllegalArgumentException("No child facility for " + stopFacilityId + " found in Transit Route " + transitRoute + ".");
	}
	private TransitRouteStop getRouteStop(TransitRoute transitRoute, String stopFacilityIdStr) {
		return getRouteStop(transitRoute, createStopFacilityId(stopFacilityIdStr));
	}

	/**
	 * Shortcut to create a stop facility id
	 */
	private Id<TransitStopFacility> createStopFacilityId(String stopFacilityIdStr) {
		return Id.create(stopFacilityIdStr, TransitStopFacility.class);
	}

	/**
	 * Changes the reference of a stop facility (for all routes)
	 */
	@Override
	public void changeRefLink(Id<TransitStopFacility> stopFacilityId, Id<Link> newRefLinkId) {
		TransitStopFacility oldStopFacility = schedule.getFacilities().get(stopFacilityId);
		TransitStopFacility newChildStopFacility = parentStops.getChildStopFacility(ScheduleTools.createParentStopFacilityId(stopFacilityId.toString()), newRefLinkId.toString());
		replaceStopFacilityInAllRoutes(oldStopFacility, newChildStopFacility);
	}

	private void changeRefLink(String stopFacilityIdStr, String newRefLinkIdStr) {
		TransitStopFacility oldStopFacility = schedule.getFacilities().get(createStopFacilityId(stopFacilityIdStr));
		TransitStopFacility newChildStopFacility = parentStops.getChildStopFacility(ScheduleTools.createParentStopFacilityId(stopFacilityIdStr), newRefLinkIdStr);
		replaceStopFacilityInAllRoutes(oldStopFacility, newChildStopFacility);
	}

	/**
	 * changes the child stop in the given route
	 */
	private void changeRefLink(TransitLine transitLine, TransitRoute transitRoute, String childStopFacilityIdStr, String newRefLinkIdStr) {
		TransitStopFacility childStopToReplace = schedule.getFacilities().get(Id.create(childStopFacilityIdStr, TransitStopFacility.class));
		TransitStopFacility childStopReplaceWith = parentStops.getChildStopFacility(ScheduleTools.createParentStopFacilityId(childStopFacilityIdStr), newRefLinkIdStr);

		replaceStopFacilityInRoute(transitLine, transitRoute, childStopToReplace, childStopReplaceWith);
	}

	/**
	 * creates a new stop facility and adds it to the schedule
	 */
	public TransitStopFacility createStopFacility(Id<TransitStopFacility> facilityId, Coord coord, String name, Id<Link> linkId) {
		TransitStopFacility newTransitStopFacility = scheduleFactory.createTransitStopFacility(facilityId, coord, false);
		newTransitStopFacility.setName(name);
		newTransitStopFacility.setLinkId(linkId);
		return newTransitStopFacility;
	}

	/**
	 * Adds a link to the network. Uses the attributes (freespeed, nr of lanes, transportModes)
	 * of the attributeLink.
	 */
	@Override
	public void addLink(Id<Link> newLinkId, Id<Node> fromNodeId, Id<Node> toNodeId, Id<Link> attributeLinkId) {
		Node fromNode = network.getNodes().get(fromNodeId);
		Node toNode = network.getNodes().get(toNodeId);

		Link newLink = networkFactory.createLink(newLinkId, fromNode, toNode);

		if(attributeLinkId != null) {
			Link attributeLink = network.getLinks().get(attributeLinkId);

			newLink.setAllowedModes(attributeLink.getAllowedModes());
			newLink.setCapacity(attributeLink.getCapacity());
			newLink.setFreespeed(attributeLink.getFreespeed());
			newLink.setNumberOfLanes(attributeLink.getNumberOfLanes());
		}

		network.addLink(newLink);
	}
	private void addLink(String newLinkIdStr, String fromNodeIdStr, String toNodeIdStr, String attributeLinkIdStr) {
		addLink(Id.createLinkId(newLinkIdStr), Id.createNodeId(fromNodeIdStr), Id.createNodeId(toNodeIdStr), Id.createLinkId(attributeLinkIdStr));
	}

	/**
	 * Replaces a stop facility with another one in the given route. Both ids must exist.
	 */
	public void replaceStopFacilityInRoute(TransitLine transitLine, TransitRoute transitRoute, Id<TransitStopFacility> toReplaceId, Id<TransitStopFacility> replaceWithId) {
		TransitStopFacility toReplace = schedule.getFacilities().get(toReplaceId);
		TransitStopFacility replaceWith = schedule.getFacilities().get(replaceWithId);

		if(toReplace == null) {
			throw new IllegalArgumentException("StopFacility " + toReplaceId + " not found in schedule!");
		} else if(replaceWith == null) {
			throw new IllegalArgumentException("StopFacility " + replaceWithId + " not found in schedule!");
		}
		replaceStopFacilityInRoute(transitLine, transitRoute, toReplace, replaceWith);
	}

	/**
	 * Replaces a stop facility with another one in the given route. Both facilities must exist.
	 */
	public void replaceStopFacilityInRoute(TransitLine transitLine, TransitRoute transitRoute, TransitStopFacility toReplace, TransitStopFacility replaceWith) {
		TransitRouteStop routeStopToReplace = transitRoute.getStop(toReplace);
		if(routeStopToReplace != null) {
			routeStopToReplace.setStopFacility(replaceWith);
			refreshTransitRoute(transitLine, transitRoute);
		} else {
			throw new IllegalArgumentException("StopFacility " + toReplace.getId() + " not found in TransitRoute " + transitRoute.getId());
		}
	}

	/**
	 * Replaces a stop facility with another one the whole schedule. Both must exist.
	 */
	public void replaceStopFacilityInAllRoutes(TransitStopFacility toReplace, TransitStopFacility replaceWith) {
		for(TransitLine line : schedule.getTransitLines().values()) {
			for(TransitRoute route : line.getRoutes().values()) {
				replaceStopFacilityInRoute(line, route, toReplace, replaceWith);
			}
		}
	}

	/**
	 * Gets all transit routes that ar on the given link
	 */
	public Set<Tuple<TransitLine, TransitRoute>> getTransitRoutesOnLink(Id<Link> linkId) {
		Set<Tuple<TransitLine, TransitRoute>> transitRoutesOnLink = new HashSet<>();
		for(TransitLine transitLine : schedule.getTransitLines().values()) {
			for(TransitRoute transitRoute : transitLine.getRoutes().values()) {
				if(ScheduleTools.getTransitRouteLinkIds(transitRoute).contains(linkId)) {
					transitRoutesOnLink.add(new Tuple<>(transitLine, transitRoute));
				}
			}
		}
		return transitRoutesOnLink;
	}

	/**
	 * "Refreshes" the transit route by routing between all referenced links
	 * of the stop facilities.
	 */
	@Override
	public void refreshTransitRoute(TransitLine transitLine, TransitRoute transitRoute) {
		List<TransitRouteStop> routeStops = transitRoute.getStops();
		List<Id<Link>> linkSequence = new ArrayList<>();
		linkSequence.add(routeStops.get(0).getStopFacility().getLinkId());

		// route
		for(int i = 0; i < routeStops.size() - 1; i++) {
			if(routeStops.get(i).getStopFacility().getLinkId() == null) {
				throw new IllegalArgumentException("stop facility " + routeStops.get(i).getStopFacility().getName() + " (" + routeStops.get(i).getStopFacility().getId() + " not referenced!");
			}
			if(routeStops.get(i + 1).getStopFacility().getLinkId() == null) {
				throw new IllegalArgumentException("stop facility " + routeStops.get(i - 1).getStopFacility().getName() + " (" + routeStops.get(i + 1).getStopFacility().getId() + " not referenced!");
			}

			Id<Link> currentLinkId = Id.createLinkId(routeStops.get(i).getStopFacility().getLinkId().toString());

			Link currentLink = network.getLinks().get(currentLinkId);
			Link nextLink = network.getLinks().get(routeStops.get(i + 1).getStopFacility().getLinkId());

			List<Id<Link>> path = PTMapperTools.getLinkIdsFromPath(routers.calcLeastCostPath(currentLink.getToNode().getId(), nextLink.getFromNode().getId(), transitLine, transitRoute));

			if(path != null)
				linkSequence.addAll(path);

			linkSequence.add(nextLink.getId());
		}

		// add link sequence to schedule
		transitRoute.setRoute(RouteUtils.createNetworkRoute(linkSequence, network));
	}

	/**
	 * Refreshes the whole schedule by routing all transit routes.
	 */
	@Override
	public void refreshSchedule() {
		for(TransitLine transitLine : schedule.getTransitLines().values()) {
			for(TransitRoute transitRoute : transitLine.getRoutes().values()) {
				refreshTransitRoute(transitLine, transitRoute);
			}
		}
	}

	/**
	 * Container class for all parent stop facilities
	 */
	private class ParentStops {

		final Map<Id<TransitStopFacility>, ParentStopFacility> fac = new HashMap<>();

		public ParentStops() {
			for(TransitStopFacility stopFacility : schedule.getFacilities().values()) {
				Id<TransitStopFacility> parentId = ScheduleTools.createParentStopFacilityId(stopFacility);
				if(!fac.containsKey(parentId)) {
					fac.put(parentId, new ParentStopFacility(stopFacility));
				} else {
					fac.get(parentId).getChildStopFacility(stopFacility);
				}
			}
		}

		private TransitStopFacility getChildStopFacility(Id<TransitStopFacility> parentId, String newRefLinkIdStr) {
			return fac.get(parentId).getChildStopFacility(newRefLinkIdStr);
		}

	}

	/**
	 * Container class for a parent stop facility (most likely
	 * not actual facilities in the schedule)
	 */
	private class ParentStopFacility {
		final String id;
		final String name;
		final Coord coord;

		final Map<Id<Link>, TransitStopFacility> children = new HashMap<>();

		public ParentStopFacility(String id, String name, Coord coord) {
			this.id = id;
			this.name = name;
			this.coord = coord;
		}

		public ParentStopFacility(TransitStopFacility childStopFacility) {
			this.id = ScheduleTools.createParentStopFacilityId(childStopFacility).toString();
			this.name = childStopFacility.getName();
			this.coord = childStopFacility.getCoord();

			children.put(childStopFacility.getLinkId(), childStopFacility);
		}

		public void getChildStopFacility(TransitStopFacility childStopFacility) {
			children.put(childStopFacility.getLinkId(), childStopFacility);
		}

		/**
		 * Adds a child stop facility for the given refLink, creates
		 * a new one if needed.
		 * @param refLinkId the id of the ref link
		 * @return the childStopFacility
		 */
		public TransitStopFacility getChildStopFacility(Id<Link> refLinkId) {
			Id<TransitStopFacility> newChildStopId = ScheduleTools.createChildStopFacilityId(id, refLinkId.toString());
			TransitStopFacility newChildStopFacilty = schedule.getFacilities().get(newChildStopId);
			if(newChildStopFacilty == null) {
				newChildStopFacilty = createStopFacility(newChildStopId, this.coord, this.name, refLinkId);
				newChildStopFacilty.setLinkId(refLinkId);
				newChildStopFacilty.setStopAreaId(Id.create(this.id, TransitStopArea.class));
				schedule.addStopFacility(newChildStopFacilty);
			}
			children.put(newChildStopFacilty.getLinkId(), newChildStopFacilty);
			return newChildStopFacilty;
		}

		public TransitStopFacility getChildStopFacility(String newRefLinkIdStr) {
			return getChildStopFacility(Id.createLinkId(newRefLinkIdStr));
		}
	}
}