001package jmri.jmrix.can.cbus.node;
002
003import java.util.ArrayList;
004import java.util.Arrays;
005
006import javax.annotation.CheckForNull;
007import javax.swing.JTable;
008import javax.swing.JTextField;
009
010import jmri.jmrix.can.CanSystemConnectionMemo;
011import jmri.jmrix.can.cbus.swing.nodeconfig.CbusNodeEditEventFrame;
012import jmri.util.ThreadingUtil;
013
014import org.slf4j.Logger;
015import org.slf4j.LoggerFactory;
016
017/**
018 * Table data model for display of CBUS Node Single Events
019 *
020 * @author Steve Young (c) 2019
021 * 
022 */
023public class CbusNodeSingleEventTableDataModel extends javax.swing.table.AbstractTableModel {
024
025    public int[] newEVs;
026    private final CbusNodeEvent _ndEv;
027    private final CanSystemConnectionMemo _memo;
028    
029    // column order needs to match list in column tooltips
030    static public final int EV_NUMBER_COLUMN = 0;
031    static public final int EV_CURRENT_VAL_COLUMN = 1;
032    static public final int EV_CURRENT_HEX_COLUMN = 2;
033    static public final int EV_CURRENT_BIT_COLUMN = 3;
034    static public final int EV_SELECT_COLUMN = 4;
035    static public final int EV_SELECT_HEX_COLUMN = 5;
036    static public final int EV_SELECT_BIT_COLUMN = 6;
037    static public final int MAX_COLUMN = 7;
038
039    public CbusNodeSingleEventTableDataModel(CanSystemConnectionMemo memo, int row, int column , CbusNodeEvent ndEv) {
040        
041        log.debug("Starting a Single Node Event Variable Model");
042        _memo = memo;
043        _ndEv = ndEv;
044        if ( _ndEv.getEvVarArray() == null ) {
045            newEVs = new int[0];
046        }
047        else {
048            newEVs = new int[ ( _ndEv.getEvVarArray().length ) ];
049            log.debug(" set node newEVs length {} ",newEVs.length);
050        
051            newEVs = Arrays.copyOf(
052                _ndEv.getEvVarArray(),
053                _ndEv.getEvVarArray().length);
054            log.debug(" set ev var arr length {} data {}",newEVs.length, newEVs);
055        }
056        setTableModel();
057    }
058    
059    public final void setTableModel(){
060        _ndEv.setEditTableModel(this);
061    }
062    
063    /**
064     * {@inheritDoc}
065     */
066    @Override
067    public int getRowCount() {
068        return _ndEv.getNumEvVars();
069    }
070
071    /**
072     * {@inheritDoc}
073     */
074    @Override
075    public int getColumnCount() {
076        return MAX_COLUMN;
077    }
078    
079    /**
080     * Configure a table to have our standard rows and columns.
081     * <p>
082     * This is optional, in that other table formats can use this table model.
083     * But we put it here to help keep it consistent.
084     * @param eventTable Table to configure
085     */
086    public void configureTable(JTable eventTable) {
087        // allow reordering of the columns
088        eventTable.getTableHeader().setReorderingAllowed(true);
089
090        // shut off autoResizeMode to get horizontal scroll to work (JavaSwing p 541)
091        eventTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
092
093        // resize columns as requested
094        for (int i = 0; i < eventTable.getColumnCount(); i++) {
095            int width = getPreferredWidth(i);
096            eventTable.getColumnModel().getColumn(i).setPreferredWidth(width);
097        }
098        eventTable.sizeColumnsToFit(-1);
099    }
100
101    /**
102     * Returns String of column name from column int
103     * used in table header
104     * @param col int col number
105     */
106    @Override
107    public String getColumnName(int col) { // not in any order
108        switch (col) {
109            case EV_NUMBER_COLUMN:
110                return ("EV Var");
111            case EV_CURRENT_VAL_COLUMN:
112                return ("Dec.");
113            case EV_CURRENT_HEX_COLUMN:
114                return ("Hex.");
115            case EV_CURRENT_BIT_COLUMN:
116                return ("Bin.");
117            case EV_SELECT_COLUMN:
118                return ("New Dec.");
119            case EV_SELECT_HEX_COLUMN:
120                return ("New Hex.");
121            case EV_SELECT_BIT_COLUMN:
122                return("New Bin.");
123            default:
124                return "unknown " + col; // NOI18N
125        }
126    }
127
128    /**
129    * Returns int of startup column widths
130    * @param col int col number
131    * @return preferred initial width
132    */
133    public static int getPreferredWidth(int col) {
134        switch (col) {
135            case EV_NUMBER_COLUMN:
136            case EV_CURRENT_BIT_COLUMN:
137            case EV_SELECT_COLUMN:
138            case EV_SELECT_HEX_COLUMN:
139            case EV_SELECT_BIT_COLUMN:
140                return new JTextField(6).getPreferredSize().width;
141            case EV_CURRENT_VAL_COLUMN:
142            case EV_CURRENT_HEX_COLUMN:
143                return new JTextField(4).getPreferredSize().width;
144            default:
145                return new JTextField(" <unknown> ").getPreferredSize().width; // NOI18N
146        }
147    }
148    
149    /**
150     * {@inheritDoc}
151     */
152    @Override
153    public Class<?> getColumnClass(int col) {
154        switch (col) {
155            case EV_SELECT_HEX_COLUMN:
156            case EV_SELECT_BIT_COLUMN:
157            case EV_CURRENT_HEX_COLUMN:
158            case EV_CURRENT_BIT_COLUMN:
159                return String.class;
160            default:
161                return Integer.class;
162        }
163    }
164    
165    /**
166     * {@inheritDoc}
167     */
168    @Override
169    public boolean isCellEditable(int row, int col) {
170        switch (col) {
171            case EV_SELECT_COLUMN:
172            case EV_SELECT_HEX_COLUMN:
173            case EV_SELECT_BIT_COLUMN:
174                return true;
175            default:
176                return false;
177        }
178    }
179
180    /**
181     * {@inheritDoc}
182     */
183    @Override
184    public Object getValueAt(int row, int col) {
185        
186        int currEvVal = _ndEv.getEvVar(row+1);
187        
188        switch (col) {
189            case EV_NUMBER_COLUMN:
190                return (row +1);
191            case EV_CURRENT_VAL_COLUMN:
192                if ( ( newEVs[(row)] < 0 ) && ( currEvVal > -1 )){
193                    newEVs[(row)] = currEvVal;
194                }
195                return currEvVal;
196            case EV_CURRENT_HEX_COLUMN:
197                if ( currEvVal > -1 ) {
198                    return jmri.util.StringUtil.twoHexFromInt(currEvVal);
199                }
200                else {
201                    return currEvVal;
202                }
203            case EV_CURRENT_BIT_COLUMN:
204                if ( currEvVal > -1 ) {
205                    return (String.format("%8s", Integer.toBinaryString(currEvVal)).replace(' ', '0')).substring(0,4) + " " +
206                    (String.format("%8s", Integer.toBinaryString(currEvVal)).replace(' ', '0')).substring(4,8);
207                }
208                else {
209                    return currEvVal;
210                }
211            case EV_SELECT_COLUMN:
212                if ( newEVs[(row)] > -1 ) {
213                    return newEVs[(row)];
214                } else {
215                    return currEvVal;
216                }
217            case EV_SELECT_HEX_COLUMN:
218                if ( newEVs[(row)] != currEvVal ) {
219                    return jmri.util.StringUtil.twoHexFromInt(newEVs[(row)]);
220                } else {
221                    return "";
222                }
223            case EV_SELECT_BIT_COLUMN:
224                if ( newEVs[(row)] != currEvVal ) {
225                    return (String.format("%8s", Integer.toBinaryString(newEVs[(row)])).replace(' ', '0')).substring(0,4) + " " +
226                        (String.format("%8s", Integer.toBinaryString(newEVs[(row)])).replace(' ', '0')).substring(4,8);
227                }
228                else {
229                    return "";
230                }
231            default:
232                return null;
233        }
234    }
235    
236    /**
237     * {@inheritDoc}
238     */
239    @Override
240    public void setValueAt(Object value, int row, int col) {
241        log.debug("set value {} row {} col {}",value,row,col);
242        switch (col) {
243            case EV_SELECT_COLUMN:
244                newEVs[(row)] = (int) value;
245                updateFromNode(row,col);
246                break;
247            case EV_SELECT_HEX_COLUMN:
248                newEVs[(row)] = jmri.util.StringUtil.getByte(0, ((String) value).trim());
249                ThreadingUtil.runOnGUIEventually(() -> fireTableRowsUpdated(row,row));
250                break;
251            case EV_SELECT_BIT_COLUMN:
252                try {
253                    int newInt = Integer.parseInt(((String) value).replaceAll("\\s+",""), 2);
254                    if (newInt > -1 && newInt < 256) {
255                        newEVs[(row)] = newInt;
256                        ThreadingUtil.runOnGUIEventually(() -> fireTableRowsUpdated(row,row));
257                    }
258                }
259                catch ( NumberFormatException e ){}
260                break;
261            default:
262                break;
263        }
264    }
265    
266    public void updateFromNode( int arrayid, int col){
267        fireTableDataChanged();
268    }
269    
270    public boolean isTableLoaded(){
271        if ( getRowCount() < 1 ) {
272            return false;
273        }
274        try {
275            for (int i = 0; i < getRowCount(); i++) {            
276                if ( ( (int) getValueAt(i,EV_CURRENT_VAL_COLUMN) ) < 0 ) {
277                    return false;
278                }
279            }
280            return true;
281        }
282        catch ( NullPointerException e ){
283            return false;
284        }
285    }
286    
287    public boolean isSingleEvDirty( int evToCheck ) {
288        return ( (int) getValueAt(evToCheck,EV_CURRENT_VAL_COLUMN) ) != (
289            (int) getValueAt(evToCheck,EV_SELECT_COLUMN) );
290    }
291    
292    public boolean isTableDirty() {
293        try {
294            for (int i = 0; i < getRowCount(); i++) {            
295                if ( isSingleEvDirty(i) ) {
296                    return true;
297                }
298            }
299            return false;
300        }
301        catch ( NullPointerException e ){
302            return false;
303        }
304    }
305    
306    public int getCountDirty() {
307        int count = 0;
308        for (int i = 0; i < getRowCount(); i++) {            
309            if ( isSingleEvDirty(i) ) {
310                count++;
311            }
312        }
313        return count;
314    }
315    
316    public void resetnewEVs() {
317        for (int i = 0; i < getRowCount(); i++) {
318            setValueAt( getValueAt(i,EV_CURRENT_VAL_COLUMN), i, EV_SELECT_COLUMN);
319        }
320    }
321
322    @CheckForNull
323    private CbusNode getEventNode(){
324        CbusNodeTableDataModel nodeModel = _memo.get(CbusNodeTableDataModel.class);
325        return nodeModel.getNodeByNodeNum( _ndEv.getParentNn() );
326    }
327    
328    public void passNewEvToNode ( CbusNodeEditEventFrame frame ) {
329        CbusNodeEvent newevent = new CbusNodeEvent( _memo,
330                frame.getNodeVal(),
331                frame.getEventVal(),
332                _ndEv.getParentNn(),
333                -1,
334                _ndEv.getNumEvVars() );
335        
336        newevent.setEvArr(newEVs);
337        
338        ArrayList<CbusNodeEvent> eventArray = new ArrayList<>(1);
339        eventArray.add(newevent);
340        log.debug(" pass changes arr length {} ",newEVs.length);
341
342        CbusNode tmpNode = getEventNode();
343        if (tmpNode!=null) {
344            tmpNode.getNodeEventManager().sendNewEvSToNode( eventArray );
345        }
346    }
347
348    public void passEditEvToNode( CbusNodeEditEventFrame frame ) {
349        if ( frame.spinnersDirty() ) {
350            CbusNode tmpNode = getEventNode();
351            if (tmpNode!=null) {
352                tmpNode.getNodeEventManager().deleteEvOnNode(_ndEv.getNn(), _ndEv.getEn() );
353            }
354            
355            // learn mode - to reset after unlearn, timeout, no feedback from node
356            // teach new event ( as brand new event )
357            // notify frame
358            ThreadingUtil.runOnLayoutDelayed( () -> {
359                passNewEvToNode(frame);
360            }, 200 );
361            
362        } else {
363            // loop through each ev var and send
364            passNewEvToNode(frame);
365        }
366    }
367    
368    public void dispose(){
369        _ndEv.setEditTableModel(null);
370    }
371    
372    private final static Logger log = LoggerFactory.getLogger(CbusNodeSingleEventTableDataModel.class);
373}