001package jmri.jmrit.withrottle;
002
003import java.io.IOException;
004import java.util.ArrayList;
005import java.util.Arrays;
006import java.util.List;
007import jmri.AddressedProgrammer;
008import jmri.AddressedProgrammerManager;
009import jmri.Consist;
010import jmri.ConsistManager;
011import jmri.DccLocoAddress;
012import jmri.InstanceManager;
013import jmri.LocoAddress;
014import jmri.ProgListener;
015import jmri.ProgrammerException;
016import jmri.jmrit.consisttool.ConsistFile;
017import org.jdom2.JDOMException;
018import org.slf4j.Logger;
019import org.slf4j.LoggerFactory;
020
021/**
022 * @author Brett Hoffman Copyright (C) 2010
023 */
024public class ConsistController extends AbstractController implements ProgListener {
025
026    private ConsistManager manager;
027    private ConsistFile file;
028    private boolean isConsistAllowed;
029
030    public ConsistController() {
031        //  writeFile needs to be separate method
032        if (InstanceManager.getDefault(WiThrottlePreferences.class).isUseWiFiConsist()) {
033            try {
034               manager = new WiFiConsistManager();
035               InstanceManager.store(manager, ConsistManager.class);
036               log.debug("Using WiFiConsisting");
037            } catch (NullPointerException npe) {
038               log.error("Attempting to use WiFiConsisting, but no Command Station available");
039               manager = null;
040            }
041        } else {
042            manager = InstanceManager.getNullableDefault(ConsistManager.class);
043            log.debug("Using JMRIConsisting");
044        }
045
046        if (manager == null) {
047            log.info("No consist manager instance.");
048            isValid = false;
049        } else {
050            if (InstanceManager.getDefault(WiThrottlePreferences.class).isUseWiFiConsist()) {
051                file = new WiFiConsistFile(manager);
052            } else {
053                file = new ConsistFile();
054                try {
055                    file.readFile();
056                } catch (IOException | JDOMException e) {
057                    log.warn("error reading consist file", (Object) e);
058                }
059            }
060            isValid = true;
061        }
062    }
063
064    /**
065     * Allows device to decide how to handle consisting. Just selection or
066     * selection and Make and Break. .size() indicates how many
067     * consists are being sent so the device can wait before displaying them
068     */
069    public void sendConsistListType() {
070        if (listeners == null) {
071            return;
072        }
073        String message;
074
075        int numConsists = manager.getConsistList().size();  //number of JMRI consists found
076        if (log.isDebugEnabled()) {
077            log.debug("{} consists found.", numConsists);
078        }
079
080        if (isConsistAllowed) {  //  Allow Make & Break consists
081            message = ("RCC" + numConsists);  //  Roster Consist Controller
082        } else {  //  Just allow selection list
083            message = ("RCL" + numConsists);  //  Roster Consist List
084        }
085
086        for (ControllerInterface listener : listeners) {
087            listener.sendPacketToDevice(message);
088        }
089    }
090
091    public void sendAllConsistData() {
092        // Loop thru JMRI consists and send consist detail for each
093        for (LocoAddress conAddr : manager.getConsistList()) {
094            sendDataForConsist(manager.getConsist(conAddr));
095        }
096    }
097
098    public void sendDataForConsist(Consist con) {
099        if (listeners == null) {
100            return;
101        }
102        StringBuilder list = new StringBuilder("RCD");  //  Roster Consist Data
103        list.append("}|{");
104        list.append(con.getConsistAddress());
105        list.append("}|{");
106        if (con.getConsistID().length() > 0) {
107            list.append(con.getConsistID());
108        }
109
110        for (DccLocoAddress loco : con.getConsistList()) {
111            list.append("]\\[");
112            list.append(loco.toString());
113            list.append("}|{");
114            list.append(con.getLocoDirection(loco));
115        }
116
117        String message = list.toString();
118
119        for (ControllerInterface listener : listeners) {
120            listener.sendPacketToDevice(message);
121        }
122    }
123
124    public void setIsConsistAllowed(boolean b) {
125        isConsistAllowed = b;
126    }
127
128    @Override
129    boolean verifyCreation() {
130        return isValid;
131    }
132
133    /**
134     *
135     * @param message string containing new consist information
136     */
137    @Override
138    void handleMessage(String message, DeviceServer deviceServer) {
139        try {
140            if (message.charAt(0) == 'P') {  //  Change consist 'P'ositions
141                reorderConsist(message);
142
143            }
144            if (message.charAt(0) == 'R') {  //  'R'emove consist
145                removeConsist(message);
146
147            }
148            if (message.charAt(0) == '+') {  //  Add loco to consist and/or set relative direction
149                addLoco(message);
150
151            }
152            if (message.charAt(0) == '-') {  //  remove loco from consist
153                removeLoco(message);
154
155            }
156            if (message.charAt(0) == 'F') {   //  program CV 21 & 22 'F'unctions
157                setConsistCVs(message);
158            }
159        } catch (NullPointerException exb) {
160            log.warn("Message \"{}\" does not match a consist command.", message);
161        }
162    }
163
164    /**
165     * Change the sequence of locos in this consist. Reorders the consistList,
166     * instead of setting the 'position' value. Lead and Trail are set on first
167     * and last locos by DccConsist.
168     *
169     * @param message RCP<;>consistAddress<:>leadLoco<;>nextLoco<;>...
170     * ...<;>nextLoco<;>trailLoco
171     */
172    private void reorderConsist(String message) {
173        Consist consist;
174        List<String> headerAndLocos = Arrays.asList(message.split("<:>"));
175
176        if (headerAndLocos.size() < 2) {
177            log.warn("reorderConsist missing data in message: {}", message);
178            return;
179        }
180
181        try {
182            List<String> headerData = Arrays.asList(headerAndLocos.get(0).split("<;>"));
183            //
184            consist = manager.getConsist(stringToDcc(headerData.get(1)));
185
186            List<String> locoData = Arrays.asList(headerAndLocos.get(1).split("<;>"));
187            /*
188             * Reorder the consistList:
189             * For each loco sent, remove it from the consistList
190             * and reinsert it at the front of the list.
191             */
192            for (String loco : locoData) {
193                ArrayList<DccLocoAddress> conList = consist.getConsistList();
194                int index = conList.indexOf(stringToDcc(loco));
195                if (index != -1) {
196                    conList.add(conList.remove(index));
197                }
198
199            }
200
201        } catch (NullPointerException e) {
202            log.warn("reorderConsist error for message: {}", message);
203            return;
204        }
205
206        writeFile();
207
208    }
209
210    /**
211     * remove a consist by it's Dcc address. Wiil remove all locos in the
212     * process.
213     *
214     * @param message RCR<;>consistAddress
215     */
216    private void removeConsist(String message) {
217        List<String> header = Arrays.asList(message.split("<;>"));
218        try {
219            Consist consist = manager.getConsist(stringToDcc(header.get(1)));
220            while (!consist.getConsistList().isEmpty()) {
221                DccLocoAddress loco = consist.getConsistList().get(0);
222                log.debug("Remove loco: {}, from consist: {}", loco, consist.getConsistAddress());
223                consist.remove(loco);
224            }
225        } catch (NullPointerException noCon) {
226            log.warn("Consist: {} not found. Cannot delete.", header.get(1));
227            return;
228        }
229
230        try {
231            manager.delConsist(stringToDcc(header.get(1)));
232        } catch (NullPointerException noCon) {
233            log.warn("Consist: {} not found. Cannot delete.", header.get(1));
234            return;
235        }
236
237        writeFile();
238
239    }
240
241    /**
242     * Add a loco or change it's direction. Creates a new consist if one does
243     * not already exist
244     *
245     * @param message RC+<;>consistAddress<;>ID<:>locoAddress<;>directionNormal
246     */
247    private void addLoco(String message) {
248        Consist consist;
249
250        List<String> headerAndLoco = Arrays.asList(message.split("<:>"));
251
252        try {
253            //  Break out header and either get existing consist or create new
254            List<String> headerData = Arrays.asList(headerAndLoco.get(0).split("<;>"));
255
256            consist = manager.getConsist(stringToDcc(headerData.get(1)));
257            consist.setConsistID(headerData.get(2));
258
259            List<String> locoData = Arrays.asList(headerAndLoco.get(1).split("<;>"));
260
261            if (consist.isAddressAllowed(stringToDcc(locoData.get(0)))) {
262                consist.add(stringToDcc(locoData.get(0)), Boolean.valueOf(locoData.get(1)));
263                if (log.isDebugEnabled()) {
264                    log.debug("add loco: {}, to consist: {}", locoData.get(0), headerData.get(1));
265                }
266            }
267
268        } catch (NullPointerException e) {
269            log.warn("addLoco error for message: {}", message);
270            return;
271        }
272
273        writeFile();
274    }
275
276    /**
277     * remove a loco if it exist in this consist.
278     *
279     * @param message RC-<;>consistAddress<:>locoAddress
280     */
281    private void removeLoco(String message) {
282        Consist consist;
283
284        List<String> headerAndLoco = Arrays.asList(message.split("<:>"));
285
286        log.debug("remove loco string: {}", message);
287
288        try {
289            List<String> headerData = Arrays.asList(headerAndLoco.get(0).split("<;>"));
290
291            consist = manager.getConsist(stringToDcc(headerData.get(1)));
292
293            List<String> locoData = Arrays.asList(headerAndLoco.get(1).split("<;>"));
294
295            DccLocoAddress loco = stringToDcc(locoData.get(0));
296            if (checkForBroadcastAddress(loco)) {
297                return;
298            }
299
300            if (consist.contains(loco)) {
301                consist.remove(loco);
302                if (log.isDebugEnabled()) {
303                    log.debug("Remove loco: {}, from consist: {}", loco, headerData.get(1));
304                }
305            }
306        } catch (NullPointerException e) {
307            log.warn("removeLoco error for message: {}", message);
308            return;
309        }
310
311        writeFile();
312    }
313
314    private void writeFile() {
315        try {
316            if (InstanceManager.getDefault(WiThrottlePreferences.class).isUseWiFiConsist()) {
317                file.writeFile(manager.getConsistList(), WiFiConsistFile.getFileLocation() + "wifiConsist.xml");
318            } else {
319                file.writeFile(manager.getConsistList());
320            }
321        } catch (IOException e) {
322            log.warn("Consist file could not be written!");
323        }
324    }
325
326    /**
327     * set CV 21&22 for consist functions send each CV individually
328     *
329     * @param message RCF<;> locoAddress <:> CV# <;> value
330     */
331    private void setConsistCVs(String message) {
332
333        DccLocoAddress loco;
334
335        List<String> headerAndCVs = Arrays.asList(message.split("<:>"));
336
337        log.debug("setConsistCVs string: {}", message);
338
339        try {
340            List<String> headerData = Arrays.asList(headerAndCVs.get(0).split("<;>"));
341
342            loco = stringToDcc(headerData.get(1));
343            if (checkForBroadcastAddress(loco)) {
344                return;
345            }
346
347        } catch (NullPointerException e) {
348            log.warn("setConsistCVs error for message: {}", message);
349            return;
350        }
351        AddressedProgrammer pom = InstanceManager.getDefault(AddressedProgrammerManager.class).getAddressedProgrammer(loco);
352        if (pom != null) {
353            // loco done, now get CVs
354            for (int i = 1; i < headerAndCVs.size(); i++) {
355                List<String> CVData = Arrays.asList(headerAndCVs.get(i).split("<;>"));
356
357                try {
358                    try {
359                        pom.writeCV(CVData.get(0), Integer.parseInt(CVData.get(1)), this);
360                    } catch (ProgrammerException e) {
361                        log.debug("Unable to communicate with programmer manager.");
362                    }
363                } catch (NumberFormatException nfe) {
364                    log.warn("Error in setting CVs", nfe);
365                }
366            }
367            InstanceManager.getDefault(AddressedProgrammerManager.class).releaseAddressedProgrammer(pom);
368        }
369    }
370
371    @Override
372    public void programmingOpReply(int value, int status) {
373
374    }
375
376    // this method may belong somewhere else.
377    static public DccLocoAddress stringToDcc(String s) {
378        int num = Integer.parseInt(s.substring(1));
379        boolean isLong = (s.charAt(0) == 'L');
380        return (new DccLocoAddress(num, isLong));
381    }
382
383    /**
384     * Check to see if an address will try to broadcast (0) a programming
385     * message.
386     *
387     * @param addr The address to check
388     * @return true if address is no good, otherwise false
389     */
390    public boolean checkForBroadcastAddress(DccLocoAddress addr) {
391        if (addr.getNumber() < 1) {
392            log.warn("Trying to use broadcast address!");
393            return true;
394        }
395        return false;
396    }
397
398    @Override
399    void register() {
400        throw new UnsupportedOperationException("Not used.");
401    }
402
403    @Override
404    void deregister() {
405        throw new UnsupportedOperationException("Not used.");
406    }
407
408    private final static Logger log = LoggerFactory.getLogger(ConsistController.class);
409
410}