001package jmri.jmrix.rfid.swing.tagcarwin;
002
003import jmri.IdTagManager;
004import jmri.InstanceManager;
005import jmri.UserPreferencesManager;
006import jmri.jmrit.operations.rollingstock.cars.Car;
007import jmri.jmrit.operations.rollingstock.cars.CarManager;
008import jmri.util.AlphanumComparator;
009import jmri.util.JmriJFrame;
010import jmri.util.swing.JmriPanel;
011import org.slf4j.Logger;
012import org.slf4j.LoggerFactory;
013
014import java.awt.*;
015import java.awt.event.ActionEvent;
016import java.awt.event.ActionListener;
017import java.util.ArrayList;
018import java.util.Hashtable;
019import java.util.List;
020import javax.swing.*;
021import javax.swing.event.ListSelectionEvent;
022import javax.swing.event.ListSelectionListener;
023
024/**
025 * A panel to associate an RFID tag with a car
026 *
027 * @author J. Scott Walton Copyright (C) 2022
028 */
029public class AssociateTag extends JmriPanel implements ActionListener, ListSelectionListener {
030    private static final Logger log = LoggerFactory.getLogger(AssociateTag.class);
031
032    String tag;
033    private final JButton okayButton = new JButton();
034    private final JButton cancelButton = new JButton();
035    private final DefaultListModel<String> roadListModel = new DefaultListModel<>();
036    private final DefaultListModel<String> numberListModel = new DefaultListModel<>();
037    private final JList<String> roadCombo = new JList<>();
038    private final JList<String> numberCombo = new JList<>();
039
040
041    private final JCheckBox includeCars = new JCheckBox();
042    private final JLabel message = new JLabel("");
043    private final String includeCarsString = this.getClass().getName() + "IncludeAllCars";
044    private final ArrayList<String> roadList = new ArrayList<>();
045    private final ArrayList<String> roadsWith = new ArrayList<>();
046    private final Hashtable<String, List<String>> numbersWith = new Hashtable<>();
047    private final Hashtable<String, List<String>> roadNumbers = new Hashtable<>();
048    protected CarManager carManager = InstanceManager.getDefault(CarManager.class);
049    protected IdTagManager tagManager = InstanceManager.getDefault(IdTagManager.class);
050
051    public void setParentFrame(JmriJFrame parentFrame) {
052        this.parentFrame = parentFrame;
053    }
054
055    JmriJFrame parentFrame;
056
057    public AssociateTag(String thisTag) {
058        super();
059        tag = thisTag;
060    }
061
062
063    @Override
064    public String getTitle(){
065        return Bundle.getMessage("AssociateTitle" ) + tag;
066    }
067
068    @Override
069    public void dispose() {
070        UserPreferencesManager pm = InstanceManager.getDefault(UserPreferencesManager.class);
071        pm.setSimplePreferenceState(includeCarsString, includeCars.isSelected());
072    }
073
074    @Override
075    public void initComponents() {
076        log.debug("setting up the AssociateTag panel");
077        initRoads();
078        this.setLayout(new GridBagLayout());
079        this.setPreferredSize(new Dimension(500, 500));
080        this.setMinimumSize(new Dimension(500, 450));
081
082
083        UserPreferencesManager pm = InstanceManager.getDefault(UserPreferencesManager.class);
084        includeCars.setSelected(pm.getSimplePreferenceState(includeCarsString));
085        JScrollPane roadScroll = new JScrollPane();
086        roadScroll.setMinimumSize(new Dimension(150, 250));
087        roadScroll.setViewportView(roadCombo);
088        roadCombo.setVisibleRowCount(10);
089        roadCombo.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
090        roadCombo.setLayoutOrientation(JList.VERTICAL);
091        roadCombo.addListSelectionListener(this);
092        roadCombo.setMinimumSize(new Dimension(150, 250));
093        roadCombo.setModel(roadListModel);
094        this.add(roadScroll, new GridBagConstraints(0, 0, 1, 2, 0.0, 0.0,
095                GridBagConstraints.LINE_START, GridBagConstraints.BOTH,
096                new Insets(10, 10, 10, 10), 10, 10));
097
098        JScrollPane numberScroll = new JScrollPane();
099        numberScroll.setViewportView(numberCombo);
100        numberScroll.setMinimumSize(new Dimension(150, 250));
101        numberCombo.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
102        numberCombo.setVisibleRowCount(10);
103        numberCombo.setLayoutOrientation(JList.VERTICAL);
104        numberCombo.setMinimumSize(new Dimension(75, 75));
105        numberCombo.setEnabled(false);
106        numberCombo.setModel(numberListModel);
107
108        this.add(numberScroll, new GridBagConstraints( 2, 0, 1, 2, 0.0, 0.0,
109                GridBagConstraints.LINE_END, GridBagConstraints.BOTH,
110                new Insets(10, 10, 10, 10), 10, 10));
111        JLabel tagLabel = new JLabel();
112        tagLabel.setText(Bundle.getMessage("AssociateTag"));
113        this.add(tagLabel, new GridBagConstraints(0, 4, 2, 1, 0.0, 0.0,
114                GridBagConstraints.LINE_END, GridBagConstraints.HORIZONTAL,
115                new Insets(5, 5, 5, 5), 5, 5));
116        JLabel thisTag = new JLabel(tag);
117        this.add(thisTag, new GridBagConstraints(2, 4, 1, 1, 0.0, 0.0,
118                GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL,
119                new Insets(5, 5, 5, 5), 5, 5));
120        includeCars.setText(Bundle.getMessage("AssociateIncludeAll"));
121        includeCars.setToolTipText(Bundle.getMessage("AssociateAllToolTip"));
122        includeCars.addActionListener(this);
123        this.add(includeCars, new GridBagConstraints(0, 5, 3, 1, 0.0, 0.0,
124                GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL,
125                new Insets(5, 5, 5, 5), 0, 0));
126        message.setMinimumSize(new Dimension(400,10));
127        this.add(message, new GridBagConstraints(0, 6, 3, 1, 0.0, 0.0,
128                GridBagConstraints.LINE_START, GridBagConstraints.NONE,
129                new Insets(10, 10, 10, 10), 5, 5));
130
131        message.setText("  ");
132        okayButton.setText(Bundle.getMessage("AssociateOkay"));
133        okayButton.addActionListener(this);
134        okayButton.setEnabled(false);
135        this.add(okayButton, new GridBagConstraints(0, 7, 1, 1, 0.0, 0.0,
136                GridBagConstraints.LINE_START, GridBagConstraints.NONE,
137                new Insets(20, 20, 20, 20), 10, 10));
138        cancelButton.setText(Bundle.getMessage("AssociateCancel"));
139        cancelButton.addActionListener(this);
140        this.add(cancelButton, new GridBagConstraints(2, 7, 1, 1, 0.0, 0.0,
141                GridBagConstraints.LINE_END, GridBagConstraints.NONE,
142                new Insets(20, 20, 20, 20), 10, 10));
143        setRoads();
144        if (includeCars.isSelected()) {
145            if (roadsWith.size() < 1) {
146                message.setText(Bundle.getMessage("AssociateNoCars"));
147            }
148        }
149        if (roadList.size() < 1 ) {
150            message.setText(Bundle.getMessage("AssociateNoRoads"));
151        }
152    }
153
154    private void initRoads() {
155        log.debug("building a list of road number with their cars");
156        roadList.clear();
157        roadNumbers.clear();
158        roadsWith.clear();
159        numbersWith.clear();
160        List<Car> carList = carManager.getList();
161        for (Car thisCar : carList) {
162            if (numbersWith.containsKey(thisCar.getRoadName())) {
163                numbersWith.get(thisCar.getRoadName()).add(thisCar.getNumber());
164            } else {
165                roadsWith.add(thisCar.getRoadName());
166                ArrayList<String> tempArray = new ArrayList<>();
167                tempArray.add(thisCar.getNumber());
168                numbersWith.put(thisCar.getRoadName(), tempArray);
169            }
170            if ( "".equals(thisCar.getRfid()) ) {
171                if (roadNumbers.containsKey(thisCar.getRoadName())) {
172                    roadNumbers.get(thisCar.getRoadName()).add(thisCar.getNumber());
173                } else {
174                    roadList.add(thisCar.getRoadName());
175                    ArrayList<String> tempArray = new ArrayList<>();
176                    tempArray.add(thisCar.getNumber());
177                    roadNumbers.put(thisCar.getRoadName(), tempArray);
178                }
179            }
180        }
181        java.util.Collections.sort(roadsWith);
182        java.util.Collections.sort(roadList);
183        for (String road : roadList) {
184            java.util.Collections.sort(roadNumbers.get(road), new AlphanumComparator());
185        }
186        for (String road : roadsWith) {
187            java.util.Collections.sort(numbersWith.get(road), new AlphanumComparator());
188        }
189    }
190
191    private void setOneRoad() {
192        message.setText(Bundle.getMessage("AssociateReady"));
193        numberCombo.setEnabled(true);
194        roadCombo.setSelectedIndex(0);
195    }
196
197    private void setRoads() {
198        roadCombo.removeListSelectionListener(this);
199        numberCombo.removeListSelectionListener(this);
200        message.setText(" ");
201        numberListModel.clear(); // no numbers until we have a selected road
202        okayButton.setEnabled(false); // can't okay until we have selected both
203        roadListModel.clear();
204        List<String> theList;
205        if (includeCars.isSelected()) {
206            theList = roadsWith;
207        } else {
208            theList = roadList;
209        }
210        for (String road : theList) {
211            roadListModel.addElement(road);
212        }
213        roadCombo.addListSelectionListener(this);
214        if (theList.size() == 1) {
215            // we have only 1 item select it
216            setOneRoad();
217
218        }
219    }
220
221    private void closePage() {
222        dispose();
223        parentFrame.dispose();
224    }
225
226    @Override
227    public void actionPerformed(ActionEvent e) {
228        if (e.getSource().equals(okayButton)) {
229        String thisRoad =  roadCombo.getSelectedValue();
230        String thisNumber =  numberCombo.getSelectedValue();
231        if (thisRoad == null || thisNumber == null) {
232            message.setText(Bundle.getMessage("AssociateNeedSelected"));
233            return;
234        }
235        Car thisCar = carManager.getByRoadAndNumber(thisRoad, thisNumber);
236        if (thisCar == null) {
237            log.error("Car was pulled from combo but was not found {} {}", thisRoad, thisNumber);
238            return;
239        }
240        if (e.getSource().equals(okayButton)) {
241            log.debug("setting this tag ({}) to car {} with number {}", tag, thisRoad, thisNumber);
242            tagManager.provideIdTag(tag);
243            thisCar.setRfid(tag);
244            closePage();
245        }
246        } else if (e.getSource().equals(cancelButton)){
247            log.debug("closing the Associate panel");
248            closePage();
249        } else if (e.getSource().equals(includeCars)) {
250            log.debug("the includeCars checkbox is changing - rebuilding lists");
251            setRoads();
252
253        } else {
254            log.error("action performed for an unrecognized source");
255            return;
256        }
257
258    }
259
260    private void doNumbers(String road) {
261        numberCombo.removeListSelectionListener(this);
262        numberListModel.clear();
263        List<String> numberList;
264        if (includeCars.isSelected()) {
265            numberList = numbersWith.get(road);
266        } else {
267            numberList = roadNumbers.get(road);
268        }
269        if (numberList == null) {
270            log.error("didn't find the road in the list");
271            return;
272        }
273        for (String thisNumber : numberList) {
274            numberListModel.addElement(thisNumber);
275        }
276        numberCombo.addListSelectionListener(this);
277        numberCombo.setEnabled(true);
278        if (numberList.size() == 1) {
279            numberCombo.setSelectedIndex(0);
280            message.setText(Bundle.getMessage("AssociateOkayReady"));
281        } else {
282            message.setText("  ");
283        }
284
285    }
286
287
288    @Override
289    public void valueChanged(ListSelectionEvent e) {
290        log.debug("got a list selection event");
291        if (e.getValueIsAdjusting()) {
292            return;
293        }
294        if (e.getSource().equals(roadCombo)) {
295            numberCombo.setEnabled(false);
296            numberCombo.removeListSelectionListener(this);
297            okayButton.setEnabled(false);
298            if (roadCombo.getSelectedIndex() == -1) {
299                log.debug("no selection - turning off numbers combo");
300                message.setText(" ");
301                okayButton.setEnabled(false);
302                numberCombo.setEnabled(false);
303                numberListModel.clear();
304            } else {
305                doNumbers(roadCombo.getSelectedValue());
306                message.setText(Bundle.getMessage("AssociateReady"));
307                numberCombo.addListSelectionListener(this);
308                numberCombo.setEnabled(true);
309            }
310        } else if (e.getSource().equals(numberCombo)) {
311            if (numberCombo.getSelectedIndex() == -1 ) {
312                log.debug("road number was deselected - turning off okay button");
313                message.setText(Bundle.getMessage("AssociateReady"));
314                okayButton.setEnabled(false);
315            } {
316                okayButton.setEnabled(true);
317                message.setText(Bundle.getMessage("AssociateOkayReady"));
318            }
319        } else {
320            log.error("don't recognize the source of the event");
321        }
322    }
323
324}