001package jmri.jmrix.can.cbus.swing.eventrequestmonitor;
002
003import javax.swing.JButton;
004import javax.swing.JCheckBox;
005import javax.swing.JOptionPane;
006import java.util.ArrayList;
007import java.util.Date;
008import jmri.jmrix.can.CanListener;
009import jmri.jmrix.can.CanMessage;
010import jmri.jmrix.can.CanReply;
011import jmri.jmrix.can.CanSystemConnectionMemo;
012import jmri.jmrix.can.cbus.CbusMessage;
013import jmri.jmrix.can.cbus.CbusNameService;
014import jmri.jmrix.can.cbus.CbusOpCodes;
015import jmri.jmrix.can.TrafficController;
016import jmri.jmrix.can.cbus.CbusEvent;
017import jmri.util.swing.TextAreaFIFO;
018import jmri.util.ThreadingUtil;
019
020// import org.slf4j.Logger;
021// import org.slf4j.LoggerFactory;
022
023/**
024 * Table data model for display of Cbus request events
025 *
026 * @author Steve Young (c) 2018
027 * 
028 */
029public class CbusEventRequestDataModel extends javax.swing.table.AbstractTableModel implements CanListener {
030
031    private boolean sessionConfirmDeleteRow=true; // display confirm popup
032    private final int _defaultFeedback= 1;
033    protected int _contype=0; // event table pane console message type
034    protected String _context; // event table pane console text
035    private final int _defaultfeedbackdelay = 4000;
036    private static final int MAX_LINES = 500; // tablefeedback screen log size
037    
038    protected ArrayList<CbusEventRequestMonitorEvent> _mainArray;
039    private final CbusNameService nameService;
040    protected TextAreaFIFO tablefeedback;
041    // private CanSystemConnectionMemo _memo;
042    private final TrafficController tc;
043    
044    // column order needs to match list in column tooltips
045    static public final int EVENT_COLUMN = 0; 
046    static public final int NODE_COLUMN = 1; 
047    static public final int NAME_COLUMN = 2;
048    static public final int LATEST_TIMESTAMP_COLUMN = 3;
049    static public final int STATUS_REQUEST_BUTTON_COLUMN = 4;
050    static public final int LASTFEEDBACK_COLUMN = 5;
051    static public final int FEEDBACKOUTSTANDING_COLUMN = 6;
052    static public final int FEEDBACKREQUIRED_COLUMN = 7;
053    static public final int FEEDBACKTIMEOUT_COLUMN = 8;
054    static public final int FEEDBACKEVENT_COLUMN = 9;
055    static public final int FEEDBACKNODE_COLUMN = 10;
056    static public final int DELETE_BUTTON_COLUMN = 11;
057    
058    static public final int MAX_COLUMN = 12;
059
060    CbusEventRequestDataModel(CanSystemConnectionMemo memo, int row, int column) {
061        
062        _mainArray = new ArrayList<>();
063        tablefeedback = new TextAreaFIFO(MAX_LINES);
064        // _memo = memo;
065        tc = memo.getTrafficController();
066        addTc(tc);
067        nameService = new CbusNameService(memo);
068    }
069
070    // order needs to match column list top of dtabledatamodel
071    static protected final String[] columnToolTips = {
072        Bundle.getMessage("EventColTip"),
073        Bundle.getMessage("NodeColTip"),
074        Bundle.getMessage("NameColTip"),
075        Bundle.getMessage("ColumnLastHeard") + Bundle.getMessage("TypeColTip"),
076        Bundle.getMessage("ColumnRequestStatusTip"),
077        Bundle.getMessage("FBLastTip"),        
078        Bundle.getMessage("FBOutstandingTip"),
079        Bundle.getMessage("FBNumTip"),
080        Bundle.getMessage("FBTimeoutTip"),
081        Bundle.getMessage("FBEventTip"),
082        Bundle.getMessage("FBNodeTip"),
083        Bundle.getMessage("ColumnEventDeleteTip")
084
085    }; // Length = number of items in array should (at least) match number of columns
086    
087    /**
088     * Return the number of rows to be displayed.
089     */
090    @Override
091    public int getRowCount() {
092        return _mainArray.size();
093    }
094
095    @Override
096    public int getColumnCount() {
097        return MAX_COLUMN;
098    }
099    
100    /**
101     * Returns String of column name from column int
102     * used in table header
103     * @param col int col number
104     */
105    @Override
106    public String getColumnName(int col) { // not in any order
107        switch (col) {
108            case NODE_COLUMN:
109                return Bundle.getMessage("CbusNode");
110            case NAME_COLUMN:
111                return Bundle.getMessage("ColumnName");
112            case EVENT_COLUMN:
113                return Bundle.getMessage("CbusEvent");
114            case DELETE_BUTTON_COLUMN:
115                return Bundle.getMessage("ColumnEventDelete");
116            case STATUS_REQUEST_BUTTON_COLUMN:
117                return Bundle.getMessage("ColumnStatusRequest");
118            case LATEST_TIMESTAMP_COLUMN:
119                return Bundle.getMessage("ColumnLastHeard");
120            case LASTFEEDBACK_COLUMN:
121                return Bundle.getMessage("FBLast");                
122            case FEEDBACKREQUIRED_COLUMN:
123                return Bundle.getMessage("FBRequired");
124            case FEEDBACKOUTSTANDING_COLUMN:
125                return Bundle.getMessage("FBOutstanding");
126            case FEEDBACKEVENT_COLUMN:
127                return Bundle.getMessage("FBEvent");
128            case FEEDBACKNODE_COLUMN:
129                return Bundle.getMessage("FBNode");
130            case FEEDBACKTIMEOUT_COLUMN:
131                return Bundle.getMessage("FBTimeout");
132            default:
133                return "unknown"; // NOI18N
134        }
135    }
136
137    /**
138    * Returns column class type.
139    * {@inheritDoc} 
140    */
141    @Override
142    public Class<?> getColumnClass(int col) {
143        switch (col) {
144            case EVENT_COLUMN:
145            case NODE_COLUMN:
146            case FEEDBACKREQUIRED_COLUMN:
147            case FEEDBACKOUTSTANDING_COLUMN:
148            case FEEDBACKEVENT_COLUMN:
149            case FEEDBACKNODE_COLUMN:
150            case FEEDBACKTIMEOUT_COLUMN:
151                return Integer.class;
152            case NAME_COLUMN:
153                return String.class;
154            case DELETE_BUTTON_COLUMN:
155            case STATUS_REQUEST_BUTTON_COLUMN:
156                return JButton.class;
157            case LATEST_TIMESTAMP_COLUMN:
158                return Date.class;
159            case LASTFEEDBACK_COLUMN:
160                return Enum.class;
161            default:
162                return null;
163        }
164    }
165    
166    /**
167    * Boolean return to edit table cell or not
168    * {@inheritDoc} 
169    * @return boolean
170    */
171    @Override
172    public boolean isCellEditable(int row, int col) {
173        switch (col) {
174            case DELETE_BUTTON_COLUMN:
175            case STATUS_REQUEST_BUTTON_COLUMN:
176            case FEEDBACKREQUIRED_COLUMN:
177            case FEEDBACKEVENT_COLUMN:
178            case FEEDBACKNODE_COLUMN:
179            case FEEDBACKTIMEOUT_COLUMN:
180                return true;
181            default:
182                return false;
183        }
184    }
185
186     /**
187     * Return table values
188     * @param row int row number
189     * @param col int col number
190     */
191    @Override
192    public Object getValueAt(int row, int col) {
193        switch (col) {
194            case NODE_COLUMN:
195                return  _mainArray.get(row).getNn();
196            case EVENT_COLUMN:
197                return _mainArray.get(row).getEn();
198            case NAME_COLUMN:
199                return nameService.getEventName(_mainArray.get(row).getNn(),_mainArray.get(row).getEn() );
200            case STATUS_REQUEST_BUTTON_COLUMN:
201                return Bundle.getMessage("StatusButton");
202            case DELETE_BUTTON_COLUMN:
203                return Bundle.getMessage("ButtonDelete");
204            case LATEST_TIMESTAMP_COLUMN:
205                return _mainArray.get(row).getDate();
206            case LASTFEEDBACK_COLUMN:
207                return _mainArray.get(row).getLastFb();
208            case FEEDBACKREQUIRED_COLUMN:
209                return _mainArray.get(row).getFeedbackTotReqd();
210            case FEEDBACKOUTSTANDING_COLUMN:
211                return _mainArray.get(row).getFeedbackOutstanding();
212            case FEEDBACKEVENT_COLUMN:
213                return _mainArray.get(row).getExtraEvent();
214            case FEEDBACKNODE_COLUMN:
215                return _mainArray.get(row).getExtraNode();
216            case FEEDBACKTIMEOUT_COLUMN:
217                return _mainArray.get(row).getFeedbackTimeout();
218            default:
219                return null;
220        }
221    }
222    
223    /**
224     * Capture new comments or node names.
225     * Button events
226     * @param value object value
227     * @param row int row number
228     * @param col int col number
229     */
230    @Override
231    public void setValueAt(Object value, int row, int col) {
232        // log.debug("427 set valueat called row: {} col: {}", row, col);
233        switch (col) {
234            case DELETE_BUTTON_COLUMN:
235                buttonDeleteClicked(row);
236                break;
237            case STATUS_REQUEST_BUTTON_COLUMN:
238                _mainArray.get(row).sendEvent(CbusEventRequestMonitorEvent.EvState.REQUEST); // gui updates from outgoing msg
239                break;
240            case LATEST_TIMESTAMP_COLUMN:
241                _mainArray.get(row).setDate( new Date() );
242                updateGui(row, col);
243                break;
244            case FEEDBACKREQUIRED_COLUMN:
245                _mainArray.get(row).setFeedbackTotReqd( (int) value );
246                updateGui(row, col);
247                break;
248            case FEEDBACKOUTSTANDING_COLUMN:
249                _mainArray.get(row).setFeedbackOutstanding( (Integer) value );
250                updateGui(row, col);
251                break;
252            case FEEDBACKEVENT_COLUMN:
253                _mainArray.get(row).setExtraEvent( (int) value );
254                updateGui(row, col);
255                break;
256            case FEEDBACKNODE_COLUMN:
257                _mainArray.get(row).setExtraNode( (int) value );
258                updateGui(row, col);
259                break;
260            case FEEDBACKTIMEOUT_COLUMN:
261                _mainArray.get(row).setFeedbackTimeout( (int) value );
262                updateGui(row, col);
263                break;
264            case LASTFEEDBACK_COLUMN:
265                _mainArray.get(row).setLastFb( (CbusEventRequestMonitorEvent.FbState) value );
266                updateGui(row, col);
267                break;
268            default:
269                break;
270        }
271    }
272    
273    private void updateGui(int row, int col){
274        ThreadingUtil.runOnGUIEventually( ()->{
275            fireTableCellUpdated(row, col); 
276        });
277    }
278
279    // outgoing cbus message
280    // or incoming CanReply
281    @Override
282    public void message(CanMessage m) {
283        if ( m.extendedOrRtr() ) {
284            return;
285        }
286        int opc = CbusMessage.getOpcode(m);
287        if (CbusOpCodes.isEventRequest(opc)) {
288            processEvRequest( CbusMessage.getNodeNumber(m) , CbusMessage.getEvent(m) );
289        }
290        else if (CbusOpCodes.isEventNotRequest(opc)) {
291            processEvent( CbusMessage.getNodeNumber(m) , CbusMessage.getEvent(m) );
292        }
293    }
294    
295    // incoming cbus message
296    // handled the same as outgoing
297    @Override
298    public void reply(CanReply r) {
299        if ( r.extendedOrRtr() ) {
300            return;
301        }
302        CanMessage m = new CanMessage(r);
303        message(m);
304    }
305
306    // called when event heard as CanReply / CanMessage
307    private void processEvent( int nn, int en ){
308        
309        int existingRow = eventRow( nn, en);
310        int fbRow = extraFeedbackRow( nn, en);
311        if ( existingRow > -1 ) {
312            _mainArray.get(existingRow).setResponseReceived();
313            setValueAt(1, existingRow, LATEST_TIMESTAMP_COLUMN);
314        }
315        else if ( fbRow > -1 ){
316            _mainArray.get(fbRow).setResponseReceived();
317        }
318    }
319    
320    // called when request heard as CanReply / CanMessage
321    private void processEvRequest( int nn, int en ) {
322        
323        int existingRow = eventRow( nn, en);
324        if (existingRow<0) {
325            addEvent(nn,en,CbusEventRequestMonitorEvent.EvState.REQUEST,null); 
326        }
327        existingRow = eventRow( nn, en);
328        _mainArray.get(existingRow).setRequestReceived();
329        
330    }
331    
332    protected int eventRow(int nn, int en) {
333        return _mainArray.indexOf(new CbusEvent(nn,en));
334    }
335
336    protected int extraFeedbackRow(int nn, int en) {
337        for (int i = 0; i < getRowCount(); i++) {
338            if (_mainArray.get(i).matchesFeedback(nn, en)) {
339                return i;
340            }
341        }
342        return -1;
343    }
344
345    public void addEvent(int node, int event, CbusEventRequestMonitorEvent.EvState state, Date timestamp) {
346        
347        CbusEventRequestMonitorEvent newmonitor = new CbusEventRequestMonitorEvent(
348            node, event, state, timestamp, _defaultfeedbackdelay, 
349            _defaultFeedback, this );
350        
351        _mainArray.add(newmonitor);
352        
353        ThreadingUtil.runOnGUI( ()->{ fireTableRowsInserted((getRowCount()-1), (getRowCount()-1)); });
354        addToLog(1,newmonitor.toString() + Bundle.getMessage("AddedToTable"));
355    }
356
357    /**
358     * Remove Row from table
359     * @see #buttonDeleteClicked
360     * @param row int row number
361     */    
362    private void removeRow(int row) {
363        _context = _mainArray.get(row).toString() + Bundle.getMessage("TableConfirmDelete");
364        _mainArray.remove(row);
365        ThreadingUtil.runOnGUI( ()->{ fireTableRowsDeleted(row,row); });
366        addToLog(3,_context);
367    }
368    
369    /**
370     * Delete Button Clicked
371     * See whether to display confirm popup
372     * @see #removeRow
373     * @param row int row number
374     */
375    private void buttonDeleteClicked(int row) {
376        if (sessionConfirmDeleteRow) {
377            // confirm deletion with the user
378            JCheckBox checkbox = new JCheckBox(Bundle.getMessage("PopupSessionConfirmDel"));
379            String message = Bundle.getMessage("DelConfirmOne") + "\n"   
380            + Bundle.getMessage("DelConfirmTwo");
381            Object[] params = {message, checkbox};
382            
383            if (JOptionPane.OK_OPTION == JOptionPane.showConfirmDialog(
384                    null, params, Bundle.getMessage("DelEvPopTitle"), 
385                    JOptionPane.OK_CANCEL_OPTION,
386                    JOptionPane.WARNING_MESSAGE)) {
387                    boolean dontShow = checkbox.isSelected();
388                    if (dontShow) {
389                        sessionConfirmDeleteRow=false;
390                    }
391                    removeRow(row);
392            }
393        } else {
394            // no need to show warning, just delete
395            removeRow(row);
396        }
397    }
398    
399    /**
400     * Add to Event Table Console Log
401     * @param cbuserror int
402     * @param cbustext String console message
403     */
404    public void addToLog(int cbuserror, String cbustext){
405        ThreadingUtil.runOnGUI( ()->{  
406            if (cbuserror==3) {
407            tablefeedback.append ("\n * * * * * * * * * * * * * * * * * * * * * * " + cbustext);
408            } else {
409                tablefeedback.append( "\n"+cbustext);
410            }
411        });
412    }
413        
414    /**
415     * disconnect from the CBUS
416     */
417    public void dispose() {
418        // eventTable.removeAllElements();
419        // eventTable = null;
420        for (int i = 0; i < getRowCount(); i++) {
421            _mainArray.get(i).stopTheTimer();
422        }
423        _mainArray = null;
424        
425        tablefeedback.dispose();
426        tc.removeCanListener(this);
427        
428    }
429
430    protected TextAreaFIFO tablefeedback(){
431        return tablefeedback;
432    }
433    
434    // private final static Logger log = LoggerFactory.getLogger(CbusEventRequestDataModel.class);
435}