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    public static final int NV_NUMBER_COLUMN = 0;
029    public static final int NV_NAME_COLUMN = 1;
030    public static final int NV_CURRENT_VAL_COLUMN = 2;
031    public static final int NV_CURRENT_HEX_COLUMN = 3;
032    public static final int NV_CURRENT_BIT_COLUMN = 4;
033    public static final int NV_SELECT_COLUMN = 5;
034    public static final int NV_SELECT_HEX_COLUMN = 6;
035    public static final int NV_SELECT_BIT_COLUMN = 7;
036    public static 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 || row < 0 || row + 1 >= newNVs.length ) {
223            return;
224        }
225        int newval;
226        switch (col) {
227            case NV_SELECT_COLUMN:
228                newval = (int) value;
229                break;
230            case NV_SELECT_HEX_COLUMN:
231                newval = StringUtil.getByte(0, ((String) value).trim());
232                break;
233            case NV_SELECT_BIT_COLUMN:
234                try {
235                    newval = Integer.parseInt(((String) value).replaceAll("\\s+",""), 2);
236                }
237                catch ( NumberFormatException e ){
238                    return;
239                }
240                if (newval < 0 || newval > 255) {
241                    return;
242                }
243                break;
244            default:
245                return;
246        }
247        if ( newNVs[(row+1)] == newval ) {
248            // no change, do not trigger table updates
249            return;
250        }
251        newNVs[(row+1)] = newval;
252        ThreadingUtil.runOnGUIEventually(() -> fireTableRowsUpdated(row,row));
253    }
254
255    /**
256     * Set the Node to be used in table.
257     * @param node the CbusNode of Interest to the NV Table
258     */
259    public void setNode( CbusNode node){
260        log.debug("setting array for node {}",node);
261        
262        if ( nodeOfInterest != null ) {
263            nodeOfInterest.removePropertyChangeListener(this);
264            nodeOfInterest.setliveUpdate(false);
265        }
266        
267        nodeOfInterest = node;
268        
269        if ( nodeOfInterest == null ) {
270            return;
271        }
272        
273        resetNewNvs();
274        nodeOfInterest.addPropertyChangeListener(this);
275        fireTableDataChanged();
276        
277    }
278
279    /**
280     * Get the Node being used in table.
281     * 
282     * @return the CbusNode of Interest
283     */
284    public CbusNode getNode() {
285        return nodeOfInterest;
286    }
287
288    /**
289     * Checks if a single NV has been edited to a new value
290     * @param nvToCheck the single NV to check
291     * @return true if dirty, else false
292     */
293    public boolean isSingleNvDirty( int nvToCheck ) {
294        return ( (int) getValueAt(nvToCheck,NV_CURRENT_VAL_COLUMN) ) != (
295                (int) getValueAt(nvToCheck,NV_SELECT_COLUMN) );
296    }
297
298    /**
299     * Checks if any NV has been edited to a new value
300     * @return true if any NV has been edited, else false
301     */
302    public boolean isTableDirty() {
303        try {
304            for (int i = 0; i < getRowCount(); i++) {            
305                if ( isSingleNvDirty(i) ) {
306                    return true;
307                }
308            }
309            return false;
310        }
311        catch ( NullPointerException e ){
312            return false;
313        }
314    }
315
316    /**
317     * Get count of changed NVs.
318     * @return number of changed NVs
319     */
320    public int getCountDirty() {
321        int count = 0;
322        for (int i = 0; i < getRowCount(); i++) {            
323            if ( isSingleNvDirty(i) ) {
324                count++;
325            }
326        }
327        return count;
328    }
329
330    /**
331     * Resets the edit NV value to match the actual NV value.
332     */
333    public void resetNewNvs() {
334        
335        // setup a new fixed length array to hold new nv values
336        if ( nodeOfInterest.getNodeNvManager().getNvArray() == null ) {
337            log.debug("Create newNVs[] of length 0");
338            newNVs = new int[0];
339        }
340        else {
341            log.debug("Create newNVs[] of length {}", nodeOfInterest.getNodeNvManager().getNvArray().length);
342            newNVs = new int[ ( nodeOfInterest.getNodeNvManager().getNvArray().length ) ];        
343            newNVs = Arrays.copyOf(
344                nodeOfInterest.getNodeNvManager().getNvArray(),
345                nodeOfInterest.getNodeNvManager().getNvArray().length);
346        }
347        
348        for (int i = 0; i < getRowCount(); i++) {
349            
350            setValueAt( getValueAt(i,NV_CURRENT_VAL_COLUMN), i, NV_SELECT_COLUMN);
351        }
352    }
353
354    /**
355     * Resets a single edit NV value to match the actual NV value.
356     * 
357     * @param row row to reset
358     */
359    public void resetSingleNv(int row) {
360        setValueAt( getValueAt(row,NV_CURRENT_VAL_COLUMN), row, NV_SELECT_COLUMN);
361    }
362
363    /**
364     * Get a backup node containing the edited NVs.
365     * @return a node which has the new NV's
366     */
367    public CbusNodeFromBackup getChangedNode(){
368        CbusNodeFromBackup temp = new CbusNodeFromBackup(nodeOfInterest,null);
369        temp.getNodeNvManager().setNVs(newNVs);
370        return temp;
371    }
372
373    /**
374     * De-registers the NV Table from receiving updates from the CbusNode.
375     */
376    public void dispose(){
377        if ( nodeOfInterest != null ) {
378            nodeOfInterest.removePropertyChangeListener(this);
379            nodeOfInterest.setliveUpdate(false);
380        }
381    }
382
383    private static final Logger log = LoggerFactory.getLogger(CbusNodeNVTableDataModel.class);
384}