001package jmri.jmrix.bidib.netbidib;
002
003import java.util.Map;
004import java.awt.event.ActionEvent;
005import java.awt.event.ActionListener;
006import javax.swing.JButton;
007import javax.swing.JComboBox;
008import javax.swing.JLabel;
009import javax.swing.JPanel;
010
011import jmri.jmrix.PortAdapter;
012import jmri.jmrix.bidib.BiDiBConstants;
013import jmri.util.ThreadingUtil;
014import java.util.Map.Entry;
015        
016import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
017
018import org.bidib.jbidibc.messages.utils.ByteUtils;
019import org.bidib.jbidibc.netbidib.client.NetBidibClient;
020
021import org.slf4j.Logger;
022import org.slf4j.LoggerFactory;
023
024/**
025 * Definition of objects to handle configuring a netBiDiB layout
026 * connection via a NetBiDiBAdapter object.
027 *
028 * @author Eckart Meyer Copyright (C) 2024
029 */
030public class ConnectionConfig  extends jmri.jmrix.AbstractNetworkConnectionConfig implements ActionListener {
031
032    public final static String NAME = "netBiDiB"; //text to show in connection type ComboBox
033    
034    private final JComboBox<String> deviceListField = new JComboBox<>();
035    private final JLabel deviceListFieldLabel = new JLabel(Bundle.getMessage("NetBiDiBConnectionAvailableDevices"));
036    private final JButton deviceListRefreshButton = new JButton("Refresh");
037    private final JButton pairingButton = new JButton("Pairing");
038    private final JButton logoffButton = new JButton("Logoff");
039
040    /**
041     * Ctor for an object being created during load process; Swing init is
042     * deferred.
043     */
044    public ConnectionConfig() {
045        super();
046    }
047
048    /**
049     * Ctor for a connection configuration with no preexisting adapter.
050     * {@link #setInstance()} will fill the adapter member.
051     * @param p network port adapter.
052     */
053    public ConnectionConfig(jmri.jmrix.NetworkPortAdapter p) {
054        super(p);
055        log.info("NetworkPortAdapter opening.");
056    }
057
058    @Override
059    public String name() {
060        return NAME;
061    }
062
063    /**
064     * {@inheritDoc}
065     */
066    @Override
067    protected void setInstance() {
068        if (adapter == null) {
069            adapter = new NetBiDiBAdapter();
070            adapter.setPort(NetBidibClient.NET_BIDIB_PORT_NUMBER);
071            adapter.setHostName(BiDiBConstants.BIDIB_OVER_TCP_DEFAULT_HOST);
072        }
073    }
074    
075    /**
076     * fill the device list combo box if autoconfig is enabled
077     */
078    private void getDeviceListData() {
079        if (adapter.getMdnsConfigure()) {
080            adapter.autoConfigure(); //get new data
081        }
082        else {
083            //((NetBiDiBAdapter)adapter).deviceListAddFromPairingStore(); //get at least data from pairing store
084        }
085        // get the data from netBiDiB adapter
086        Map<Long, String> devlist =  ((NetBiDiBAdapter)adapter).getDeviceListEntries();
087        deviceListField.setEnabled(false); //signal the combo box action listener to do nothing while filling
088        deviceListField.removeAllItems();
089        for ( Entry<Long, String> entry: devlist.entrySet()) { //don't use keySet - CI Tests doesn't like it :-(
090            Long uid = entry.getKey();
091            log.trace("get device list entry for uid {}: [{}]", ByteUtils.getUniqueIdAsString(uid), devlist.get(uid));
092            deviceListField.addItem(devlist.get(uid));
093        }
094        deviceListField.setEnabled(true); //re-enable the combo box
095        // get the current unique id if available
096        String item = devlist.get(((NetBiDiBAdapter)adapter).getUniqueId());
097        if (item != null) {
098            // it is available - select the correspondent entry in the combo box
099            // this will trigger the selection event, which in turn fills other fields
100            deviceListField.setSelectedItem(item);
101        }
102        else {
103            // no current uid available - just select the first entry (if there are entries at all)
104            // this will trigger the selection event, which in turn fills other fields
105            if (devlist.size() > 0) {
106                deviceListField.setSelectedIndex(0);
107            }
108        }
109    }
110
111    /**
112     * {@inheritDoc}
113     */
114    @Override
115    public void loadDetails(JPanel details) {
116        log.trace("load Details");
117
118        super.loadDetails(details);
119        
120        // add a listener to the traffic controller so we will be informed if something has changed
121        ((NetBiDiBAdapter)adapter).addConnectionChangedListener(this);
122        
123//        ((NetBiDiBAdapter)adapter).addConnectionChangedListener((ActionEvent e) -> {
124//            log.debug("Update connection panel {}", e.paramString());
125//            ThreadingUtil.runOnGUIEventually(() -> {
126//                // We are probably called from an event thread.
127//                // Be sure to update the GUI on the GUI thread.
128//                log.trace("update GUI");
129//                getDeviceListData(); //update data
130//                showAdvancedItems(); //and refresh the panel
131//            });
132//        });
133        
134        // change label for port
135        portFieldLabel.setText("TCP Port");
136        
137        // remove output delay since it is not used by BiDiB
138        outputIntervalLabel.setVisible(false);
139        outputIntervalSpinner.setVisible(false);
140        outputIntervalReset.setVisible(false);
141        
142        getDeviceListData(); //get current data
143
144        // add listeners to our extra components\
145
146        // Device list Combo-Box
147        deviceListField.addActionListener((ActionEvent e) -> {
148            log.trace("devlist selection event {}", e.paramString());
149            
150            if (deviceListField.isEnabled()  &&  deviceListField.getSelectedIndex() >= 0) {
151                log.debug("device list item selected: [{}] {}", deviceListField.getSelectedIndex(), deviceListField.getSelectedItem());
152                ((NetBiDiBAdapter)adapter).selectDeviceListItem(deviceListField.getSelectedIndex());
153
154                hostNameField.setText(adapter.getHostName());
155                portField.setText(String.valueOf(adapter.getPort()));
156                log.debug("selected Unique UID: {},adName field: {}", adapter.getAdvertisementName(), adNameField.getText());
157                adNameField.setText(adapter.getAdvertisementName());
158            }
159        });
160        
161        // Device list refresh button
162        deviceListRefreshButton.addActionListener((ActionEvent e) -> {
163            log.trace("devlist refresh event {}", e.paramString());
164            getDeviceListData(); //update data
165            showAdvancedItems(); //and refresh the panel
166        });
167        
168        // Pairing button
169        pairingButton.addActionListener((ActionEvent e) -> {
170            log.trace("pairing button event {}", e.paramString());
171            NetBiDiBAdapter a = (NetBiDiBAdapter)adapter;
172            a.setPaired(!a.isConnectionReady(), (ActionEvent pe) -> {
173                log.trace("pairing action finished event {}", pe.paramString());
174                getDeviceListData(); //update data
175                showAdvancedItems(); //and refresh the panel
176            });
177        });
178        
179        // Logon/Logoff button
180        logoffButton.addActionListener((ActionEvent e) -> {
181            log.trace("logoff button event {}", e.paramString());
182            NetBiDiBAdapter a = (NetBiDiBAdapter)adapter;
183            a.setLogon(a.isDetached());
184            showAdvancedItems(); //and refresh the panel
185        });
186        
187    }
188    
189    /**
190     * connection changed action event
191     * 
192     * @param e - Action event
193     */
194    @Override
195    public void actionPerformed(ActionEvent e) {
196        log.debug("Update connection panel {}", e.paramString());
197        ThreadingUtil.runOnGUIEventually(() -> {
198            // We are probably called from an event thread.
199            // Be sure to update the GUI on the GUI thread.
200            log.trace("update GUI");
201            getDeviceListData(); //update data
202            showAdvancedItems(); //and refresh the panel
203        });
204    }
205    
206    /**
207     * {@inheritDoc}
208     */
209    @Override
210    @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST",justification = "Cast safe by design") //parameter adapter always is a NetBiDiBAdapter here
211    public int addStandardDetails(PortAdapter adapter, boolean incAdvanced, int i) {
212
213        log.trace("add Details to JPanel");
214        
215        if (showAutoConfig.isSelected()) {
216            // the device list combo box should appear first, this is before super.addStandardDetails()
217            cR.gridy = i;
218            cL.gridy = i;
219            JPanel deviceListPanel = new JPanel();
220            deviceListPanel.add(deviceListField);
221            deviceListPanel.add(deviceListRefreshButton);
222            gbLayout.setConstraints(deviceListFieldLabel, cL);
223            gbLayout.setConstraints(deviceListPanel, cR);
224            _details.add(deviceListFieldLabel);
225            _details.add(deviceListPanel);
226            i++;
227        }
228
229        i = super.addStandardDetails(adapter, incAdvanced, i);
230
231        //boolean connectionIsReady = ((NetBiDiBAdapter)adapter).isConnectionReady();
232        boolean connectionIsOpened = ((NetBiDiBAdapter)adapter).isOpened();
233        if (showAdvanced.isSelected()  ||  !connectionIsOpened) {
234            if (connectionIsOpened) {
235                pairingButton.setText(Bundle.getMessage("netBiDiBPairingButtonUnpair"));
236            }
237            else {
238                pairingButton.setText(Bundle.getMessage("netBiDiBPairingButtonPair"));
239            }
240            cR.gridy = i;
241            cL.gridy = i;
242            JPanel buttonPanel = new JPanel();
243            buttonPanel.add(pairingButton);
244            buttonPanel.add(logoffButton);
245            gbLayout.setConstraints(buttonPanel, cR);
246            _details.add(buttonPanel);
247            i++;
248        }
249
250        logoffButton.setEnabled(connectionIsOpened);
251        if (((NetBiDiBAdapter)adapter).isDetached()) {
252            logoffButton.setText(Bundle.getMessage("netBiDiBLogoffButtonLogon"));
253        }
254        else {
255            logoffButton.setText(Bundle.getMessage("netBiDiBLogoffButtonLogoff"));
256        }
257        
258        return i;
259    }
260
261
262    /**
263     * Actions to be done if network autoconfig has changed.
264     * 
265     * If autoconfig is set, disable input to the hostname field - it is then filled
266     * from the device list combo box
267     */
268    @Override
269    public void setAutoNetworkConfig() {
270        log.trace("setAutoNetworkConfig: {}", showAutoConfig.isSelected());
271        super.setAutoNetworkConfig();
272        hostNameField.setEnabled(!showAutoConfig.isSelected());
273        hostNameFieldLabel.setEnabled(!showAutoConfig.isSelected());
274        getDeviceListData(); //update data
275        showAdvancedItems(); //and refresh the panel
276    }
277
278    @Override
279    public boolean isHostNameAdvanced() {
280        return showAutoConfig.isSelected();
281    }
282
283    @Override
284    public boolean isAutoConfigPossible() {
285        return true;
286    }
287    
288    @Override
289    public void dispose() {
290        ((NetBiDiBAdapter)adapter).removeConnectionChangedListener(this); //just in case - I think this will never happen
291        super.dispose();
292    }
293    
294
295    private static final Logger log = LoggerFactory.getLogger(ConnectionConfig.class);
296
297}