001package jmri.jmrit.symbolicprog;
002
003import java.io.File;
004import java.io.FileInputStream;
005import java.io.InputStream;
006import java.io.IOException;
007import java.util.Properties;
008
009import jmri.jmrit.roster.RosterEntry;
010
011/**
012 * Import CV values from a TCS backup file (from a CDI backup)
013 * directly into a RosterEntry.
014 *<p>
015 * Note that this does not update any GUI that's showing the
016 * RosterEntry, e.g. a RosterPane or FunctionLabelPane. Those
017 * must have their updates triggered elsewhere, e.g. TcsImportAction.
018 *
019 * @author Alex Shepherd Copyright (C) 2003 (original Pr1Importer)
020 * *author Bob Jacobsen  Copyright (C) 2023
021 */
022public class TcsImporter {
023
024    Properties tcsProperties;
025
026    /**
027     * The import process starts upon creation of a TcsImporter
028     * @param file The File object to be read
029     * @param cvModel Model used to look up CV values (not used)
030     * @param model Variable model used to look up function bits (retained for later use)
031     * @throws IOException from underlying file operations
032     */
033    public TcsImporter(File file, CvTableModel cvModel, VariableTableModel model) throws IOException {
034        this.model = model;
035        tcsProperties = new Properties();
036        FileInputStream fileStream = new FileInputStream(file);
037        try {
038            tcsProperties.load(fileStream);
039        } finally {
040            fileStream.close();
041        }
042    }
043
044    VariableTableModel model;
045
046    public TcsImporter(InputStream stream) throws IOException {
047        tcsProperties = new Properties();
048        tcsProperties.load(stream);
049    }
050
051    public void setRosterEntry(RosterEntry rosterEntry) {
052        // TODO: How to handle name?  Check for mismatch with ID? (Don't change ID?)
053        log.debug("found name {}", tcsProperties.get("Train.Name"));
054        // TODO: Confirm that address is correct?  How to handle that?
055        log.debug("found address {}", tcsProperties.get("Train.Address"));
056        log.debug("found step {}", tcsProperties.get("Train.Speed")); // note key truncated as space
057
058        // TODO: move the following to a static method ot allow reuse from elsewhere
059
060        // copy User Description to comment
061        var userDescription = tcsProperties.get("Train.User").toString(); // note key truncated as space
062        // remove the "Description=" at front (due to space in key=value pair in input)
063        userDescription = userDescription.substring("Description=".length());
064        rosterEntry.setComment(userDescription);
065
066        for (int i=0; i < 27; i++) {
067            var momentaryObj = tcsProperties.get("Train.Functions("+i+").Momentary");
068            log.trace("Found Momentary {} as {}", i, momentaryObj);
069            if (momentaryObj == null) continue; // no change if null
070            var momentary = momentaryObj.toString();
071
072            var displayObj = tcsProperties.get("Train.Functions("+i+").Display");
073            log.trace("Found Display {} as {}", i, displayObj);
074            if (displayObj == null) continue; // no change if null
075            var display = displayObj.toString();
076
077            var descriptionObj = tcsProperties.get("Train.Functions("+i+").Description");
078            log.trace("Found Description {} as {}", i, descriptionObj);
079            if (descriptionObj == null) continue; // no change if null
080            var description = descriptionObj.toString();
081
082            // Handle non-zero Display values by updating description value
083            description = unpackDescription(description, display);
084
085            // Here, we copy Description to the label
086            rosterEntry.setFunctionLabel(i+1, description);
087            // and momentary to the "locked" status
088            rosterEntry.setFunctionLockable(i+1, momentary.equals("1"));
089
090            // process consist bit
091            // first, see if function variable exists
092            var variable = model.findVar("Consist Address Active For F"+(i+1));
093            if (variable != null) {
094                // it exists, so we can't ignore the consist info.
095                // retrieve it
096                var consistObj = tcsProperties.get("Train.Functions("+i+").Consist");
097                log.debug("Found {} as \'{}\'", "Train.Functions("+i+").Consist", consistObj);
098                if (consistObj != null) {
099                    if (consistObj.equals("Behavior=1")) { // "Current Cab Only"
100                        variable.setIntValue(1);
101                    } else {
102                        variable.setIntValue(0);    // respond to the consist address
103                    }
104                    log.trace("result is value {}", variable.getIntValue());
105                }
106            } else {
107                log.debug("Variable {} not found", "Consist Address Active For F"+(i+1) );
108            }
109        }
110    }
111
112    static String unpackDescription(String description, String display) {
113        // if there is a description value, that wins
114        if (!description.isEmpty()) return description;
115
116        // there must be a value in display, unpack it.
117        // We do string switch in case of non-numerically-parseable garbage
118        switch (display) {
119
120            case "0":   return "";
121
122            case "1":   return "Headlight";
123            case "13":  return "Bell";
124            case "14":  return "Horn";
125            case "15":  return "Whistle";
126            case "11":  return "Pantograph";
127            case "10":  return "Smoke";
128            case "4":   return "Engine";
129            case "74":  return "Light";
130            case "28":  return "Coupler Clank";
131            case "122": return "Couple";
132            case "9":   return "Uncouple";
133
134            case "7":   return "Shunting Mode";
135            case "8":   return "Momentum";
136
137            case "57":  return "Brake";
138            case "200": return "Brake Release";
139            case "41":  return "Dynamic Brake";
140            case "31":  return "Manual Notch Down";
141            case "30":  return "Manual Notch Up";
142            case "69":  return "Reverser";
143            case "100": return "Mute";
144
145            case "12":  return "Far Light";
146            case "3":   return "Cab Light";
147            case "48":  return "Ditch Lights";
148            case "98":  return "Step Lights";
149            case "62":  return "Tail Lights";
150            case "58":  return "Switching Lights";
151            case "51":  return "Dimmer";
152            case "2":   return "Interior Lights";
153
154            case "42":  return "Air Compressor";
155            case "45":  return "Air Pump";
156            case "60":  return "Injector";
157            case "108": return "Exhaust Fan";
158            case "17":  return "Radiator Fan";
159            case "66":  return "Steam Generator";
160            case "105": return "Blower";
161            case "56":  return "Blow Down";
162            case "38":  return "Safety";
163            case "55":  return "Sanding";
164            case "88":  return "Ash Dump";
165            case "18":  return "Shoveling";
166            case "35":  return "Water Fill";
167
168            case "103": return "Long Whistle";
169            case "64":  return "Short Whistle";
170            case "63":  return "Doppler Horn";
171
172            case "36":  return "Curve Squeal";
173            case "21":  return "Brake Squeal";
174            case "6":   return "Announce";
175            case "27":  return "Cab Chatter";
176
177            case "255": return "Unavailable_";
178
179            default:    return "<entry error \""+display+"\">";
180        }
181    }
182
183    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TcsImporter.class);
184}