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