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