001package jmri.jmrit.operations.trains.csv;
002
003import java.io.*;
004import java.nio.charset.StandardCharsets;
005import java.util.ArrayList;
006import java.util.List;
007
008import org.apache.commons.csv.CSVFormat;
009import org.apache.commons.csv.CSVPrinter;
010import org.slf4j.Logger;
011import org.slf4j.LoggerFactory;
012
013import jmri.InstanceManager;
014import jmri.jmrit.operations.locations.Track;
015import jmri.jmrit.operations.rollingstock.cars.Car;
016import jmri.jmrit.operations.rollingstock.engines.Engine;
017import jmri.jmrit.operations.routes.RouteLocation;
018import jmri.jmrit.operations.setup.Setup;
019import jmri.jmrit.operations.trains.*;
020
021/**
022 * Builds a train's manifest using Comma Separated Values (csv).
023 *
024 * @author Daniel Boudreau Copyright (C) 2011, 2015
025 *
026 */
027public class TrainCsvManifest extends TrainCsvCommon {
028
029    public TrainCsvManifest(Train train) throws BuildFailedException {
030        if (!Setup.isGenerateCsvManifestEnabled()) {
031            return;
032        }
033        // create comma separated value manifest file
034        File file = InstanceManager.getDefault(TrainManagerXml.class).createTrainCsvManifestFile(train.getName());
035
036        try (CSVPrinter fileOut = new CSVPrinter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)),
037                CSVFormat.DEFAULT)) {
038            // build header
039            printHeader(fileOut);
040            printRailroadName(fileOut,
041                    train.getRailroadName().isEmpty() ? Setup.getRailroadName() : train.getRailroadName());
042            printTrainName(fileOut, train.getName());
043            printTrainDescription(fileOut, train.getDescription());
044            printPrinterName(fileOut, locationManager.getLocationByName(train.getTrainDepartsName()).getDefaultPrinterName());
045            printLogoURL(fileOut, train);
046            printValidity(fileOut, getDate(true));
047            printTrainComment(fileOut, train);
048            printRouteComment(fileOut, train);
049
050            // get engine and car lists
051            List<Engine> engineList = engineManager.getByTrainBlockingList(train);
052            List<Car> carList = carManager.getByTrainDestinationList(train);
053
054            boolean newWork = false;
055            String previousRouteLocationName = null;
056            List<RouteLocation> routeList = train.getRoute().getLocationsBySequenceList();
057            for (RouteLocation rl : routeList) {
058                // print info only if new location
059                if (!rl.getSplitName().equals(previousRouteLocationName)) {
060                    printLocationName(fileOut, rl.getSplitName());
061                    if (rl != train.getTrainDepartsRouteLocation()) {
062                        fileOut.printRecord("AT", Bundle.getMessage("csvArrivalTime"), train.getExpectedArrivalTime(rl)); // NOI18N
063                    }
064                    if (rl == train.getTrainDepartsRouteLocation()) {
065                        fileOut.printRecord("DT", Bundle.getMessage("csvDepartureTime"), train.getFormatedDepartureTime()); // NOI18N
066                    } else if (!rl.getDepartureTime().equals(RouteLocation.NONE)) {
067                        fileOut.printRecord("DTR", Bundle.getMessage("csvDepartureTimeRoute"), rl.getFormatedDepartureTime()); // NOI18N
068                    } else {
069                        fileOut.printRecord("EDT", Bundle.getMessage("csvEstimatedDepartureTime"), train.getExpectedDepartureTime(rl)); // NOI18N
070                    }
071                    printLocationComment(fileOut, rl.getLocation());
072                    if (Setup.isPrintTruncateManifestEnabled() && rl.getLocation().isSwitchListEnabled()) {
073                        fileOut.printRecord("TRUN", Bundle.getMessage("csvTruncate"));
074                    }
075                }
076                printRouteLocationComment(fileOut, rl);
077                printTrackComments(fileOut, rl, carList);
078
079                // engine change or helper service?
080                checkForEngineOrCabooseChange(fileOut, train, rl);
081
082                for (Engine engine : engineList) {
083                    if (engine.getRouteLocation() == rl) {
084                        printEngine(fileOut, engine, "PL", Bundle.getMessage("csvPickUpLoco"));
085                    }
086                }
087                for (Engine engine : engineList) {
088                    if (engine.getRouteDestination() == rl) {
089                        printEngine(fileOut, engine, "SL", Bundle.getMessage("csvSetOutLoco"));
090                    }
091                }
092                // block pick up cars
093                // caboose or FRED is placed at end of the train
094                // passenger cars are already blocked in the car list
095                // passenger cars with negative block numbers are placed at
096                // the front of the train, positive numbers at the end of
097                // the train.
098                for (RouteLocation rld : train.getTrainBlockingOrder()) {
099                    for (Car car : carList) {
100                        if (isNextCar(car, rl, rld)) {
101                            newWork = true;
102                            int count = 0;
103                            if (car.isUtility()) {
104                                count = countPickupUtilityCars(carList, car, IS_MANIFEST);
105                                if (count == 0) {
106                                    continue; // already done this set of
107                                              // utility cars
108                                }
109                            }
110                            printCar(fileOut, car, "PC", Bundle.getMessage("csvPickUpCar"), count);
111                        }
112                    }
113                }
114                // car set outs
115                for (Car car : carList) {
116                    if (car.getRouteDestination() == rl) {
117                        newWork = true;
118                        int count = 0;
119                        if (car.isUtility()) {
120                            count = countSetoutUtilityCars(carList, car, false, IS_MANIFEST);
121                            if (count == 0) {
122                                continue; // already done this set of utility cars
123                            }
124                        }
125                        printCar(fileOut, car, "SC", Bundle.getMessage("csvSetOutCar"), count);
126                    }
127                }
128                // car holds
129                List<Car> carsByLocation = carManager.getByLocationList();
130                List<Car> cList = new ArrayList<>();
131                for (Car car : carsByLocation) {
132                    if (car.getLocation() == rl.getLocation() && car.getRouteLocation() == null && car.getTrack() != null) {
133                        cList.add(car);
134                    }
135                }
136                clearUtilityCarTypes(); // list utility cars by quantity
137                for (Car car : cList) {
138                    // list cars on tracks that only this train can service
139                    if (!car.getTrack().getLocation().isStaging()
140                            && car.getTrack().isPickupTrainAccepted(train) && car.getTrack().getPickupIds().length == 1
141                            && car.getTrack().getPickupOption().equals(Track.TRAINS)) {
142                        int count = 0;
143                        if (car.isUtility()) {
144                            count = countPickupUtilityCars(cList, car, !IS_MANIFEST);
145                            if (count == 0) {
146                                continue; // already done this set of utility cars
147                            }
148                        }
149                        printCar(fileOut, car, "HOLD", Bundle.getMessage("csvHoldCar"), count);
150                    }
151                }
152                if (rl != train.getTrainTerminatesRouteLocation()) {
153                    // Is the next location the same as the previous?
154                    RouteLocation rlNext = train.getRoute().getNextRouteLocation(rl);
155                    if (!rl.getSplitName().equals(rlNext.getSplitName())) {
156                        if (newWork) {
157                            printTrainDeparts(fileOut, rl.getSplitName(), rl.getTrainDirectionString());
158                            printTrainLength(fileOut, train.getTrainLength(rl), train.getNumberEmptyCarsInTrain(rl),
159                                    train.getNumberCarsInTrain(rl));
160                            printTrainWeight(fileOut, train.getTrainWeight(rl));
161                            newWork = false;
162                        } else {
163                            fileOut.printRecord("NW", Bundle.getMessage("csvNoWork"));
164                        }
165                    }
166                } else {
167                    printTrainTerminates(fileOut, rl.getSplitName());
168                }
169                previousRouteLocationName = rl.getSplitName();
170            }
171            // Are there any cars that need to be found?
172            listCarsLocationUnknown(fileOut);
173
174            fileOut.flush();
175            fileOut.close();
176        } catch (IOException e) {
177            log.error("Can not open CSV manifest file: {}", e.getLocalizedMessage());
178            throw new BuildFailedException(e);
179        }
180    }
181
182    private final static Logger log = LoggerFactory.getLogger(TrainCsvManifest.class);
183}