001package jmri.jmrit.operations.trains.trainbuilder;
002
003import java.util.ArrayList;
004import java.util.List;
005
006import jmri.jmrit.operations.locations.Location;
007import jmri.jmrit.operations.locations.Track;
008import jmri.jmrit.operations.rollingstock.RollingStock;
009import jmri.jmrit.operations.rollingstock.engines.Engine;
010import jmri.jmrit.operations.routes.Route;
011import jmri.jmrit.operations.routes.RouteLocation;
012import jmri.jmrit.operations.setup.Control;
013import jmri.jmrit.operations.setup.Setup;
014import jmri.jmrit.operations.trains.BuildFailedException;
015import jmri.jmrit.operations.trains.Train;
016
017import org.apache.commons.lang3.StringUtils;
018import org.slf4j.Logger;
019import org.slf4j.LoggerFactory;
020
021/**
022 * Contains methods for engines when building a train.
023 * 
024 * @author Daniel Boudreau Copyright (C) 2022, 2026
025 */
026public class TrainBuilderEngines extends TrainBuilderBase {
027
028    /**
029     * Builds a list of possible engines for this train.
030     */
031    protected void getAndRemoveEnginesFromList() {
032        setEngineList(engineManager.getAvailableTrainList(getTrain()));
033
034        // remove any locos that the train can't use
035        for (int indexEng = 0; indexEng < getEngineList().size(); indexEng++) {
036            Engine engine = getEngineList().get(indexEng);
037            // remove engines types that train does not service
038            if (!getTrain().isTypeNameAccepted(engine.getTypeName())) {
039                addLine(SEVEN, Bundle.getMessage("buildExcludeEngineType", engine.toString(),
040                        engine.getLocationName(), engine.getTrackName(), engine.getTypeName()));
041                getEngineList().remove(indexEng--);
042                continue;
043            }
044            // remove engines with roads that train does not service
045            if (!getTrain().isLocoRoadNameAccepted(engine.getRoadName())) {
046                addLine(SEVEN, Bundle.getMessage("buildExcludeEngineRoad", engine.toString(),
047                        engine.getLocationName(), engine.getTrackName(), engine.getRoadName()));
048                getEngineList().remove(indexEng--);
049                continue;
050            }
051            // remove engines with owners that train does not service
052            if (!getTrain().isOwnerNameAccepted(engine.getOwnerName())) {
053                addLine(SEVEN, Bundle.getMessage("buildExcludeEngineOwner", engine.toString(),
054                        engine.getLocationName(), engine.getTrackName(), engine.getOwnerName()));
055                getEngineList().remove(indexEng--);
056                continue;
057            }
058            // remove engines with built dates that train does not service
059            if (!getTrain().isBuiltDateAccepted(engine.getBuilt())) {
060                addLine(SEVEN, Bundle.getMessage("buildExcludeEngineBuilt", engine.toString(),
061                        engine.getLocationName(), engine.getTrackName(), engine.getBuilt()));
062                getEngineList().remove(indexEng--);
063                continue;
064            }
065            // remove engines that are out of service
066            if (engine.isOutOfService()) {
067                addLine(SEVEN, Bundle.getMessage("buildExcludeEngineOutOfService", engine.toString(),
068                        engine.getLocationName(), engine.getTrackName()));
069                getEngineList().remove(indexEng--);
070                continue;
071            }
072            // remove engines that aren't on the train's route
073            if (getTrain().getRoute().getLastLocationByName(engine.getLocationName()) == null) {
074                log.debug("removing engine ({}) location ({}) not serviced by train", engine.toString(),
075                        engine.getLocationName());
076                getEngineList().remove(indexEng--);
077                continue;
078            }
079            // is engine at interchange?
080            if (engine.getTrack().isInterchange()) {
081                // don't service a engine at interchange and has been dropped off
082                // by this train
083                if (engine.getTrack().getPickupOption().equals(Track.ANY) &&
084                        engine.getLastRouteId().equals(getTrain().getRoute().getId())) {
085                    addLine(SEVEN, Bundle.getMessage("buildExcludeEngineDropByTrain", engine.toString(),
086                            engine.getTypeName(), getTrain().getRoute().getName(), engine.getLocationName(),
087                            engine.getTrackName()));
088                    getEngineList().remove(indexEng--);
089                    continue;
090                }
091            }
092            // is engine at interchange or spur and is this train allowed to pull?
093            if (engine.getTrack().isInterchange() || engine.getTrack().isSpur()) {
094                if (engine.getTrack().getPickupOption().equals(Track.TRAINS) ||
095                        engine.getTrack().getPickupOption().equals(Track.EXCLUDE_TRAINS)) {
096                    if (engine.getTrack().isPickupTrainAccepted(getTrain())) {
097                        log.debug("Engine ({}) can be picked up by this train", engine.toString());
098                    } else {
099                        addLine(SEVEN,
100                                Bundle.getMessage("buildExcludeEngineByTrain", engine.toString(), engine.getTypeName(),
101                                        engine.getTrack().getTrackTypeName(), engine.getLocationName(),
102                                        engine.getTrackName()));
103                        getEngineList().remove(indexEng--);
104                        continue;
105                    }
106                } else if (engine.getTrack().getPickupOption().equals(Track.ROUTES) ||
107                        engine.getTrack().getPickupOption().equals(Track.EXCLUDE_ROUTES)) {
108                    if (engine.getTrack().isPickupRouteAccepted(getTrain().getRoute())) {
109                        log.debug("Engine ({}) can be picked up by this route", engine.toString());
110                    } else {
111                        addLine(SEVEN,
112                                Bundle.getMessage("buildExcludeEngineByRoute", engine.toString(), engine.getTypeName(),
113                                        engine.getTrack().getTrackTypeName(), engine.getLocationName(),
114                                        engine.getTrackName()));
115                        getEngineList().remove(indexEng--);
116                        continue;
117                    }
118                }
119            }
120        }
121    }
122
123    protected boolean getEngines(String requestedEngines, String model, String road, RouteLocation rl,
124            RouteLocation rld) throws BuildFailedException {
125        return getEngines(requestedEngines, model, road, rl, rld, !USE_BUNIT);
126    }
127
128    /**
129     * Get the engines for this train at a route location. If departing from
130     * staging engines must come from that track. Finds the required number of
131     * engines in a consist, or if the option to build from single locos, builds
132     * a consist for the user. When true, engines successfully added to train
133     * for the leg requested.
134     *
135     * @param requestedEngines Requested number of Engines, can be number, AUTO
136     *                         or AUTO HPT
137     * @param model            Optional model name for the engines
138     * @param road             Optional road name for the engines
139     * @param rl               Departure route location for the engines
140     * @param rld              Destination route location for the engines
141     * @param useBunit         true if B unit engine is allowed
142     * @return true if correct number of engines found.
143     * @throws BuildFailedException if coding issue
144     */
145    protected boolean getEngines(String requestedEngines, String model, String road, RouteLocation rl,
146            RouteLocation rld, boolean useBunit) throws BuildFailedException {
147        // load departure track if staging
148        Track departStagingTrack = null;
149        if (rl == getTrain().getTrainDepartsRouteLocation()) {
150            // get departure track from staging, could be null
151            departStagingTrack = getDepartureStagingTrack();
152        }
153
154        int reqNumberEngines = getNumberEngines(requestedEngines);
155
156        // if not departing staging track and engines aren't required done!
157        if (departStagingTrack == null && reqNumberEngines == 0) {
158            return true;
159        }
160        // if departing staging and no engines required and none available,
161        // we're done
162        if (departStagingTrack != null && reqNumberEngines == 0 && departStagingTrack.getNumberEngines() == 0) {
163            return true;
164        }
165
166        // code check, staging track selection checks number of engines needed
167        if (departStagingTrack != null &&
168                reqNumberEngines != 0 &&
169                departStagingTrack.getNumberEngines() != reqNumberEngines) {
170            throw new BuildFailedException(Bundle.getMessage("buildStagingNotEngines", departStagingTrack.getName(),
171                    departStagingTrack.getNumberEngines(), reqNumberEngines));
172        }
173
174        // code check
175        if (rl == null || rld == null) {
176            throw new BuildFailedException(
177                    Bundle.getMessage("buildErrorEngLocUnknown"));
178        }
179
180        addLine(FIVE, Bundle.getMessage("buildBegineSearchEngines", reqNumberEngines, model, road,
181                rl.getName(), rld.getName()));
182
183        int assignedLocos = 0; // the number of locos assigned to this train
184        List<Engine> singleLocos = new ArrayList<>();
185        for (_engineIndex = 0; _engineIndex < getEngineList().size(); _engineIndex++) {
186            Engine engine = getEngineList().get(_engineIndex);
187            log.debug("Engine ({}) at location ({}, {})", engine.toString(), engine.getLocationName(),
188                    engine.getTrackName());
189
190            // use engines that are departing from the selected staging track
191            // (departTrack
192            // != null if staging)
193            if (departStagingTrack != null && !departStagingTrack.equals(engine.getTrack())) {
194                continue;
195            }
196            // use engines that are departing from the correct location
197            if (!engine.getLocationName().equals(rl.getName())) {
198                log.debug("Skipping engine ({}) at location ({})", engine.toString(), engine.getLocationName());
199                continue;
200            }
201            // skip engines models that train does not service
202            if (!model.equals(Train.NONE) && !engine.getModel().equals(model)) {
203                addLine(SEVEN, Bundle.getMessage("buildExcludeEngineModel", engine.toString(),
204                        engine.getModel(), engine.getLocationName()));
205                continue;
206            }
207            // Does the train have a very specific engine road name requirement?
208            if (!road.equals(Train.NONE) && !engine.getRoadName().equals(road)) {
209                addLine(SEVEN, Bundle.getMessage("buildExcludeEngineRoad", engine.toString(),
210                        engine.getLocationName(), engine.getTrackName(), engine.getRoadName()));
211                continue;
212            }
213            // skip engines on tracks that don't service the train's departure
214            // direction
215            if (!checkPickUpTrainDirection(engine, rl)) {
216                continue;
217            }
218            // skip engines that have been assigned destinations that don't
219            // match the requested destination
220            if (engine.getDestination() != null && !engine.getDestinationName().equals(rld.getName())) {
221                addLine(SEVEN, Bundle.getMessage("buildExcludeEngineDestination", engine.toString(),
222                        engine.getDestinationName()));
223                continue;
224            }
225            // don't use non lead locos in a consist
226            if (engine.getConsist() != null) {
227                if (engine.isLead()) {
228                    addLine(SEVEN,
229                            Bundle.getMessage("buildEngineLeadConsist", engine.toString(),
230                                    engine.getConsist().getName(), engine.getConsist().getEngines().size()));
231                } else {
232                    continue;
233                }
234            }
235            if (!checkQuickServiceDeparting(engine, rl)) {
236                continue;
237            }
238            // departing staging, then all locos must go!
239            if (departStagingTrack != null) {
240                if (!setEngineDestination(engine, rl, rld)) {
241                    return false;
242                }
243                if (engine.getConsist() != null) {
244                    assignedLocos = assignedLocos + engine.getConsist().getSize();
245                } else {
246                    assignedLocos++;
247                }
248                continue;
249            }
250            // can't use B units if requesting one loco
251            if (!useBunit && reqNumberEngines == 1 && engine.isBunit()) {
252                addLine(SEVEN,
253                        Bundle.getMessage("buildExcludeEngineBunit", engine.toString(), engine.getModel()));
254                continue;
255            }
256            // is this engine part of a consist?
257            if (engine.getConsist() == null) {
258                // single engine, but does the train require a consist?
259                if (reqNumberEngines > 1) {
260                    addLine(SEVEN,
261                            Bundle.getMessage("buildExcludeEngineSingle", engine.toString(), reqNumberEngines));
262                    singleLocos.add(engine);
263                    continue;
264                }
265                // engine is part of a consist
266            } else if (engine.getConsist().getSize() == reqNumberEngines) {
267                log.debug("Consist ({}) has the required number of engines", engine.getConsist().getName()); // NOI18N
268            } else if (reqNumberEngines != 0) {
269                addLine(SEVEN,
270                        Bundle.getMessage("buildExcludeEngConsistNumber", engine.toString(),
271                                engine.getConsist().getName(), engine.getConsist().getSize()));
272                continue;
273            }
274            // found a loco or consist!
275            assignedLocos++;
276
277            // now find terminal track for engine(s)
278            addLine(FIVE,
279                    Bundle.getMessage("buildEngineRoadModelType", engine.toString(), engine.getRoadName(),
280                            engine.getModel(), engine.getTypeName(), engine.getLocationName(), engine.getTrackName(),
281                            rld.getName()));
282            if (setEngineDestination(engine, rl, rld)) {
283                return true; // normal exit when not staging
284            }
285        }
286        // build a consist out of non-consisted locos
287        if (assignedLocos == 0 && reqNumberEngines > 1 && getTrain().isBuildConsistEnabled()) {
288            if (buildConsistFromSingleLocos(reqNumberEngines, singleLocos, rl, rld)) {
289                return true; // normal exit when building with single locos
290            }
291        }
292        if (assignedLocos == 0) {
293            String locationName = rl.getName();
294            if (departStagingTrack != null) {
295                locationName = locationName + ", " + departStagingTrack.getName();
296            }
297            addLine(FIVE, Bundle.getMessage("buildNoLocosFoundAtLocation", locationName));
298        } else if (departStagingTrack != null && (reqNumberEngines == 0 || reqNumberEngines == assignedLocos)) {
299            return true; // normal exit assigning from staging
300        }
301        // not able to assign engines to train
302        return false;
303    }
304
305    private boolean buildConsistFromSingleLocos(int reqNumberEngines, List<Engine> singleLocos, RouteLocation rl,
306            RouteLocation rld) {
307        addLine(FIVE, Bundle.getMessage("buildOptionBuildConsist", reqNumberEngines, rl.getName()));
308        addLine(FIVE, Bundle.getMessage("buildOptionSingleLocos", singleLocos.size(), rl.getName()));
309        if (singleLocos.size() >= reqNumberEngines) {
310            int locos = 0;
311            // first find an "A" unit
312            for (Engine engine : singleLocos) {
313                if (engine.isBunit()) {
314                    continue;
315                }
316                if (setEngineDestination(engine, rl, rld)) {
317                    singleLocos.remove(engine);
318                    locos++;
319                    break; // found "A" unit
320                }
321            }
322            // did we find an "A" unit?
323            if (locos > 0) {
324                // now add the rest "A" or "B" units
325                for (Engine engine : singleLocos) {
326                    if (setEngineDestination(engine, rl, rld)) {
327                        locos++;
328                    }
329                    if (locos == reqNumberEngines) {
330                        return true; // done!
331                    }
332                }
333            } else {
334                // list the "B" units found
335                for (Engine engine : singleLocos) {
336                    if (engine.isBunit()) {
337                        addLine(FIVE,
338                                Bundle.getMessage("BuildEngineBunit", engine.toString(), engine.getLocationName(),
339                                        engine.getTrackName()));
340                    }
341                }
342            }
343        }
344        return false;
345    }
346
347    /**
348     * Adds engines to the train starting at the first location in the train's
349     * route. Note that engines from staging are already part of the train.
350     * There can be up to two engine swaps in a train's route.
351     * 
352     * @throws BuildFailedException if required engines can't be added to train.
353     */
354    protected void addEnginesToTrain() throws BuildFailedException {
355        // allow up to two engine and caboose swaps in the train's route
356        RouteLocation engineTerminatesFirstLeg = getTrain().getTrainTerminatesRouteLocation();
357        RouteLocation engineTerminatesSecondLeg = getTrain().getTrainTerminatesRouteLocation();
358        RouteLocation engineTerminatesThirdLeg = getTrain().getTrainTerminatesRouteLocation();
359
360        // Adjust where the locos will terminate
361        if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES &&
362                getTrain().getSecondLegStartRouteLocation() != null) {
363            engineTerminatesFirstLeg = getTrain().getSecondLegStartRouteLocation();
364        }
365        if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES &&
366                getTrain().getThirdLegStartRouteLocation() != null) {
367            engineTerminatesSecondLeg = getTrain().getThirdLegStartRouteLocation();
368            // No engine or caboose change at first leg?
369            if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) != Train.CHANGE_ENGINES) {
370                engineTerminatesFirstLeg = getTrain().getThirdLegStartRouteLocation();
371            }
372        }
373        // optionally set out added engines
374        if (getTrain().getSecondLegEndRouteLocation() != null &&
375                ((getTrain().getSecondLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES ||
376                        (getTrain().getSecondLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES)) {
377            engineTerminatesSecondLeg = getTrain().getSecondLegEndRouteLocation();
378        }
379        if (getTrain().getThirdLegEndRouteLocation() != null &&
380                ((getTrain().getThirdLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES ||
381                        (getTrain().getThirdLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES)) {
382            engineTerminatesThirdLeg = getTrain().getThirdLegEndRouteLocation();
383        }
384
385        if (getTrain().getLeadEngine() == null) {
386            // option to remove locos from the train
387            if ((getTrain().getSecondLegOptions() & Train.REMOVE_ENGINES) == Train.REMOVE_ENGINES &&
388                    getTrain().getSecondLegStartRouteLocation() != null) {
389                addLine(THREE, BLANK_LINE);
390                addLine(THREE,
391                        Bundle.getMessage("buildTrainRemoveEngines", getTrain().getSecondLegNumberEngines(),
392                                getTrain().getSecondLegStartLocationName(), getTrain().getSecondLegEngineModel(),
393                                getTrain().getSecondLegEngineRoad()));
394                if (getEngines(getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(),
395                        getTrain().getSecondLegEngineRoad(), getTrain().getTrainDepartsRouteLocation(),
396                        getTrain().getSecondLegStartRouteLocation())) {
397                } else if (getConsist(getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(),
398                        getTrain().getSecondLegEngineRoad(), getTrain().getTrainDepartsRouteLocation(),
399                        getTrain().getSecondLegStartRouteLocation())) {
400                } else {
401                    throw new BuildFailedException(Bundle.getMessage("buildErrorEngines",
402                            getTrain().getSecondLegNumberEngines(), getTrain().getTrainDepartsName(),
403                            getTrain().getSecondLegStartRouteLocation().getLocation().getName()));
404                }
405            }
406            if ((getTrain().getThirdLegOptions() & Train.REMOVE_ENGINES) == Train.REMOVE_ENGINES &&
407                    getTrain().getThirdLegStartRouteLocation() != null) {
408                addLine(THREE, BLANK_LINE);
409                addLine(THREE,
410                        Bundle.getMessage("buildTrainRemoveEngines", getTrain().getThirdLegNumberEngines(),
411                                getTrain().getThirdLegStartLocationName(), getTrain().getThirdLegEngineModel(),
412                                getTrain().getThirdLegEngineRoad()));
413                if (getEngines(getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(),
414                        getTrain().getThirdLegEngineRoad(), getTrain().getTrainDepartsRouteLocation(),
415                        getTrain().getThirdLegStartRouteLocation())) {
416                } else if (getConsist(getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(),
417                        getTrain().getThirdLegEngineRoad(), getTrain().getTrainDepartsRouteLocation(),
418                        getTrain().getThirdLegStartRouteLocation())) {
419                } else {
420                    throw new BuildFailedException(Bundle.getMessage("buildErrorEngines",
421                            getTrain().getThirdLegNumberEngines(), getTrain().getTrainDepartsName(),
422                            getTrain().getThirdLegStartRouteLocation().getLocation().getName()));
423                }
424            }
425            // load engines at the start of the route for this train
426            if (getEngines(getTrain().getNumberEngines(), getTrain().getEngineModel(), getTrain().getEngineRoad(),
427                    getTrain().getTrainDepartsRouteLocation(), engineTerminatesFirstLeg)) {
428                // when adding a caboose later in the route, no engine change
429                _secondLeadEngine = _lastEngine;
430                _thirdLeadEngine = _lastEngine;
431            } else if (getConsist(getTrain().getNumberEngines(), getTrain().getEngineModel(),
432                    getTrain().getEngineRoad(),
433                    getTrain().getTrainDepartsRouteLocation(), engineTerminatesFirstLeg)) {
434                // when adding a caboose later in the route, no engine change
435                _secondLeadEngine = _lastEngine;
436                _thirdLeadEngine = _lastEngine;
437            } else {
438                addLine(THREE, BLANK_LINE);
439                throw new BuildFailedException(Bundle.getMessage("buildErrorEngines", getTrain().getNumberEngines(),
440                        getTrain().getTrainDepartsName(), engineTerminatesFirstLeg.getName()));
441            }
442        }
443
444        // First engine change in route?
445        if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES ||
446                (getTrain().getSecondLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES ||
447                (getTrain().getSecondLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES) {
448            addLine(THREE, BLANK_LINE);
449            if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES) {
450                addLine(THREE,
451                        Bundle.getMessage("buildTrainEngineChange", getTrain().getSecondLegStartLocationName(),
452                                getTrain().getSecondLegStartRouteLocation().getId(),
453                                getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(),
454                                getTrain().getSecondLegEngineRoad()));
455            } else {
456                addLine(THREE,
457                        Bundle.getMessage("buildTrainAddEngines", getTrain().getSecondLegNumberEngines(),
458                                getTrain().getSecondLegStartLocationName(), getTrain().getSecondLegEngineModel(),
459                                getTrain().getSecondLegEngineRoad()));
460            }
461            if (getEngines(getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(),
462                    getTrain().getSecondLegEngineRoad(), getTrain().getSecondLegStartRouteLocation(),
463                    engineTerminatesSecondLeg)) {
464                _secondLeadEngine = _lastEngine;
465                _thirdLeadEngine = _lastEngine;
466            } else if (getConsist(getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(),
467                    getTrain().getSecondLegEngineRoad(), getTrain().getSecondLegStartRouteLocation(),
468                    engineTerminatesSecondLeg)) {
469                _secondLeadEngine = _lastEngine;
470                _thirdLeadEngine = _lastEngine;
471            } else {
472                throw new BuildFailedException(
473                        Bundle.getMessage("buildErrorEngines", getTrain().getSecondLegNumberEngines(),
474                                getTrain().getSecondLegStartRouteLocation(), engineTerminatesSecondLeg));
475            }
476        }
477        // Second engine change in route?
478        if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES ||
479                (getTrain().getThirdLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES ||
480                (getTrain().getThirdLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES) {
481            addLine(THREE, BLANK_LINE);
482            if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES) {
483                addLine(THREE,
484                        Bundle.getMessage("buildTrainEngineChange", getTrain().getThirdLegStartLocationName(),
485                                getTrain().getThirdLegStartRouteLocation().getId(),
486                                getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(),
487                                getTrain().getThirdLegEngineRoad()));
488            } else {
489                addLine(THREE,
490                        Bundle.getMessage("buildTrainAddEngines", getTrain().getThirdLegNumberEngines(),
491                                getTrain().getThirdLegStartLocationName(), getTrain().getThirdLegEngineModel(),
492                                getTrain().getThirdLegEngineRoad()));
493            }
494            if (getEngines(getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(),
495                    getTrain().getThirdLegEngineRoad(), getTrain().getThirdLegStartRouteLocation(),
496                    engineTerminatesThirdLeg)) {
497                _thirdLeadEngine = _lastEngine;
498            } else if (getConsist(getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(),
499                    getTrain().getThirdLegEngineRoad(), getTrain().getThirdLegStartRouteLocation(),
500                    engineTerminatesThirdLeg)) {
501                _thirdLeadEngine = _lastEngine;
502            } else {
503                throw new BuildFailedException(
504                        Bundle.getMessage("buildErrorEngines", Integer.parseInt(getTrain().getThirdLegNumberEngines()),
505                                getTrain().getThirdLegStartRouteLocation(),
506                                getTrain().getTrainTerminatesRouteLocation()));
507            }
508        }
509        if (!getTrain().getNumberEngines().equals("0") &&
510                (!getTrain().isBuildConsistEnabled() || Setup.getHorsePowerPerTon() == 0)) {
511            addLine(SEVEN, Bundle.getMessage("buildDoneAssingEnginesTrain", getTrain().getName()));
512        }
513    }
514
515    protected boolean getConsist(String reqNumEngines, String model, String road, RouteLocation rl, RouteLocation rld)
516            throws BuildFailedException {
517        if (reqNumEngines.equals(Train.AUTO_HPT)) {
518            for (int i = 2; i < Setup.getMaxNumberEngines(); i++) {
519                if (getEngines(Integer.toString(i), model, road, rl, rld)) {
520                    return true;
521                }
522            }
523        }
524        return false;
525    }
526
527    protected void showEnginesByLocation() {
528        // show how many engines were found
529        addLine(SEVEN, BLANK_LINE);
530        addLine(ONE,
531                Bundle.getMessage("buildFoundLocos", Integer.toString(getEngineList().size()), getTrain().getName()));
532
533        // only show engines once using the train's route
534        List<String> locationNames = new ArrayList<>();
535        for (RouteLocation rl : getTrain().getRoute().getLocationsBySequenceList()) {
536            if (locationNames.contains(rl.getName())) {
537                continue;
538            }
539            locationNames.add(rl.getName());
540            int count = countRollingStockAt(rl, new ArrayList<RollingStock>(getEngineList()));
541            if (rl.getLocation().isStaging()) {
542                addLine(FIVE,
543                        Bundle.getMessage("buildLocosInStaging", count, rl.getName()));
544            } else {
545                addLine(FIVE,
546                        Bundle.getMessage("buildLocosAtLocation", count, rl.getName()));
547            }
548            for (Engine engine : getEngineList()) {
549                if (engine.getLocationName().equals(rl.getName())) {
550                    addLine(SEVEN,
551                            Bundle.getMessage("buildLocoAtLocWithMoves", engine.toString(), engine.getTypeName(),
552                                    engine.getModel(), engine.getLocationName(), engine.getTrackName(),
553                                    engine.getMoves()));
554                }
555            }
556            addLine(SEVEN, BLANK_LINE);
557        }
558    }
559
560    /**
561     * Adds engines to the train if needed based on HPT. Note that the engine
562     * additional weight isn't considered in this method so HP requirements can
563     * be lower compared to the original calculation which did include the
564     * weight of the engines.
565     *
566     * @param hpAvailable   the engine hp already assigned to the train for this
567     *                      leg
568     * @param extraHpNeeded the additional hp needed
569     * @param rlNeedHp      where in the route the additional hp is needed
570     * @param rl            the start of the leg
571     * @param rld           the end of the leg
572     * @throws BuildFailedException if unable to add engines to train
573     */
574    protected void addEnginesBasedHPT(int hpAvailable, int extraHpNeeded, RouteLocation rlNeedHp, RouteLocation rl,
575            RouteLocation rld) throws BuildFailedException {
576        if (rlNeedHp == null) {
577            return;
578        }
579        
580        // determine how many locos have already been assigned to the train
581        int numberLocos = getTrain().getNumberEngines(rl);
582
583        addLine(ONE, BLANK_LINE);
584        addLine(ONE, Bundle.getMessage("buildTrainReqExtraHp", extraHpNeeded, rlNeedHp.getName(),
585                rld.getName(), numberLocos));
586
587        // determine engine model and road
588        String model = getTrain().getEngineModel();
589        String road = getTrain().getEngineRoad();
590        if (rl == getTrain().getSecondLegStartRouteLocation() &&
591                ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES ||
592                        (getTrain().getSecondLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES)) {
593            model = getTrain().getSecondLegEngineModel();
594            road = getTrain().getSecondLegEngineRoad();
595        } else if (rl == getTrain().getThirdLegStartRouteLocation() &&
596                ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES ||
597                        (getTrain().getThirdLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES)) {
598            model = getTrain().getThirdLegEngineModel();
599            road = getTrain().getThirdLegEngineRoad();
600        }
601
602        while (numberLocos < Setup.getMaxNumberEngines()) {
603            // if no engines assigned, can't use B unit as first engine
604            if (!getEngines("1", model, road, rl, rld, numberLocos > 0)) {
605                throw new BuildFailedException(Bundle.getMessage("buildErrorEngines", Bundle.getMessage("additional"),
606                        rl.getName(), rld.getName()));
607            }
608            numberLocos++;
609            int currentHp = getTrain().getTrainHorsePower(rlNeedHp);
610            if (currentHp >= hpAvailable + extraHpNeeded) {
611                break; // done
612            }
613            if (numberLocos < Setup.getMaxNumberEngines()) {
614                addLine(FIVE, BLANK_LINE);
615                addLine(THREE,
616                        Bundle.getMessage("buildContinueAddLocos", (hpAvailable + extraHpNeeded - currentHp),
617                                rlNeedHp.getName(), rld.getName(), numberLocos, currentHp));
618            } else {
619                addLine(FIVE,
620                        Bundle.getMessage("buildMaxNumberLocoAssigned", Setup.getMaxNumberEngines()));
621            }
622        }
623    }
624
625    /**
626     * Checks to see if the engine or consist assigned to the train has the
627     * appropriate HP. If the train's HP requirements are significantly higher
628     * or lower than the engine that was assigned, the program will search for a
629     * more appropriate engine or consist, and assign that engine or consist to
630     * the train. The HP calculation is based on a minimum train speed of 36
631     * MPH. The formula HPT x 12 / % Grade = Speed, is used to determine the
632     * horsepower required. Speed is fixed at 36 MPH. For example a 1% grade
633     * requires a minimum of 3 HPT. Disabled for trains departing staging.
634     * 
635     * @throws BuildFailedException if coding error.
636     */
637    protected void checkEngineHP() throws BuildFailedException {
638        if (Setup.getHorsePowerPerTon() != 0) {
639            if (getTrain().getNumberEngines().equals(Train.AUTO_HPT)) {
640                checkEngineHP(getTrain().getLeadEngine(), getTrain().getEngineModel(), getTrain().getEngineRoad());
641            }
642            if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES &&
643                    getTrain().getSecondLegNumberEngines().equals(Train.AUTO_HPT)) {
644                checkEngineHP(_secondLeadEngine, getTrain().getSecondLegEngineModel(),
645                        getTrain().getSecondLegEngineRoad());
646            }
647            if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES &&
648                    getTrain().getThirdLegNumberEngines().equals(Train.AUTO_HPT)) {
649                checkEngineHP(_thirdLeadEngine, getTrain().getThirdLegEngineModel(),
650                        getTrain().getThirdLegEngineRoad());
651            }
652        }
653    }
654
655    private void checkEngineHP(Engine leadEngine, String model, String road) throws BuildFailedException {
656        // code check
657        if (leadEngine == null) {
658            throw new BuildFailedException("ERROR coding issue, engine missing from checkEngineHP()");
659        }
660        // departing staging?
661        if (leadEngine.getRouteLocation() == getTrain().getTrainDepartsRouteLocation() &&
662                getTrain().isDepartingStaging()) {
663            return;
664        }
665        addLine(ONE, BLANK_LINE);
666        addLine(ONE,
667                Bundle.getMessage("buildDetermineHpNeeded", leadEngine.toString(), leadEngine.getLocationName(),
668                        leadEngine.getDestinationName(), getTrain().getTrainHorsePower(leadEngine.getRouteLocation()),
669                        Setup.getHorsePowerPerTon()));
670        // now determine the HP needed for this train
671        double hpNeeded = 0;
672        int hpAvailable = 0;
673        Route route = getTrain().getRoute();
674        if (route != null) {
675            boolean helper = false;
676            boolean foundStart = false;
677            for (RouteLocation rl : route.getLocationsBySequenceList()) {
678                if (!foundStart && rl != leadEngine.getRouteLocation()) {
679                    continue;
680                }
681                foundStart = true;
682                if ((getTrain().getSecondLegOptions() == Train.HELPER_ENGINES &&
683                        rl == getTrain().getSecondLegStartRouteLocation()) ||
684                        (getTrain().getThirdLegOptions() == Train.HELPER_ENGINES &&
685                                rl == getTrain().getThirdLegStartRouteLocation())) {
686                    addLine(FIVE,
687                            Bundle.getMessage("AddHelpersAt", rl.getName()));
688                    helper = true;
689                }
690                if ((getTrain().getSecondLegOptions() == Train.HELPER_ENGINES &&
691                        rl == getTrain().getSecondLegEndRouteLocation()) ||
692                        (getTrain().getThirdLegOptions() == Train.HELPER_ENGINES &&
693                                rl == getTrain().getThirdLegEndRouteLocation())) {
694                    addLine(FIVE,
695                            Bundle.getMessage("RemoveHelpersAt", rl.getName()));
696                    helper = false;
697                }
698                if (helper) {
699                    continue; // ignore HP needed when helpers are assigned to
700                              // the train
701                }
702                // check for a change of engines in the train's route
703                if (rl == leadEngine.getRouteDestination()) {
704                    log.debug("Remove loco ({}) at ({})", leadEngine.toString(), rl.getName());
705                    break; // done
706                }
707                if (getTrain().getTrainHorsePower(rl) > hpAvailable)
708                    hpAvailable = getTrain().getTrainHorsePower(rl);
709                int weight = rl.getTrainWeight();
710                double hpRequired = (Control.speedHpt * rl.getGrade() / 12) * weight;
711                if (hpRequired < Setup.getHorsePowerPerTon() * weight)
712                    hpRequired = Setup.getHorsePowerPerTon() * weight; // min HPT
713                if (hpRequired > hpNeeded) {
714                    addLine(SEVEN,
715                            Bundle.getMessage("buildReportTrainHpNeeds", weight, getTrain().getNumberCarsInTrain(rl),
716                                    rl.getGrade(), rl.getName(), rl.getId(), hpRequired));
717                    hpNeeded = hpRequired;
718                }
719            }
720        }
721        if (hpNeeded > hpAvailable) {
722            addLine(ONE,
723                    Bundle.getMessage("buildAssignedHpNotEnough", leadEngine.toString(), hpAvailable, hpNeeded));
724            getNewEngine((int) hpNeeded, leadEngine, model, road);
725        } else if (hpAvailable > 2 * hpNeeded) {
726            addLine(ONE,
727                    Bundle.getMessage("buildAssignedHpTooMuch", leadEngine.toString(), hpAvailable, hpNeeded));
728            getNewEngine((int) hpNeeded, leadEngine, model, road);
729        } else {
730            log.debug("Keeping engine ({}) it meets the train's HP requirement", leadEngine.toString());
731        }
732    }
733
734    /**
735     * Removes engine from train and attempts to replace it with engine or
736     * consist that meets the HP requirements of the train.
737     *
738     * @param hpNeeded   How much hp is needed
739     * @param leadEngine The lead engine for this leg
740     * @param model      The engine's model
741     * @param road       The engine's road
742     * @throws BuildFailedException if new engine not found
743     */
744    protected void getNewEngine(int hpNeeded, Engine leadEngine, String model, String road)
745            throws BuildFailedException {
746        // save lead engine's rl, and rld
747        RouteLocation rl = leadEngine.getRouteLocation();
748        RouteLocation rld = leadEngine.getRouteDestination();
749        removeEngineFromTrain(leadEngine);
750        getEngineList().add(0, leadEngine); // put engine back into the pool
751        if (hpNeeded < 50) {
752            hpNeeded = 50; // the minimum HP
753        }
754        int hpMax = hpNeeded;
755        // largest single engine HP known today is less than 15,000.
756        // high end modern diesel locos approximately 5000 HP.
757        // 100 car train at 100 tons per car and 2 HPT requires 20,000 HP.
758        // will assign consisted engines to train.
759        boolean foundLoco = false;
760        List<Engine> rejectedLocos = new ArrayList<>();
761        hpLoop: while (hpMax < 20000) {
762            hpMax += hpNeeded / 2; // start off looking for an engine with no
763                                   // more than 50% extra HP
764            log.debug("Max hp {}", hpMax);
765            for (Engine engine : getEngineList()) {
766                if (rejectedLocos.contains(engine)) {
767                    continue;
768                }
769                // don't use non lead locos in a consist
770                if (engine.getConsist() != null && !engine.isLead()) {
771                    continue;
772                }
773                if (engine.getLocation() != rl.getLocation()) {
774                    continue;
775                }
776                if (!model.equals(Train.NONE) && !engine.getModel().equals(model)) {
777                    continue;
778                }
779                if (!road.equals(Train.NONE) && !engine.getRoadName().equals(road) ||
780                        road.equals(Train.NONE) && !getTrain().isLocoRoadNameAccepted(engine.getRoadName())) {
781                    continue;
782                }
783                int engineHp = engine.getHpInteger();
784                if (engine.getConsist() != null) {
785                    for (Engine e : engine.getConsist().getEngines()) {
786                        if (e != engine) {
787                            engineHp = engineHp + e.getHpInteger();
788                        }
789                    }
790                }
791                if (engineHp > hpNeeded && engineHp <= hpMax) {
792                    addLine(FIVE,
793                            Bundle.getMessage("buildLocoHasRequiredHp", engine.toString(), engineHp, hpNeeded));
794                    if (setEngineDestination(engine, rl, rld)) {
795                        foundLoco = true;
796                        break hpLoop;
797                    } else {
798                        rejectedLocos.add(engine);
799                    }
800                }
801            }
802        }
803        if (!foundLoco && !getTrain().isBuildConsistEnabled()) {
804            throw new BuildFailedException(Bundle.getMessage("buildErrorEngHp", rl.getLocation().getName()));
805        }
806    }
807
808    /**
809     * Sets the destination track for an engine and assigns it to the train.
810     *
811     * @param engine The engine to be added to train
812     * @param rl     Departure route location
813     * @param rld    Destination route location
814     * @return true if destination track found and set
815     */
816    protected boolean setEngineDestination(Engine engine, RouteLocation rl, RouteLocation rld) {
817        engine.setRouteDestinationTiming(rld); // for timing
818        // engine to staging?
819        if (rld == getTrain().getTrainTerminatesRouteLocation() && getTerminateStagingTrack() != null) {
820            String status =
821                    engine.checkDestination(getTerminateStagingTrack().getLocation(), getTerminateStagingTrack());
822            if (status.equals(Track.OKAY)) {
823                addEngineToTrain(engine, rl, rld, getTerminateStagingTrack());
824                return true; // done
825            } else {
826                addLine(SEVEN,
827                        Bundle.getMessage("buildCanNotDropEngineToTrack", engine.toString(),
828                                getTerminateStagingTrack().getTrackTypeName(),
829                                getTerminateStagingTrack().getLocation().getName(),
830                                getTerminateStagingTrack().getName(), status));
831            }
832        } else {
833            // find a destination track for this engine
834            Location destination = rld.getLocation();
835            List<Track> destTracks = destination.getTracksByMoves(null);
836            if (destTracks.size() == 0) {
837                addLine(THREE, Bundle.getMessage("buildNoTracksAtDestination", rld.getName()));
838            }
839            for (Track track : destTracks) {
840                if (!checkDropTrainDirection(engine, rld, track)) {
841                    continue;
842                }
843                if (!checkTrainCanDrop(engine, track)) {
844                    continue;
845                }
846                String status = engine.checkDestination(destination, track);
847                if (status.equals(Track.OKAY)) {
848                    addLine(FIVE,
849                            Bundle.getMessage("buildEngineCanDrop", engine.toString(),
850                                    track.getTrackTypeName(),
851                                    track.getLocation().getName(), track.getName()));
852                    addEngineToTrain(engine, rl, rld, track);
853                    return true;
854                } else {
855                    addLine(SEVEN,
856                            Bundle.getMessage("buildCanNotDropEngineToTrack", engine.toString(),
857                                    track.getTrackTypeName(),
858                                    track.getLocation().getName(), track.getName(), status));
859                }
860            }
861            addLine(FIVE,
862                    Bundle.getMessage("buildCanNotDropEngToDest", engine.toString(), rld.getName()));
863        }
864        return false; // not able to set loco's destination
865    }
866
867    /**
868     * Adds an engine to the train.
869     *
870     * @param engine the engine being added to the train
871     * @param rl     where in the train's route to pick up the engine
872     * @param rld    where in the train's route to set out the engine
873     * @param track  the destination track for this engine
874     */
875    private void addEngineToTrain(Engine engine, RouteLocation rl, RouteLocation rld, Track track) {
876        _lastEngine = engine; // needed in case there's a engine change in the
877                              // train's route
878        if (getTrain().getLeadEngine() == null) {
879            getTrain().setLeadEngine(engine); // load lead engine
880        }
881        engine = checkQuickServiceArrival(engine, rld, track);
882        addLine(ONE, Bundle.getMessage("buildEngineAssigned", engine.toString(), rl.getName(),
883                rld.getName(), track.getName()));
884        engine.setDestination(track.getLocation(), track, Engine.FORCE);
885        int length = engine.getTotalLength();
886        int weightTons = engine.getAdjustedWeightTons();
887        // engine in consist?
888        if (engine.getConsist() != null) {
889            length = engine.getConsist().getTotalLength();
890            weightTons = engine.getConsist().getAdjustedWeightTons();
891            for (Engine cEngine : engine.getConsist().getEngines()) {
892                if (cEngine != engine) {
893                    addLine(ONE, Bundle.getMessage("buildEngineAssigned", cEngine.toString(),
894                            rl.getName(), rld.getName(), track.getName()));
895                    cEngine.setTrain(getTrain());
896                    cEngine.setRouteLocation(rl);
897                    cEngine.setRouteDestination(rld);
898                    cEngine.setDestination(track.getLocation(), track, RollingStock.FORCE); // force
899                }
900            }
901        }
902        remove (engine);
903        // now adjust train length and weight for each location that engines are
904        // in the train
905        finishAddRsToTrain(engine, rl, rld, length, weightTons);
906    }
907
908    /**
909     * Checks to see if track is requesting a quick service. Since it isn't
910     * possible for a engine to be pulled and set out twice, this code creates a
911     * "clone" engine to create the requested Manifest. A engine could have
912     * multiple clones, therefore each clone has a creation order number. The
913     * first clone is used to restore a engine's location in the case of reset.
914     * 
915     * @param engine the engine possibly needing quick service
916     * @param track  the destination track
917     * @return the engine if not quick service, or a clone if quick service
918     */
919    private Engine checkQuickServiceArrival(Engine engine, RouteLocation rld, Track track) {
920        if (!track.isQuickServiceEnabled()) {
921            if (Setup.isBuildOnTime()) {
922                addLine(THREE,
923                        Bundle.getMessage("buildTrackNotQuickService", StringUtils.capitalize(track.getTrackTypeName()),
924                                track.getLocation().getName(), track.getName(), engine.toString()));
925                // warn if departing staging that is quick serviced enabled
926                if (engine.getTrack().isStaging() && engine.getTrack().isQuickServiceEnabled()) {
927                    _warnings++;
928                    addLine(THREE,
929                            Bundle.getMessage("buildWarningQuickService", engine.toString(),
930                                    engine.getTrack().getTrackTypeName(),
931                                    engine.getTrack().getLocation().getName(), engine.getTrack().getName(),
932                                    getTrain().getName(),
933                                    StringUtils.capitalize(engine.getTrack().getTrackTypeName())));
934                }
935            }
936            return engine;
937        }
938        // quick service enabled, create clones
939        Engine cloneEng = engineManager.createClone(engine, track, getTrain(), getStartTime());
940        addLine(FIVE,
941                Bundle.getMessage("buildTrackQuickService", StringUtils.capitalize(track.getTrackTypeName()),
942                        track.getLocation().getName(), track.getName(), cloneEng.toString(), engine.toString()));
943        // remember where in the route the car was delivered
944        engine.setRouteDestination(rld);
945        return cloneEng; // return clone
946    }
947
948    int _hpAvailable = 0;
949    int _extraHpNeeded = 0;
950    RouteLocation _rlNeedHp;
951    RouteLocation _rlStart;
952
953    /**
954     * Checks to see if additional engines are needed for the train based on the
955     * train's calculated tonnage. Minimum speed for the train is fixed at 36
956     * MPH. The formula HPT x 12 / % Grade = Speed, is used to determine the
957     * horsepower needed. For example a 1% grade requires a minimum of 3 HPT.
958     * Ignored when departing staging. When using helpers, no additional
959     * engines.
960     *
961     * @throws BuildFailedException if build failure
962     */
963    protected void checkNumnberOfEnginesNeededHPT() throws BuildFailedException {
964        if (!getTrain().isBuildConsistEnabled() ||
965                Setup.getHorsePowerPerTon() == 0) {
966            return;
967        }
968        addLine(ONE, BLANK_LINE);
969        addLine(ONE, Bundle.getMessage("buildDetermineNeeds", Setup.getHorsePowerPerTon()));
970        Route route = getTrain().getRoute();
971        _rlStart = getTrain().getTrainDepartsRouteLocation();
972        boolean departingStaging = getTrain().isDepartingStaging();
973        if (route != null) {
974            boolean helper = false;
975            for (RouteLocation rl : route.getLocationsBySequenceList()) {
976                if ((getTrain().getSecondLegOptions() == Train.HELPER_ENGINES &&
977                        rl == getTrain().getSecondLegStartRouteLocation()) ||
978                        (getTrain().getThirdLegOptions() == Train.HELPER_ENGINES &&
979                                rl == getTrain().getThirdLegStartRouteLocation())) {
980                    addLine(FIVE, Bundle.getMessage("AddHelpersAt", rl.getName()));
981                    helper = true;
982                }
983                if ((getTrain().getSecondLegOptions() == Train.HELPER_ENGINES &&
984                        rl == getTrain().getSecondLegEndRouteLocation()) ||
985                        (getTrain().getThirdLegOptions() == Train.HELPER_ENGINES &&
986                                rl == getTrain().getThirdLegEndRouteLocation())) {
987                    addLine(FIVE,
988                            Bundle.getMessage("RemoveHelpersAt", rl.getName()));
989                    helper = false;
990                }
991                if (helper) {
992                    continue;
993                }
994                // check for a change of engines in the train's route
995                if (((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES &&
996                        rl == getTrain().getSecondLegStartRouteLocation()) ||
997                        ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES &&
998                                rl == getTrain().getThirdLegStartRouteLocation())) {
999                    log.debug("Loco change at ({})", rl.getName());
1000                    addEnginesBasedHPT(_hpAvailable, _extraHpNeeded, _rlNeedHp, _rlStart, rl);
1001                    // reset for next leg of train's route
1002                    _rlStart = rl;
1003                    _rlNeedHp = null;
1004                    _extraHpNeeded = 0;
1005                    departingStaging = false;
1006                }
1007                // check for add engines in the train's route
1008                if ((getTrain().getSecondLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES &&
1009                        rl == getTrain().getSecondLegStartRouteLocation()) {
1010                    RouteLocation rlEnd = getTrain().getSecondLegEndRouteLocation();
1011                    addEnginesIfNeed(route, rl, rlEnd);
1012                    departingStaging = false;
1013                }
1014                if ((getTrain().getThirdLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES &&
1015                        rl == getTrain().getThirdLegStartRouteLocation()) {
1016                    RouteLocation rlEnd = getTrain().getThirdLegEndRouteLocation();
1017                    addEnginesIfNeed(route, rl, rlEnd);
1018                    departingStaging = false;
1019                }
1020                if (departingStaging) {
1021                    continue;
1022                }
1023                determineMaxHpNeeded(rl);
1024            }
1025        }
1026        addEnginesBasedHPT(_hpAvailable, _extraHpNeeded, _rlNeedHp, _rlStart,
1027                getTrain().getTrainTerminatesRouteLocation());
1028        addLine(SEVEN, Bundle.getMessage("buildDoneAssingEnginesTrain", getTrain().getName()));
1029        addLine(THREE, BLANK_LINE);
1030    }
1031
1032    private void addEnginesIfNeed(Route route, RouteLocation rl, RouteLocation rlEnd) throws BuildFailedException {
1033        if (rlEnd == null) {
1034            rlEnd = getTrain().getTrainTerminatesRouteLocation();
1035        }
1036        determineMaxHpNeeded(route, rl, rlEnd);
1037        addEnginesBasedHPT(_hpAvailable, _extraHpNeeded, _rlNeedHp, rl, rlEnd);
1038        // reset for next leg of train's route
1039        _rlStart = rl;
1040        _rlNeedHp = null;
1041        _extraHpNeeded = 0;
1042    }
1043
1044    private void determineMaxHpNeeded(Route route, RouteLocation rl, RouteLocation rlEnd) {
1045        addLine(FIVE, BLANK_LINE);
1046        addLine(FIVE, Bundle.getMessage("buildAddEnginesHPT", rl.getName(), rlEnd.getName()));
1047        boolean foundStart = false;
1048        for (RouteLocation rlx : route.getLocationsBySequenceList()) {
1049            if (rlx == rl) {
1050                foundStart = true;
1051            }
1052            if (foundStart) {
1053                determineMaxHpNeeded(rlx);
1054                if (rlx == rlEnd) {
1055                    break;
1056                }
1057            }
1058        }
1059    }
1060
1061    private void determineMaxHpNeeded(RouteLocation rl) {
1062        double weight = rl.getTrainWeight();
1063        if (weight > 0) {
1064            double hptMinimum = Setup.getHorsePowerPerTon();
1065            double hptGrade = (Control.speedHpt * rl.getGrade() / 12);
1066            double hp = getTrain().getTrainHorsePower(rl);
1067            double hpt = hp / weight;
1068            if (hptGrade > hptMinimum) {
1069                hptMinimum = hptGrade;
1070            }
1071            if (hptMinimum > hpt) {
1072                int addHp = (int) (hptMinimum * weight - hp);
1073                if (addHp > _extraHpNeeded) {
1074                    _hpAvailable = (int) hp;
1075                    _extraHpNeeded = addHp;
1076                    _rlNeedHp = rl;
1077                }
1078                addLine(SEVEN,
1079                        Bundle.getMessage("buildAddLocosStatus", weight, hp, Control.speedHpt, rl.getGrade(),
1080                                hpt, hptMinimum, rl.getName(), rl.getId()));
1081                addLine(FIVE,
1082                        Bundle.getMessage("buildTrainRequiresAddHp", addHp, rl.getName(), hptMinimum));
1083            }
1084        }
1085    }
1086
1087    protected void removeEngineFromTrain(Engine engine) {
1088        // replace lead engine?
1089        if (getTrain().getLeadEngine() == engine) {
1090            getTrain().setLeadEngine(null);
1091        }
1092        if (engine.getConsist() != null) {
1093            for (Engine e : engine.getConsist().getEngines()) {
1094                removeRollingStockFromTrain(e);
1095            }
1096        } else {
1097            removeRollingStockFromTrain(engine);
1098        }
1099    }
1100
1101    private static final Logger log = LoggerFactory.getLogger(TrainBuilderEngines.class);
1102}