001package jmri.jmrit.operations.trains.trainbuilder;
002
003import java.util.ArrayList;
004import java.util.List;
005
006import org.apache.commons.lang3.StringUtils;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010import jmri.jmrit.operations.locations.Location;
011import jmri.jmrit.operations.locations.Track;
012import jmri.jmrit.operations.rollingstock.RollingStock;
013import jmri.jmrit.operations.rollingstock.engines.Engine;
014import jmri.jmrit.operations.routes.Route;
015import jmri.jmrit.operations.routes.RouteLocation;
016import jmri.jmrit.operations.setup.Control;
017import jmri.jmrit.operations.setup.Setup;
018import jmri.jmrit.operations.trains.BuildFailedException;
019import jmri.jmrit.operations.trains.Train;
020
021/**
022 * Contains methods for engines when building a train.
023 * 
024 * @author Daniel Boudreau Copyright (C) 2022
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 (int indexEng = 0; indexEng < getEngineList().size(); indexEng++) {
186            Engine engine = getEngineList().get(indexEng);
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                getEngineList().remove(indexEng--);
244                if (engine.getConsist() != null) {
245                    assignedLocos = assignedLocos + engine.getConsist().getSize();
246                } else {
247                    assignedLocos++;
248                }
249                continue;
250            }
251            // can't use B units if requesting one loco
252            if (!useBunit && reqNumberEngines == 1 && engine.isBunit()) {
253                addLine(SEVEN,
254                        Bundle.getMessage("buildExcludeEngineBunit", engine.toString(), engine.getModel()));
255                continue;
256            }
257            // is this engine part of a consist?
258            if (engine.getConsist() == null) {
259                // single engine, but does the train require a consist?
260                if (reqNumberEngines > 1) {
261                    addLine(SEVEN,
262                            Bundle.getMessage("buildExcludeEngineSingle", engine.toString(), reqNumberEngines));
263                    singleLocos.add(engine);
264                    continue;
265                }
266                // engine is part of a consist
267            } else if (engine.getConsist().getSize() == reqNumberEngines) {
268                log.debug("Consist ({}) has the required number of engines", engine.getConsist().getName()); // NOI18N
269            } else if (reqNumberEngines != 0) {
270                addLine(SEVEN,
271                        Bundle.getMessage("buildExcludeEngConsistNumber", engine.toString(),
272                                engine.getConsist().getName(), engine.getConsist().getSize()));
273                continue;
274            }
275            // found a loco or consist!
276            assignedLocos++;
277
278            // now find terminal track for engine(s)
279            addLine(FIVE,
280                    Bundle.getMessage("buildEngineRoadModelType", engine.toString(), engine.getRoadName(),
281                            engine.getModel(), engine.getTypeName(), engine.getLocationName(), engine.getTrackName(),
282                            rld.getName()));
283            if (setEngineDestination(engine, rl, rld)) {
284                getEngineList().remove(indexEng--);
285                return true; // normal exit when not staging
286            }
287        }
288        // build a consist out of non-consisted locos
289        if (assignedLocos == 0 && reqNumberEngines > 1 && getTrain().isBuildConsistEnabled()) {
290            if (buildConsistFromSingleLocos(reqNumberEngines, singleLocos, rl, rld)) {
291                return true; // normal exit when building with single locos
292            }
293        }
294        if (assignedLocos == 0) {
295            String locationName = rl.getName();
296            if (departStagingTrack != null) {
297                locationName = locationName + ", " + departStagingTrack.getName();
298            }
299            addLine(FIVE, Bundle.getMessage("buildNoLocosFoundAtLocation", locationName));
300        } else if (departStagingTrack != null && (reqNumberEngines == 0 || reqNumberEngines == assignedLocos)) {
301            return true; // normal exit assigning from staging
302        }
303        // not able to assign engines to train
304        return false;
305    }
306
307    private boolean buildConsistFromSingleLocos(int reqNumberEngines, List<Engine> singleLocos, RouteLocation rl,
308            RouteLocation rld) {
309        addLine(FIVE, Bundle.getMessage("buildOptionBuildConsist", reqNumberEngines, rl.getName()));
310        addLine(FIVE, Bundle.getMessage("buildOptionSingleLocos", singleLocos.size(), rl.getName()));
311        if (singleLocos.size() >= reqNumberEngines) {
312            int locos = 0;
313            // first find an "A" unit
314            for (Engine engine : singleLocos) {
315                if (engine.isBunit()) {
316                    continue;
317                }
318                if (setEngineDestination(engine, rl, rld)) {
319                    getEngineList().remove(engine);
320                    singleLocos.remove(engine);
321                    locos++;
322                    break; // found "A" unit
323                }
324            }
325            // did we find an "A" unit?
326            if (locos > 0) {
327                // now add the rest "A" or "B" units
328                for (Engine engine : singleLocos) {
329                    if (setEngineDestination(engine, rl, rld)) {
330                        getEngineList().remove(engine);
331                        locos++;
332                    }
333                    if (locos == reqNumberEngines) {
334                        return true; // done!
335                    }
336                }
337            } else {
338                // list the "B" units found
339                for (Engine engine : singleLocos) {
340                    if (engine.isBunit()) {
341                        addLine(FIVE,
342                                Bundle.getMessage("BuildEngineBunit", engine.toString(), engine.getLocationName(),
343                                        engine.getTrackName()));
344                    }
345                }
346            }
347        }
348        return false;
349    }
350
351    /**
352     * Adds engines to the train starting at the first location in the train's
353     * route. Note that engines from staging are already part of the train.
354     * There can be up to two engine swaps in a train's route.
355     * 
356     * @throws BuildFailedException if required engines can't be added to train.
357     */
358    protected void addEnginesToTrain() throws BuildFailedException {
359        // allow up to two engine and caboose swaps in the train's route
360        RouteLocation engineTerminatesFirstLeg = getTrain().getTrainTerminatesRouteLocation();
361        RouteLocation engineTerminatesSecondLeg = getTrain().getTrainTerminatesRouteLocation();
362        RouteLocation engineTerminatesThirdLeg = getTrain().getTrainTerminatesRouteLocation();
363
364        // Adjust where the locos will terminate
365        if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES &&
366                getTrain().getSecondLegStartRouteLocation() != null) {
367            engineTerminatesFirstLeg = getTrain().getSecondLegStartRouteLocation();
368        }
369        if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES &&
370                getTrain().getThirdLegStartRouteLocation() != null) {
371            engineTerminatesSecondLeg = getTrain().getThirdLegStartRouteLocation();
372            // No engine or caboose change at first leg?
373            if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) != Train.CHANGE_ENGINES) {
374                engineTerminatesFirstLeg = getTrain().getThirdLegStartRouteLocation();
375            }
376        }
377        // optionally set out added engines
378        if (getTrain().getSecondLegEndRouteLocation() != null &&
379                ((getTrain().getSecondLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES ||
380                        (getTrain().getSecondLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES)) {
381            engineTerminatesSecondLeg = getTrain().getSecondLegEndRouteLocation();
382        }
383        if (getTrain().getThirdLegEndRouteLocation() != null &&
384                ((getTrain().getThirdLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES ||
385                        (getTrain().getThirdLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES)) {
386            engineTerminatesThirdLeg = getTrain().getThirdLegEndRouteLocation();
387        }
388
389        if (getTrain().getLeadEngine() == null) {
390            // option to remove locos from the train
391            if ((getTrain().getSecondLegOptions() & Train.REMOVE_ENGINES) == Train.REMOVE_ENGINES &&
392                    getTrain().getSecondLegStartRouteLocation() != null) {
393                addLine(THREE, BLANK_LINE);
394                addLine(THREE,
395                        Bundle.getMessage("buildTrainRemoveEngines", getTrain().getSecondLegNumberEngines(),
396                                getTrain().getSecondLegStartLocationName(), getTrain().getSecondLegEngineModel(),
397                                getTrain().getSecondLegEngineRoad()));
398                if (getEngines(getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(),
399                        getTrain().getSecondLegEngineRoad(), getTrain().getTrainDepartsRouteLocation(),
400                        getTrain().getSecondLegStartRouteLocation())) {
401                } else if (getConsist(getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(),
402                        getTrain().getSecondLegEngineRoad(), getTrain().getTrainDepartsRouteLocation(),
403                        getTrain().getSecondLegStartRouteLocation())) {
404                } else {
405                    throw new BuildFailedException(Bundle.getMessage("buildErrorEngines",
406                            getTrain().getSecondLegNumberEngines(), getTrain().getTrainDepartsName(),
407                            getTrain().getSecondLegStartRouteLocation().getLocation().getName()));
408                }
409            }
410            if ((getTrain().getThirdLegOptions() & Train.REMOVE_ENGINES) == Train.REMOVE_ENGINES &&
411                    getTrain().getThirdLegStartRouteLocation() != null) {
412                addLine(THREE, BLANK_LINE);
413                addLine(THREE,
414                        Bundle.getMessage("buildTrainRemoveEngines", getTrain().getThirdLegNumberEngines(),
415                                getTrain().getThirdLegStartLocationName(), getTrain().getThirdLegEngineModel(),
416                                getTrain().getThirdLegEngineRoad()));
417                if (getEngines(getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(),
418                        getTrain().getThirdLegEngineRoad(), getTrain().getTrainDepartsRouteLocation(),
419                        getTrain().getThirdLegStartRouteLocation())) {
420                } else if (getConsist(getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(),
421                        getTrain().getThirdLegEngineRoad(), getTrain().getTrainDepartsRouteLocation(),
422                        getTrain().getThirdLegStartRouteLocation())) {
423                } else {
424                    throw new BuildFailedException(Bundle.getMessage("buildErrorEngines",
425                            getTrain().getThirdLegNumberEngines(), getTrain().getTrainDepartsName(),
426                            getTrain().getThirdLegStartRouteLocation().getLocation().getName()));
427                }
428            }
429            // load engines at the start of the route for this train
430            if (getEngines(getTrain().getNumberEngines(), getTrain().getEngineModel(), getTrain().getEngineRoad(),
431                    getTrain().getTrainDepartsRouteLocation(), engineTerminatesFirstLeg)) {
432                // when adding a caboose later in the route, no engine change
433                _secondLeadEngine = _lastEngine;
434                _thirdLeadEngine = _lastEngine;
435            } else if (getConsist(getTrain().getNumberEngines(), getTrain().getEngineModel(),
436                    getTrain().getEngineRoad(),
437                    getTrain().getTrainDepartsRouteLocation(), engineTerminatesFirstLeg)) {
438                // when adding a caboose later in the route, no engine change
439                _secondLeadEngine = _lastEngine;
440                _thirdLeadEngine = _lastEngine;
441            } else {
442                addLine(THREE, BLANK_LINE);
443                throw new BuildFailedException(Bundle.getMessage("buildErrorEngines", getTrain().getNumberEngines(),
444                        getTrain().getTrainDepartsName(), engineTerminatesFirstLeg.getName()));
445            }
446        }
447
448        // First engine change in route?
449        if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES ||
450                (getTrain().getSecondLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES ||
451                (getTrain().getSecondLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES) {
452            addLine(THREE, BLANK_LINE);
453            if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES) {
454                addLine(THREE,
455                        Bundle.getMessage("buildTrainEngineChange", getTrain().getSecondLegStartLocationName(),
456                                getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(),
457                                getTrain().getSecondLegEngineRoad()));
458            } else {
459                addLine(THREE,
460                        Bundle.getMessage("buildTrainAddEngines", getTrain().getSecondLegNumberEngines(),
461                                getTrain().getSecondLegStartLocationName(), getTrain().getSecondLegEngineModel(),
462                                getTrain().getSecondLegEngineRoad()));
463            }
464            if (getEngines(getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(),
465                    getTrain().getSecondLegEngineRoad(), getTrain().getSecondLegStartRouteLocation(),
466                    engineTerminatesSecondLeg)) {
467                _secondLeadEngine = _lastEngine;
468                _thirdLeadEngine = _lastEngine;
469            } else if (getConsist(getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(),
470                    getTrain().getSecondLegEngineRoad(), getTrain().getSecondLegStartRouteLocation(),
471                    engineTerminatesSecondLeg)) {
472                _secondLeadEngine = _lastEngine;
473                _thirdLeadEngine = _lastEngine;
474            } else {
475                throw new BuildFailedException(
476                        Bundle.getMessage("buildErrorEngines", getTrain().getSecondLegNumberEngines(),
477                                getTrain().getSecondLegStartRouteLocation(), engineTerminatesSecondLeg));
478            }
479        }
480        // Second engine change in route?
481        if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES ||
482                (getTrain().getThirdLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES ||
483                (getTrain().getThirdLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES) {
484            addLine(THREE, BLANK_LINE);
485            if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES) {
486                addLine(THREE,
487                        Bundle.getMessage("buildTrainEngineChange", getTrain().getThirdLegStartLocationName(),
488                                getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(),
489                                getTrain().getThirdLegEngineRoad()));
490            } else {
491                addLine(THREE,
492                        Bundle.getMessage("buildTrainAddEngines", getTrain().getThirdLegNumberEngines(),
493                                getTrain().getThirdLegStartLocationName(), getTrain().getThirdLegEngineModel(),
494                                getTrain().getThirdLegEngineRoad()));
495            }
496            if (getEngines(getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(),
497                    getTrain().getThirdLegEngineRoad(), getTrain().getThirdLegStartRouteLocation(),
498                    engineTerminatesThirdLeg)) {
499                _thirdLeadEngine = _lastEngine;
500            } else if (getConsist(getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(),
501                    getTrain().getThirdLegEngineRoad(), getTrain().getThirdLegStartRouteLocation(),
502                    engineTerminatesThirdLeg)) {
503                _thirdLeadEngine = _lastEngine;
504            } else {
505                throw new BuildFailedException(
506                        Bundle.getMessage("buildErrorEngines", Integer.parseInt(getTrain().getThirdLegNumberEngines()),
507                                getTrain().getThirdLegStartRouteLocation(),
508                                getTrain().getTrainTerminatesRouteLocation()));
509            }
510        }
511        if (!getTrain().getNumberEngines().equals("0") &&
512                (!getTrain().isBuildConsistEnabled() || Setup.getHorsePowerPerTon() == 0)) {
513            addLine(SEVEN, Bundle.getMessage("buildDoneAssingEnginesTrain", getTrain().getName()));
514        }
515    }
516
517    protected boolean getConsist(String reqNumEngines, String model, String road, RouteLocation rl, RouteLocation rld)
518            throws BuildFailedException {
519        if (reqNumEngines.equals(Train.AUTO_HPT)) {
520            for (int i = 2; i < Setup.getMaxNumberEngines(); i++) {
521                if (getEngines(Integer.toString(i), model, road, rl, rld)) {
522                    return true;
523                }
524            }
525        }
526        return false;
527    }
528
529    protected void showEnginesByLocation() {
530        // show how many engines were found
531        addLine(SEVEN, BLANK_LINE);
532        addLine(ONE,
533                Bundle.getMessage("buildFoundLocos", Integer.toString(getEngineList().size()), getTrain().getName()));
534
535        // only show engines once using the train's route
536        List<String> locationNames = new ArrayList<>();
537        for (RouteLocation rl : getTrain().getRoute().getLocationsBySequenceList()) {
538            if (locationNames.contains(rl.getName())) {
539                continue;
540            }
541            locationNames.add(rl.getName());
542            int count = countRollingStockAt(rl, new ArrayList<RollingStock>(getEngineList()));
543            if (rl.getLocation().isStaging()) {
544                addLine(FIVE,
545                        Bundle.getMessage("buildLocosInStaging", count, rl.getName()));
546            } else {
547                addLine(FIVE,
548                        Bundle.getMessage("buildLocosAtLocation", count, rl.getName()));
549            }
550            for (Engine engine : getEngineList()) {
551                if (engine.getLocationName().equals(rl.getName())) {
552                    addLine(SEVEN,
553                            Bundle.getMessage("buildLocoAtLocWithMoves", engine.toString(), engine.getTypeName(),
554                                    engine.getModel(), engine.getLocationName(), engine.getTrackName(),
555                                    engine.getMoves()));
556                }
557            }
558            addLine(SEVEN, BLANK_LINE);
559        }
560    }
561
562    /**
563     * Adds engines to the train if needed based on HPT. Note that the engine
564     * additional weight isn't considered in this method so HP requirements can
565     * be lower compared to the original calculation which did include the
566     * weight of the engines.
567     *
568     * @param hpAvailable   the engine hp already assigned to the train for this
569     *                      leg
570     * @param extraHpNeeded the additional hp needed
571     * @param rlNeedHp      where in the route the additional hp is needed
572     * @param rl            the start of the leg
573     * @param rld           the end of the leg
574     * @throws BuildFailedException if unable to add engines to train
575     */
576    protected void addEnginesBasedHPT(int hpAvailable, int extraHpNeeded, RouteLocation rlNeedHp, RouteLocation rl,
577            RouteLocation rld) throws BuildFailedException {
578        if (rlNeedHp == null) {
579            return;
580        }
581        
582        // determine how many locos have already been assigned to the train
583        int numberLocos = getTrain().getNumberEngines(rl);
584
585        addLine(ONE, BLANK_LINE);
586        addLine(ONE, Bundle.getMessage("buildTrainReqExtraHp", extraHpNeeded, rlNeedHp.getName(),
587                rld.getName(), numberLocos));
588
589        // determine engine model and road
590        String model = getTrain().getEngineModel();
591        String road = getTrain().getEngineRoad();
592        if (rl == getTrain().getSecondLegStartRouteLocation() &&
593                ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES ||
594                        (getTrain().getSecondLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES)) {
595            model = getTrain().getSecondLegEngineModel();
596            road = getTrain().getSecondLegEngineRoad();
597        } else if (rl == getTrain().getThirdLegStartRouteLocation() &&
598                ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES ||
599                        (getTrain().getThirdLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES)) {
600            model = getTrain().getThirdLegEngineModel();
601            road = getTrain().getThirdLegEngineRoad();
602        }
603
604        while (numberLocos < Setup.getMaxNumberEngines()) {
605            // if no engines assigned, can't use B unit as first engine
606            if (!getEngines("1", model, road, rl, rld, numberLocos > 0)) {
607                throw new BuildFailedException(Bundle.getMessage("buildErrorEngines", Bundle.getMessage("additional"),
608                        rl.getName(), rld.getName()));
609            }
610            numberLocos++;
611            int currentHp = getTrain().getTrainHorsePower(rlNeedHp);
612            if (currentHp >= hpAvailable + extraHpNeeded) {
613                break; // done
614            }
615            if (numberLocos < Setup.getMaxNumberEngines()) {
616                addLine(FIVE, BLANK_LINE);
617                addLine(THREE,
618                        Bundle.getMessage("buildContinueAddLocos", (hpAvailable + extraHpNeeded - currentHp),
619                                rlNeedHp.getName(), rld.getName(), numberLocos, currentHp));
620            } else {
621                addLine(FIVE,
622                        Bundle.getMessage("buildMaxNumberLocoAssigned", Setup.getMaxNumberEngines()));
623            }
624        }
625    }
626
627    /**
628     * Checks to see if the engine or consist assigned to the train has the
629     * appropriate HP. If the train's HP requirements are significantly higher
630     * or lower than the engine that was assigned, the program will search for a
631     * more appropriate engine or consist, and assign that engine or consist to
632     * the train. The HP calculation is based on a minimum train speed of 36
633     * MPH. The formula HPT x 12 / % Grade = Speed, is used to determine the
634     * horsepower required. Speed is fixed at 36 MPH. For example a 1% grade
635     * requires a minimum of 3 HPT. Disabled for trains departing staging.
636     * 
637     * @throws BuildFailedException if coding error.
638     */
639    protected void checkEngineHP() throws BuildFailedException {
640        if (Setup.getHorsePowerPerTon() != 0) {
641            if (getTrain().getNumberEngines().equals(Train.AUTO_HPT)) {
642                checkEngineHP(getTrain().getLeadEngine(), getTrain().getEngineModel(), getTrain().getEngineRoad());
643            }
644            if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES &&
645                    getTrain().getSecondLegNumberEngines().equals(Train.AUTO_HPT)) {
646                checkEngineHP(_secondLeadEngine, getTrain().getSecondLegEngineModel(),
647                        getTrain().getSecondLegEngineRoad());
648            }
649            if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES &&
650                    getTrain().getThirdLegNumberEngines().equals(Train.AUTO_HPT)) {
651                checkEngineHP(_thirdLeadEngine, getTrain().getThirdLegEngineModel(),
652                        getTrain().getThirdLegEngineRoad());
653            }
654        }
655    }
656
657    private void checkEngineHP(Engine leadEngine, String model, String road) throws BuildFailedException {
658        // code check
659        if (leadEngine == null) {
660            throw new BuildFailedException("ERROR coding issue, engine missing from checkEngineHP()");
661        }
662        // departing staging?
663        if (leadEngine.getRouteLocation() == getTrain().getTrainDepartsRouteLocation() &&
664                getTrain().isDepartingStaging()) {
665            return;
666        }
667        addLine(ONE, BLANK_LINE);
668        addLine(ONE,
669                Bundle.getMessage("buildDetermineHpNeeded", leadEngine.toString(), leadEngine.getLocationName(),
670                        leadEngine.getDestinationName(), getTrain().getTrainHorsePower(leadEngine.getRouteLocation()),
671                        Setup.getHorsePowerPerTon()));
672        // now determine the HP needed for this train
673        double hpNeeded = 0;
674        int hpAvailable = 0;
675        Route route = getTrain().getRoute();
676        if (route != null) {
677            boolean helper = false;
678            boolean foundStart = false;
679            for (RouteLocation rl : route.getLocationsBySequenceList()) {
680                if (!foundStart && rl != leadEngine.getRouteLocation()) {
681                    continue;
682                }
683                foundStart = true;
684                if ((getTrain().getSecondLegOptions() == Train.HELPER_ENGINES &&
685                        rl == getTrain().getSecondLegStartRouteLocation()) ||
686                        (getTrain().getThirdLegOptions() == Train.HELPER_ENGINES &&
687                                rl == getTrain().getThirdLegStartRouteLocation())) {
688                    addLine(FIVE,
689                            Bundle.getMessage("AddHelpersAt", rl.getName()));
690                    helper = true;
691                }
692                if ((getTrain().getSecondLegOptions() == Train.HELPER_ENGINES &&
693                        rl == getTrain().getSecondLegEndRouteLocation()) ||
694                        (getTrain().getThirdLegOptions() == Train.HELPER_ENGINES &&
695                                rl == getTrain().getThirdLegEndRouteLocation())) {
696                    addLine(FIVE,
697                            Bundle.getMessage("RemoveHelpersAt", rl.getName()));
698                    helper = false;
699                }
700                if (helper) {
701                    continue; // ignore HP needed when helpers are assigned to
702                              // the train
703                }
704                // check for a change of engines in the train's route
705                if (rl == leadEngine.getRouteDestination()) {
706                    log.debug("Remove loco ({}) at ({})", leadEngine.toString(), rl.getName());
707                    break; // done
708                }
709                if (getTrain().getTrainHorsePower(rl) > hpAvailable)
710                    hpAvailable = getTrain().getTrainHorsePower(rl);
711                int weight = rl.getTrainWeight();
712                double hpRequired = (Control.speedHpt * rl.getGrade() / 12) * weight;
713                if (hpRequired < Setup.getHorsePowerPerTon() * weight)
714                    hpRequired = Setup.getHorsePowerPerTon() * weight; // min HPT
715                if (hpRequired > hpNeeded) {
716                    addLine(SEVEN,
717                            Bundle.getMessage("buildReportTrainHpNeeds", weight, getTrain().getNumberCarsInTrain(rl),
718                                    rl.getGrade(), rl.getName(), rl.getId(), hpRequired));
719                    hpNeeded = hpRequired;
720                }
721            }
722        }
723        if (hpNeeded > hpAvailable) {
724            addLine(ONE,
725                    Bundle.getMessage("buildAssignedHpNotEnough", leadEngine.toString(), hpAvailable, hpNeeded));
726            getNewEngine((int) hpNeeded, leadEngine, model, road);
727        } else if (hpAvailable > 2 * hpNeeded) {
728            addLine(ONE,
729                    Bundle.getMessage("buildAssignedHpTooMuch", leadEngine.toString(), hpAvailable, hpNeeded));
730            getNewEngine((int) hpNeeded, leadEngine, model, road);
731        } else {
732            log.debug("Keeping engine ({}) it meets the train's HP requirement", leadEngine.toString());
733        }
734    }
735
736    /**
737     * Removes engine from train and attempts to replace it with engine or
738     * consist that meets the HP requirements of the train.
739     *
740     * @param hpNeeded   How much hp is needed
741     * @param leadEngine The lead engine for this leg
742     * @param model      The engine's model
743     * @param road       The engine's road
744     * @throws BuildFailedException if new engine not found
745     */
746    protected void getNewEngine(int hpNeeded, Engine leadEngine, String model, String road)
747            throws BuildFailedException {
748        // save lead engine's rl, and rld
749        RouteLocation rl = leadEngine.getRouteLocation();
750        RouteLocation rld = leadEngine.getRouteDestination();
751        removeEngineFromTrain(leadEngine);
752        getEngineList().add(0, leadEngine); // put engine back into the pool
753        if (hpNeeded < 50) {
754            hpNeeded = 50; // the minimum HP
755        }
756        int hpMax = hpNeeded;
757        // largest single engine HP known today is less than 15,000.
758        // high end modern diesel locos approximately 5000 HP.
759        // 100 car train at 100 tons per car and 2 HPT requires 20,000 HP.
760        // will assign consisted engines to train.
761        boolean foundLoco = false;
762        List<Engine> rejectedLocos = new ArrayList<>();
763        hpLoop: while (hpMax < 20000) {
764            hpMax += hpNeeded / 2; // start off looking for an engine with no
765                                   // more than 50% extra HP
766            log.debug("Max hp {}", hpMax);
767            for (Engine engine : getEngineList()) {
768                if (rejectedLocos.contains(engine)) {
769                    continue;
770                }
771                // don't use non lead locos in a consist
772                if (engine.getConsist() != null && !engine.isLead()) {
773                    continue;
774                }
775                if (engine.getLocation() != rl.getLocation()) {
776                    continue;
777                }
778                if (!model.equals(Train.NONE) && !engine.getModel().equals(model)) {
779                    continue;
780                }
781                if (!road.equals(Train.NONE) && !engine.getRoadName().equals(road) ||
782                        road.equals(Train.NONE) && !getTrain().isLocoRoadNameAccepted(engine.getRoadName())) {
783                    continue;
784                }
785                int engineHp = engine.getHpInteger();
786                if (engine.getConsist() != null) {
787                    for (Engine e : engine.getConsist().getEngines()) {
788                        if (e != engine) {
789                            engineHp = engineHp + e.getHpInteger();
790                        }
791                    }
792                }
793                if (engineHp > hpNeeded && engineHp <= hpMax) {
794                    addLine(FIVE,
795                            Bundle.getMessage("buildLocoHasRequiredHp", engine.toString(), engineHp, hpNeeded));
796                    if (setEngineDestination(engine, rl, rld)) {
797                        foundLoco = true;
798                        break hpLoop;
799                    } else {
800                        rejectedLocos.add(engine);
801                    }
802                }
803            }
804        }
805        if (!foundLoco && !getTrain().isBuildConsistEnabled()) {
806            throw new BuildFailedException(Bundle.getMessage("buildErrorEngHp", rl.getLocation().getName()));
807        }
808    }
809
810    /**
811     * Sets the destination track for an engine and assigns it to the train.
812     *
813     * @param engine The engine to be added to train
814     * @param rl     Departure route location
815     * @param rld    Destination route location
816     * @return true if destination track found and set
817     */
818    protected boolean setEngineDestination(Engine engine, RouteLocation rl, RouteLocation rld) {
819        // engine to staging?
820        if (rld == getTrain().getTrainTerminatesRouteLocation() && getTerminateStagingTrack() != null) {
821            String status =
822                    engine.checkDestination(getTerminateStagingTrack().getLocation(), getTerminateStagingTrack());
823            if (status.equals(Track.OKAY)) {
824                addEngineToTrain(engine, rl, rld, getTerminateStagingTrack());
825                return true; // done
826            } else {
827                addLine(SEVEN,
828                        Bundle.getMessage("buildCanNotDropEngineToTrack", engine.toString(),
829                                getTerminateStagingTrack().getTrackTypeName(),
830                                getTerminateStagingTrack().getLocation().getName(),
831                                getTerminateStagingTrack().getName(), status));
832            }
833        } else {
834            // find a destination track for this engine
835            Location destination = rld.getLocation();
836            List<Track> destTracks = destination.getTracksByMoves(null);
837            if (destTracks.size() == 0) {
838                addLine(THREE, Bundle.getMessage("buildNoTracksAtDestination", rld.getName()));
839            }
840            for (Track track : destTracks) {
841                if (!checkDropTrainDirection(engine, rld, track)) {
842                    continue;
843                }
844                if (!checkTrainCanDrop(engine, track)) {
845                    continue;
846                }
847                String status = engine.checkDestination(destination, track);
848                if (status.equals(Track.OKAY)) {
849                    addLine(FIVE,
850                            Bundle.getMessage("buildEngineCanDrop", engine.toString(),
851                                    track.getTrackTypeName(),
852                                    track.getLocation().getName(), track.getName()));
853                    addEngineToTrain(engine, rl, rld, track);
854                    return true;
855                } else {
856                    addLine(SEVEN,
857                            Bundle.getMessage("buildCanNotDropEngineToTrack", engine.toString(),
858                                    track.getTrackTypeName(),
859                                    track.getLocation().getName(), track.getName(), status));
860                }
861            }
862            addLine(FIVE,
863                    Bundle.getMessage("buildCanNotDropEngToDest", engine.toString(), rld.getName()));
864        }
865        return false; // not able to set loco's destination
866    }
867
868    /**
869     * Adds an engine to the train.
870     *
871     * @param engine the engine being added to the train
872     * @param rl     where in the train's route to pick up the engine
873     * @param rld    where in the train's route to set out the engine
874     * @param track  the destination track for this engine
875     */
876    private void addEngineToTrain(Engine engine, RouteLocation rl, RouteLocation rld, Track track) {
877        _lastEngine = engine; // needed in case there's a engine change in the
878                              // train's route
879        engine = checkQuickServiceArrival(engine, rld, track);
880        if (getTrain().getLeadEngine() == null) {
881            getTrain().setLeadEngine(engine); // load lead engine
882        }
883        addLine(ONE, Bundle.getMessage("buildEngineAssigned", engine.toString(), rl.getName(),
884                rld.getName(), track.getName()));
885        engine.setDestination(track.getLocation(), track, Engine.FORCE);
886        int length = engine.getTotalLength();
887        int weightTons = engine.getAdjustedWeightTons();
888        // engine in consist?
889        if (engine.getConsist() != null) {
890            length = engine.getConsist().getTotalLength();
891            weightTons = engine.getConsist().getAdjustedWeightTons();
892            for (Engine cEngine : engine.getConsist().getEngines()) {
893                if (cEngine != engine) {
894                    addLine(ONE, Bundle.getMessage("buildEngineAssigned", cEngine.toString(),
895                            rl.getName(), rld.getName(), track.getName()));
896                    cEngine.setTrain(getTrain());
897                    cEngine.setRouteLocation(rl);
898                    cEngine.setRouteDestination(rld);
899                    cEngine.setDestination(track.getLocation(), track, RollingStock.FORCE); // force
900                }
901            }
902        }
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        // for timing, use arrival times for the train that is building
944        // other trains will use their departure time, loaded when creating the Manifest
945        String expectedArrivalTime = getTrain().getExpectedArrivalTime(rld, true);
946        cloneEng.setSetoutTime(expectedArrivalTime);
947        // remember where in the route the car was delivered
948        engine.setRouteDestination(rld);
949        return cloneEng; // return clone
950    }
951
952    int _hpAvailable = 0;
953    int _extraHpNeeded = 0;
954    RouteLocation _rlNeedHp;
955    RouteLocation _rlStart;
956
957    /**
958     * Checks to see if additional engines are needed for the train based on the
959     * train's calculated tonnage. Minimum speed for the train is fixed at 36
960     * MPH. The formula HPT x 12 / % Grade = Speed, is used to determine the
961     * horsepower needed. For example a 1% grade requires a minimum of 3 HPT.
962     * Ignored when departing staging. When using helpers, no additional
963     * engines.
964     *
965     * @throws BuildFailedException if build failure
966     */
967    protected void checkNumnberOfEnginesNeededHPT() throws BuildFailedException {
968        if (!getTrain().isBuildConsistEnabled() ||
969                Setup.getHorsePowerPerTon() == 0) {
970            return;
971        }
972        addLine(ONE, BLANK_LINE);
973        addLine(ONE, Bundle.getMessage("buildDetermineNeeds", Setup.getHorsePowerPerTon()));
974        Route route = getTrain().getRoute();
975        _rlStart = getTrain().getTrainDepartsRouteLocation();
976        boolean departingStaging = getTrain().isDepartingStaging();
977        if (route != null) {
978            boolean helper = false;
979            for (RouteLocation rl : route.getLocationsBySequenceList()) {
980                if ((getTrain().getSecondLegOptions() == Train.HELPER_ENGINES &&
981                        rl == getTrain().getSecondLegStartRouteLocation()) ||
982                        (getTrain().getThirdLegOptions() == Train.HELPER_ENGINES &&
983                                rl == getTrain().getThirdLegStartRouteLocation())) {
984                    addLine(FIVE, Bundle.getMessage("AddHelpersAt", rl.getName()));
985                    helper = true;
986                }
987                if ((getTrain().getSecondLegOptions() == Train.HELPER_ENGINES &&
988                        rl == getTrain().getSecondLegEndRouteLocation()) ||
989                        (getTrain().getThirdLegOptions() == Train.HELPER_ENGINES &&
990                                rl == getTrain().getThirdLegEndRouteLocation())) {
991                    addLine(FIVE,
992                            Bundle.getMessage("RemoveHelpersAt", rl.getName()));
993                    helper = false;
994                }
995                if (helper) {
996                    continue;
997                }
998                // check for a change of engines in the train's route
999                if (((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES &&
1000                        rl == getTrain().getSecondLegStartRouteLocation()) ||
1001                        ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES &&
1002                                rl == getTrain().getThirdLegStartRouteLocation())) {
1003                    log.debug("Loco change at ({})", rl.getName());
1004                    addEnginesBasedHPT(_hpAvailable, _extraHpNeeded, _rlNeedHp, _rlStart, rl);
1005                    // reset for next leg of train's route
1006                    _rlStart = rl;
1007                    _rlNeedHp = null;
1008                    _extraHpNeeded = 0;
1009                    departingStaging = false;
1010                }
1011                // check for add engines in the train's route
1012                if ((getTrain().getSecondLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES &&
1013                        rl == getTrain().getSecondLegStartRouteLocation()) {
1014                    RouteLocation rlEnd = getTrain().getSecondLegEndRouteLocation();
1015                    addEnginesIfNeed(route, rl, rlEnd);
1016                    departingStaging = false;
1017                }
1018                if ((getTrain().getThirdLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES &&
1019                        rl == getTrain().getThirdLegStartRouteLocation()) {
1020                    RouteLocation rlEnd = getTrain().getThirdLegEndRouteLocation();
1021                    addEnginesIfNeed(route, rl, rlEnd);
1022                    departingStaging = false;
1023                }
1024                if (departingStaging) {
1025                    continue;
1026                }
1027                determineMaxHpNeeded(rl);
1028            }
1029        }
1030        addEnginesBasedHPT(_hpAvailable, _extraHpNeeded, _rlNeedHp, _rlStart,
1031                getTrain().getTrainTerminatesRouteLocation());
1032        addLine(SEVEN, Bundle.getMessage("buildDoneAssingEnginesTrain", getTrain().getName()));
1033        addLine(THREE, BLANK_LINE);
1034    }
1035
1036    private void addEnginesIfNeed(Route route, RouteLocation rl, RouteLocation rlEnd) throws BuildFailedException {
1037        if (rlEnd == null) {
1038            rlEnd = getTrain().getTrainTerminatesRouteLocation();
1039        }
1040        determineMaxHpNeeded(route, rl, rlEnd);
1041        addEnginesBasedHPT(_hpAvailable, _extraHpNeeded, _rlNeedHp, rl, rlEnd);
1042        // reset for next leg of train's route
1043        _rlStart = rl;
1044        _rlNeedHp = null;
1045        _extraHpNeeded = 0;
1046    }
1047
1048    private void determineMaxHpNeeded(Route route, RouteLocation rl, RouteLocation rlEnd) {
1049        addLine(FIVE, BLANK_LINE);
1050        addLine(FIVE, Bundle.getMessage("buildAddEnginesHPT", rl.getName(), rlEnd.getName()));
1051        boolean foundStart = false;
1052        for (RouteLocation rlx : route.getLocationsBySequenceList()) {
1053            if (rlx == rl) {
1054                foundStart = true;
1055            }
1056            if (foundStart) {
1057                determineMaxHpNeeded(rlx);
1058                if (rlx == rlEnd) {
1059                    break;
1060                }
1061            }
1062        }
1063    }
1064
1065    private void determineMaxHpNeeded(RouteLocation rl) {
1066        double weight = rl.getTrainWeight();
1067        if (weight > 0) {
1068            double hptMinimum = Setup.getHorsePowerPerTon();
1069            double hptGrade = (Control.speedHpt * rl.getGrade() / 12);
1070            double hp = getTrain().getTrainHorsePower(rl);
1071            double hpt = hp / weight;
1072            if (hptGrade > hptMinimum) {
1073                hptMinimum = hptGrade;
1074            }
1075            if (hptMinimum > hpt) {
1076                int addHp = (int) (hptMinimum * weight - hp);
1077                if (addHp > _extraHpNeeded) {
1078                    _hpAvailable = (int) hp;
1079                    _extraHpNeeded = addHp;
1080                    _rlNeedHp = rl;
1081                }
1082                addLine(SEVEN,
1083                        Bundle.getMessage("buildAddLocosStatus", weight, hp, Control.speedHpt, rl.getGrade(),
1084                                hpt, hptMinimum, rl.getName(), rl.getId()));
1085                addLine(FIVE,
1086                        Bundle.getMessage("buildTrainRequiresAddHp", addHp, rl.getName(), hptMinimum));
1087            }
1088        }
1089    }
1090
1091    protected void removeEngineFromTrain(Engine engine) {
1092        // replace lead engine?
1093        if (getTrain().getLeadEngine() == engine) {
1094            getTrain().setLeadEngine(null);
1095        }
1096        if (engine.getConsist() != null) {
1097            for (Engine e : engine.getConsist().getEngines()) {
1098                removeRollingStockFromTrain(e);
1099            }
1100        } else {
1101            removeRollingStockFromTrain(engine);
1102        }
1103    }
1104
1105    private final static Logger log = LoggerFactory.getLogger(TrainBuilderEngines.class);
1106}