001package jmri.jmrix.can.cbus.node;
002
003import java.beans.PropertyChangeListener;
004import java.beans.PropertyChangeEvent;
005import java.util.Arrays;
006
007import jmri.jmrix.can.CanSystemConnectionMemo;
008import jmri.jmrix.can.cbus.swing.modules.CbusConfigPaneProvider;
009import jmri.util.StringUtil;
010import jmri.util.ThreadingUtil;
011
012import org.slf4j.Logger;
013import org.slf4j.LoggerFactory;
014
015/**
016 * Table data model for display of CBUS Node Variables.
017 *
018 * @author Steve Young (c) 2019
019 * 
020 */
021public class CbusNodeNVTableDataModel extends javax.swing.table.AbstractTableModel 
022    implements PropertyChangeListener {
023
024    private int[] newNVs;
025    private CbusNode nodeOfInterest;
026
027    // column order needs to match list in column tooltips
028    static public final int NV_NUMBER_COLUMN = 0;
029    static public final int NV_NAME_COLUMN = 1;
030    static public final int NV_CURRENT_VAL_COLUMN = 2;
031    static public final int NV_CURRENT_HEX_COLUMN = 3;
032    static public final int NV_CURRENT_BIT_COLUMN = 4;
033    static public final int NV_SELECT_COLUMN = 5;
034    static public final int NV_SELECT_HEX_COLUMN = 6;
035    static public final int NV_SELECT_BIT_COLUMN = 7;
036    static public final int MAX_COLUMN = 8;
037
038    public CbusNodeNVTableDataModel(CanSystemConnectionMemo memo, int row, int column ) {
039        log.debug("Starting MERG CBUS Node NV Table");
040    }
041
042    /** {@inheritDoc} */
043    @Override
044    public void propertyChange(PropertyChangeEvent ev){
045        if (ev.getPropertyName().equals("SINGLENVUPDATE")) {
046            int newValue = (Integer) ev.getNewValue();
047//            resetNewNvs();    // Why do this for a single NV?
048            log.debug("SINGLENVUPDATE {}", newValue);
049            resetSingleNv(newValue);
050            fireTableRowsUpdated(newValue,newValue);
051        }
052        else if (ev.getPropertyName().equals("ALLNVUPDATE")) {
053            log.debug("ALLNVUPDATE");
054            resetNewNvs();
055            fireTableDataChanged();
056        }
057    }
058
059    /**
060     * Return the number of rows to be displayed.
061     * {@inheritDoc} 
062     */
063    @Override
064    public int getRowCount() {
065        try {
066            return nodeOfInterest.getNodeNvManager().getTotalNVs();
067        } catch (NullPointerException e) {
068            return 0;
069        }
070    }
071
072    /**
073     * {@inheritDoc} 
074     */
075    @Override
076    public int getColumnCount() {
077        return MAX_COLUMN;
078    }
079
080    /**
081     * Returns String of column name from column int
082     * used in table header
083     * {@inheritDoc}
084     * @param col int col number
085     */
086    @Override
087    public String getColumnName(int col) { // not in any order
088        switch (col) {
089            case NV_NUMBER_COLUMN:
090                return ("NV");
091            case NV_NAME_COLUMN:
092                return ("Name");
093            case NV_CURRENT_VAL_COLUMN:
094                return ("Dec.");
095            case NV_CURRENT_HEX_COLUMN:
096                return ("Hex.");
097            case NV_CURRENT_BIT_COLUMN:
098                return ("Bin.");
099            case NV_SELECT_COLUMN:
100                return ("New Dec.");
101            case NV_SELECT_HEX_COLUMN:
102                return ("New Hex.");
103            case NV_SELECT_BIT_COLUMN:
104                return("New Bin.");
105            default:
106                return "unknown " + col; // NOI18N
107        }
108    }
109
110    /**
111     * Returns column class type.
112     * {@inheritDoc}
113     */
114    @Override
115    public Class<?> getColumnClass(int col) {
116        switch (col) {
117            case NV_SELECT_HEX_COLUMN:
118            case NV_NAME_COLUMN:
119            case NV_SELECT_BIT_COLUMN:
120            case NV_CURRENT_HEX_COLUMN:
121            case NV_CURRENT_BIT_COLUMN:
122                return String.class;
123            default:
124                return Integer.class;
125        }
126    }
127
128    /**
129    * boolean return to edit table cell or not
130    * {@inheritDoc}
131    * @return boolean
132    */
133    @Override
134    public boolean isCellEditable(int row, int col) {
135        switch (col) {
136            case NV_SELECT_COLUMN:
137            case NV_SELECT_HEX_COLUMN:
138            case NV_SELECT_BIT_COLUMN:
139                return true;
140            default:
141                return false;
142        }
143    }
144
145     /**
146     * Return table values
147     * {@inheritDoc}
148     * @param row int row number
149     * @param col int col number
150     */
151    @Override
152    public Object getValueAt(int row, int col) {
153
154        if ( nodeOfInterest.getNodeNvManager().getTotalNVs() < 1 ) {
155            return null;
156        }
157
158        switch (col) {
159            case NV_NUMBER_COLUMN:
160                return (row +1);
161            case NV_NAME_COLUMN:
162                return CbusConfigPaneProvider.getProviderByNode(nodeOfInterest).getNVNameByIndex(row + 1);  // NV indices start at 1
163            case NV_CURRENT_VAL_COLUMN:
164                return nodeOfInterest.getNodeNvManager().getNV(row+1);
165            case NV_CURRENT_HEX_COLUMN:
166                if ( nodeOfInterest.getNodeNvManager().getNV(row+1) > -1 ) {
167                    return StringUtil.twoHexFromInt(nodeOfInterest.getNodeNvManager().getNV(row+1));
168                }
169                else {
170                    break;
171                }
172            case NV_CURRENT_BIT_COLUMN:
173                int num =  nodeOfInterest.getNodeNvManager().getNV(row+1);
174                if ( num > -1 ) {
175                    return (String.format("%8s", Integer.toBinaryString(num)).replace(' ', '0')).substring(0,4) + " " +
176                    (String.format("%8s", Integer.toBinaryString(num)).replace(' ', '0')).substring(4,8);
177                }
178                else {
179                    break;
180                }
181            case NV_SELECT_COLUMN:
182                if ( newNVs.length < row+1) {
183                    return 0;
184                }
185                if ( newNVs[(row+1)] > -1 ) {
186                    return newNVs[(row+1)];
187                } else {
188                    return nodeOfInterest.getNodeNvManager().getNV(row+1);
189                }
190            case NV_SELECT_HEX_COLUMN:
191                if ( newNVs.length <= row+1) {
192                    break;
193                }
194                if (newNVs[(row+1)]>-1) {
195                    return StringUtil.twoHexFromInt(newNVs[(row+1)]);
196                }
197                else {
198                    break;
199                }
200            case NV_SELECT_BIT_COLUMN:
201                if ( newNVs.length <= row+1) {
202                    break;
203                }
204                if (newNVs[(row+1)]>-1) {
205                    return (String.format("%8s", Integer.toBinaryString(newNVs[(row+1)])).replace(' ', '0')).substring(0,4) + " " +
206                        (String.format("%8s", Integer.toBinaryString(newNVs[(row+1)])).replace(' ', '0')).substring(4,8);
207                } else {
208                    break;
209                }
210            default:
211                return null;
212        }
213        return "";
214    }
215
216    /**
217     * {@inheritDoc}
218     */
219    @Override
220    public void setValueAt(Object value, int row, int col) {
221        log.debug("set value {} row {} col {}",value,row,col);
222        if ( newNVs.length ==0) {
223            return;
224        }
225        switch (col) {
226            case NV_SELECT_COLUMN:
227                int newval = (int) value;
228                newNVs[(row+1)] = newval;
229                ThreadingUtil.runOnGUIEventually(() -> fireTableRowsUpdated(row,row));
230                break;
231            case NV_SELECT_HEX_COLUMN:
232                newNVs[(row+1)] = StringUtil.getByte(0, ((String) value).trim());
233                ThreadingUtil.runOnGUIEventually(() -> fireTableRowsUpdated(row,row));
234                break;
235            case NV_SELECT_BIT_COLUMN:
236                try {
237                    int newInt = Integer.parseInt(((String) value).replaceAll("\\s+",""), 2);
238                    if (newInt > -1 && newInt < 256) {
239                        newNVs[(row+1)] = newInt;
240                        ThreadingUtil.runOnGUIEventually(() -> fireTableRowsUpdated(row,row));
241                    }
242                }
243                catch ( NumberFormatException e ){}
244                break;
245            default:
246                break;
247        }
248    }
249
250    /**
251     * Set the Node to be used in table.
252     * @param node the CbusNode of Interest to the NV Table
253     */
254    public void setNode( CbusNode node){
255        log.debug("setting array for node {}",node);
256        
257        if ( nodeOfInterest != null ) {
258            nodeOfInterest.removePropertyChangeListener(this);
259            nodeOfInterest.setliveUpdate(false);
260        }
261        
262        nodeOfInterest = node;
263        
264        if ( nodeOfInterest == null ) {
265            return;
266        }
267        
268        resetNewNvs();
269        nodeOfInterest.addPropertyChangeListener(this);
270        fireTableDataChanged();
271        
272    }
273
274    /**
275     * Get the Node being used in table.
276     * 
277     * @return the CbusNode of Interest
278     */
279    public CbusNode getNode() {
280        return nodeOfInterest;
281    }
282
283    /**
284     * Checks if a single NV has been edited to a new value
285     * @param nvToCheck the single NV to check
286     * @return true if dirty, else false
287     */
288    public boolean isSingleNvDirty( int nvToCheck ) {
289        return ( (int) getValueAt(nvToCheck,NV_CURRENT_VAL_COLUMN) ) != (
290                (int) getValueAt(nvToCheck,NV_SELECT_COLUMN) );
291    }
292
293    /**
294     * Checks if any NV has been edited to a new value
295     * @return true if any NV has been edited, else false
296     */
297    public boolean isTableDirty() {
298        try {
299            for (int i = 0; i < getRowCount(); i++) {            
300                if ( isSingleNvDirty(i) ) {
301                    return true;
302                }
303            }
304            return false;
305        }
306        catch ( NullPointerException e ){
307            return false;
308        }
309    }
310
311    /**
312     * Get count of changed NVs.
313     * @return number of changed NVs
314     */
315    public int getCountDirty() {
316        int count = 0;
317        for (int i = 0; i < getRowCount(); i++) {            
318            if ( isSingleNvDirty(i) ) {
319                count++;
320            }
321        }
322        return count;
323    }
324
325    /**
326     * Resets the edit NV value to match the actual NV value.
327     */
328    public void resetNewNvs() {
329        
330        // setup a new fixed length array to hold new nv values
331        if ( nodeOfInterest.getNodeNvManager().getNvArray() == null ) {
332            log.debug("Create newNVs[] of length 0");
333            newNVs = new int[0];
334        }
335        else {
336            log.debug("Create newNVs[] of length {}", nodeOfInterest.getNodeNvManager().getNvArray().length);
337            newNVs = new int[ ( nodeOfInterest.getNodeNvManager().getNvArray().length ) ];        
338            newNVs = Arrays.copyOf(
339                nodeOfInterest.getNodeNvManager().getNvArray(),
340                nodeOfInterest.getNodeNvManager().getNvArray().length);
341        }
342        
343        for (int i = 0; i < getRowCount(); i++) {
344            
345            setValueAt( getValueAt(i,NV_CURRENT_VAL_COLUMN), i, NV_SELECT_COLUMN);
346        }
347    }
348
349    /**
350     * Resets a single edit NV value to match the actual NV value.
351     * 
352     * @param row row to reset
353     */
354    public void resetSingleNv(int row) {
355        setValueAt( getValueAt(row,NV_CURRENT_VAL_COLUMN), row, NV_SELECT_COLUMN);
356    }
357
358    /**
359     * Get a backup node containing the edited NVs.
360     * @return a node which has the new NV's
361     */
362    public CbusNodeFromBackup getChangedNode(){
363        CbusNodeFromBackup temp = new CbusNodeFromBackup(nodeOfInterest,null);
364        temp.getNodeNvManager().setNVs(newNVs);
365        return temp;
366    }
367
368    /**
369     * De-registers the NV Table from receiving updates from the CbusNode.
370     */
371    public void dispose(){
372        if ( nodeOfInterest != null ) {
373            nodeOfInterest.removePropertyChangeListener(this);
374            nodeOfInterest.setliveUpdate(false);
375        }
376    }
377
378    private final static Logger log = LoggerFactory.getLogger(CbusNodeNVTableDataModel.class);
379}