001package jmri.jmrit.symbolicprog;
002
003import java.awt.event.ActionEvent;
004import java.beans.PropertyChangeEvent;
005import java.beans.PropertyChangeListener;
006
007import javax.swing.AbstractAction;
008import javax.swing.JLabel;
009
010import jmri.jmrit.roster.RosterEntry;
011import jmri.jmrit.symbolicprog.tabbedframe.PaneProgFrame;
012import jmri.jmrix.can.CanSystemConnectionMemo;
013import jmri.util.swing.JmriJOptionPane;
014
015import org.openlcb.OlcbInterface;
016import org.openlcb.NodeID;
017import org.openlcb.cdi.impl.ConfigRepresentation;
018
019/**
020 * Action to download the function labels from a TCS CS-105 to a roster entry
021 *
022 * @author Bob Jacobsen Copyright (C) 2003, 2023
023 * @author Dave Heap Copyright (C) 2015
024 */
025public class TcsDownloadAction extends AbstractAction implements PropertyChangeListener {
026
027    public TcsDownloadAction(String actionName, CvTableModel pModel, VariableTableModel vModel, PaneProgFrame pParent, JLabel pStatus, RosterEntry re) {
028        super(actionName);
029        this.cvTable = pModel;
030        this.vModel = vModel;
031        this.rosterEntry = re;
032        this.frame = pParent;
033    }
034
035    // will this be enabled if created?
036    static public boolean willBeEnabled() {
037        // see if there's an openlcb connection
038        var cscm = getSystemConnectionMemo();
039        if (cscm == null) {
040            return false;
041        }
042        if (cscm.get(org.openlcb.NodeID.class) == null) {
043            return false;
044        }
045        return true;
046    }
047
048    static CanSystemConnectionMemo getSystemConnectionMemo() {
049        return jmri.InstanceManager.getNullableDefault(CanSystemConnectionMemo.class);
050    }
051
052    PaneProgFrame frame;
053    RosterEntry rosterEntry;
054    CvTableModel cvTable;
055    VariableTableModel vModel;
056    ConfigRepresentation configRep;
057
058    @Override
059    public void actionPerformed(ActionEvent e) {
060        boolean isLong = cvTable.holdsLongAddress();
061        int addr = cvTable.holdsAddress();
062        log.debug("computed address is {} long: {}", addr, isLong);
063
064        // Create the train's node ID from current values in GUI
065        byte upperAddressByte = (byte) (isLong ? (192+(addr>>8)) : 0);
066        byte lowerAddressByte = (byte) (addr & 0xFF);
067        var nodeID = new NodeID(new byte[]{6,1,0,0,upperAddressByte, lowerAddressByte});
068        log.debug("node ID {}", nodeID);
069
070        // check for Node ID already known
071        var nodeStore = getSystemConnectionMemo().get(org.openlcb.MimicNodeStore.class);
072        var nodeMemo = nodeStore.findNode(nodeID);
073        if (nodeMemo == null) {
074            JmriJOptionPane.showMessageDialog(frame, "Entry "+addr+" not found in CS-105, canceling");
075            return;
076        }
077
078        // get the CD/CDI information
079        configRep = new ConfigRepresentation(getSystemConnectionMemo().get(OlcbInterface.class),nodeID);
080        configRep.addPropertyChangeListener(this);
081
082    }
083
084    @Override
085    public void propertyChange(PropertyChangeEvent event) {
086        switch (event.getPropertyName()) {
087            case ConfigRepresentation.UPDATE_STATE :
088                // Normal. Indicates that the load is proceeding.
089            case ConfigRepresentation.UPDATE_REP :
090                // Normal, CDI is read in, loading caches next
091                return;
092            case ConfigRepresentation.UPDATE_CACHE_COMPLETE :
093                log.debug("CDI read done");
094
095                // look for values
096                processValuesToGUI();
097                return;
098            default:
099                log.error("Unexpected PropertyChangeEvent {}", event);
100                return;
101        }
102    }
103
104    /**
105     * Construct and execute a listener that processses
106     * the relevant CDI elements into the Roster and Function Label
107     * GUI elements.
108     */
109    void processValuesToGUI() {
110        configRep.visit(new ConfigRepresentation.Visitor() {
111            @Override
112            public void visitString(ConfigRepresentation.StringEntry e) {
113                log.trace("String entry {} is {}", e.key, e.getValue());
114
115                if (e.key.startsWith("Train.User Description")) {
116                    log.info("setComment {}", e.getValue());
117                    frame.getRosterPane().setComment(e.getValue());
118                 } else if (e.key.startsWith("Train.Functions")) {
119                    int index = getNumberField(e.key);
120                    if (index == -1) {
121                        log.warn("Unexpected format \"{}\"", e.key);
122                        return;
123                    }
124                    if (e.key.endsWith("Description")) {
125                        String value = e.getValue();
126                        if (value==null) {
127                            value = "";
128                        }
129                        // Display has already written contents
130                        // to this.  If content here is empty, we defer to that;
131                        // if there are content here, overrides what Display wrote.
132                        if (!value.isEmpty()) {
133                            frame.getFnLabelPane().getLabel(index+1).setText(value);
134                            log.trace("Description sets {} {} {}", index, e.getValue(), value);
135                        }
136                    } else {
137                        log.warn("Unexpected content \"{}\"", e.key);
138                    }
139                }
140            }
141
142            // TODO: Have to update the Function Pane contents on every change
143            //       so that the data is present for viewing and saving
144            @Override
145            public void visitInt(ConfigRepresentation.IntegerEntry e) {
146                log.trace("Integer entry {} is {}", e.key, e.getValue());
147
148                // is this the last entry?
149                if (e.key.startsWith("Train.Delete From Roster")) {
150                    // TODO: This is firing much too soon
151                    JmriJOptionPane.showMessageDialog(frame, "Download complete.");
152                } else if (e.key.startsWith("Train.Functions")) {
153                    int index = getNumberField(e.key);
154                    if (index == -1) {
155                        log.warn("Unexpected format \"{}\"", e.key);
156                        return;
157                    }
158                    if (e.key.endsWith(".Momentary")) {
159                        boolean lockable = (e.getValue() == 0);
160                        frame.getFnLabelPane().getLockable(index+1).setSelected(lockable);
161                    } else if (e.key.endsWith(".Consist Behavior")) {
162                        // process consist bit
163                        // first, see if function variable exists
164                        var variable = vModel.findVar("Consist Address Active For F"+(index+1));
165                        if (variable != null) {
166                            // it exists, so we transfer that to the consist info
167                            int value = (int)e.getValue();
168                            variable.setIntValue(value);
169                            log.trace("Set Consist Address Active For F{} to {}", (index+1), value);
170                        } else {
171                            log.trace("Variable Consist Address Active For F{} not found", (index+1) );
172                        }
173                    } else if (e.key.endsWith(".Display")) {
174                        // do a reverse lookup and store every time,
175                        // will be overwritten by Description if needed
176                        var description = TcsImporter.unpackDescription("", ""+e.getValue());
177                        log.trace("Display sets {} {} {}", index, e.getValue(), description);
178                        frame.getFnLabelPane().getLabel(index+1).setText(description);
179                    } else {
180                        log.warn("Unexpected content \"{}\"", e.key);
181                    }
182                }
183            }
184
185            @Override
186            public void visitEvent(ConfigRepresentation.EventEntry e) {
187                log.trace("Event entry {} is {}", e.key, e.getValue());
188            }
189        });
190    }
191
192    // Extract the number from e.g. Train.Functions(25).Momentary
193    static int getNumberField(String value) {
194        int first = value.indexOf("(");
195        int last = value.indexOf(")");
196        if (first > 0 && last > 0 && last > first + 1) {
197            var digits = value.substring(first+1, last);
198            return Integer.parseInt(digits);
199        }
200        return -1;
201    }
202
203    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TcsDownloadAction.class);
204
205}