001package jmri.jmrix.bachrus;
002
003import java.io.BufferedWriter;
004import java.io.File;
005import java.io.FileOutputStream;
006import java.io.IOException;
007import java.io.OutputStreamWriter;
008import java.nio.charset.StandardCharsets;
009import java.text.SimpleDateFormat;
010import java.util.ArrayList;
011import java.util.Date;
012import java.util.List;
013import java.util.Locale;
014import javax.annotation.Nonnull;
015import javax.swing.JFileChooser;
016import jmri.util.FileUtil;
017import org.apache.commons.csv.CSVFormat;
018import org.apache.commons.csv.CSVParser;
019import org.apache.commons.csv.CSVPrinter;
020import org.apache.commons.csv.CSVRecord;
021import org.slf4j.Logger;
022import org.slf4j.LoggerFactory;
023
024/**
025 * Class to represent a dimensionless speed profile of a DCC decoder.
026 *
027 * @author Andrew Crosland Copyright (C) 2010
028 * @author Dennis Miller Copyright (C) 2015
029 */
030public class DccSpeedProfile {
031
032    protected int _length;
033    protected float[] _dataPoints;
034    protected float _max;
035    // index of last valid data point, -1 means no data
036    protected int _lastPoint;
037    protected List<CSVRecord> dccProfileData;
038
039    public DccSpeedProfile(int len) {
040        _length = len;
041        _dataPoints = new float[_length];
042
043        for (int i = 0; i < _length; i++) {
044            _dataPoints[i] = 0.0F;
045        }
046        _max = 40;
047        _lastPoint = -1;
048    }
049
050    public boolean setPoint(int idx, float val) {
051        boolean ret = false;
052        if (idx < _length) {
053            _dataPoints[idx] = val;
054            _lastPoint++;
055            log.debug("Index: {} val: {}", idx, val);
056            if (val > _max) {
057                log.debug("     Old max: {}", _max);
058                // Adjust maximum value
059                _max = (float) (Math.floor(val / 20) + 1) * 20;
060                log.debug("     New max: {}", _max);
061            }
062            ret = true;
063        }
064        return ret;
065    }
066
067    public void clear() {
068        for (int i = 0; i < _length; i++) {
069            _dataPoints[i] = 0.0F;
070        }
071        _max = 40;
072        _lastPoint = -1;
073    }
074
075    public float getPoint(int idx) {
076        if ((idx < _length) && (idx <= _lastPoint)) {
077            return _dataPoints[idx];
078        } else {
079            return -1;
080        }
081    }
082
083    public int getLength() {
084        return _length;
085    }
086
087    public void setMax(float m) {
088        _max = m;
089    }
090
091    public float getMax() {
092        return _max;
093    }
094
095    public int getLast() {
096        return _lastPoint;
097    }
098
099    public static void printHeading(@Nonnull CSVPrinter p, int address) throws IOException {
100        SimpleDateFormat formatter = new SimpleDateFormat("EEE d MMM yyyy", Locale.getDefault());
101        String today = formatter.format(new Date());
102        // title
103        String annotate = Bundle.getMessage("ProfileFor") + " "
104                + address + " " + Bundle.getMessage("CreatedOn")
105                + " " + today;
106        // should this be printComment instead?
107        p.printRecord(annotate);
108    }
109
110    // Save data as CSV
111    public static void export(DccSpeedProfile sp, int address, String dirString, int units) {
112        File file = openExportFile();
113        try (CSVPrinter p = new CSVPrinter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)), CSVFormat.DEFAULT)) {
114            String unitsString;
115            if (units == Speed.MPH) {
116                unitsString = "MPH";
117            } else {
118                unitsString = "KPH";
119            }
120            // Save rows
121            printHeading(p, address);
122            p.printRecord("Step", "Speed(" + dirString + " " + unitsString + ")");
123            // for each data point
124            for (int i = 0; i < sp.getLength(); i++) {
125                p.printRecord(i, units == Speed.MPH ? Speed.kphToMph(sp.getPoint(i)) : sp.getPoint(i));
126            }
127            p.flush();
128            p.close();
129        } catch (IOException ex) {
130            log.error("Error exporting speed profile", ex);
131        }
132    }
133
134    public static void export(DccSpeedProfile[] sp, int address, int units) {
135        File file = openExportFile();
136        try (CSVPrinter p = new CSVPrinter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)), CSVFormat.DEFAULT)) {
137
138            String unitsString;
139            if (units == Speed.MPH) {
140                unitsString = "MPH";
141            } else {
142                unitsString = "KPH";
143            }
144            // Save rows
145            printHeading(p, address);
146            p.printRecord("Step", "Speed(fwd " + unitsString + ")", "Speed(rev " + unitsString + ")");
147            // for each data point
148            for (int i = 0; i < sp[0].getLength(); i++) {
149                ArrayList<Object> list = new ArrayList<>();
150                list.add(i);
151                // for each profile
152                for (DccSpeedProfile item : sp) {
153                    list.add(units == Speed.MPH ? Speed.kphToMph(item.getPoint(i)) : item.getPoint(i));
154                }
155                p.printRecord(list);
156            }
157            p.flush();
158            p.close();
159        } catch (IOException ex) {
160            log.error("Error exporting speed profile", ex);
161        }
162    }
163
164    private static File openExportFile() {
165        JFileChooser fileChooser = new jmri.util.swing.JmriJFileChooser(FileUtil.getUserFilesPath());
166        if (fileChooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
167            return fileChooser.getSelectedFile();
168        }
169        return null;
170    }
171
172    public int importDccProfile(int units) {
173        openImportFile();
174        if (dccProfileData.size() < 31) {
175            log.error("Not enough lines in reference speed profile file");
176            clear();
177            return -1;
178        }
179
180        String secondLine = dccProfileData.get(1).toString();
181        if (!(secondLine.contains("MPH") || secondLine.contains("KPH"))) {
182            log.error("Bad 'units' format on line 2 of reference speed profile file");
183            clear();
184            return -1;
185        }
186        for (int i = 2; i < dccProfileData.size(); i++) {
187            try {
188                String value = dccProfileData.get(i).get(1);
189                float speed = Float.parseFloat(value);
190                // speed values from the speedometer are calc'd and stored in
191                // the DccSpeedProfile object as KPH so need to convert
192                // if the file was in MPH
193                if (secondLine.contains("MPH")) {
194                    speed = Speed.mphToKph(speed);
195                }
196
197                setPoint(i - 2, speed);
198            } catch (NullPointerException | NumberFormatException | ArrayIndexOutOfBoundsException ex) {
199                log.error("Bad data or format in reference speed profile file", ex);
200                clear();
201                return -1;
202            }
203        }
204        return 0;
205    }
206
207    private void openImportFile() {
208        JFileChooser fileChooser = new jmri.util.swing.JmriJFileChooser(FileUtil.getUserFilesPath());
209
210        if (fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
211            File file = fileChooser.getSelectedFile();
212            try {
213                dccProfileData = CSVParser.parse(file, StandardCharsets.UTF_8, CSVFormat.DEFAULT).getRecords();
214            } catch (IOException ex) {
215                log.error("Failed to read reference profile file", ex);
216            }
217        }
218    }
219
220    private final static Logger log = LoggerFactory.getLogger(DccSpeedProfile.class);
221
222}