001package jmri.jmrix.can.cbus.node;
002
003import javax.annotation.Nonnull;
004import jmri.jmrix.can.CanSystemConnectionMemo;
005import jmri.jmrix.can.cbus.CbusPreferences;
006
007// import org.slf4j.Logger;
008// import org.slf4j.LoggerFactory;
009
010/**
011 * Table data model for display of CBUS Nodes
012 *
013 * @author Steve Young (c) 2019
014 * 
015 */
016public class CbusBasicNodeTableFetch extends CbusBasicNodeTableOperations {
017
018    protected CbusNodeTrickleFetch trickleFetch;
019    
020    public CbusBasicNodeTableFetch(@Nonnull CanSystemConnectionMemo memo, int row, int column) {
021        super(memo,row,column);
022    }
023    
024    private int urgentNode = -1;
025    private int nodebefore = -1;
026    private int nodeafter = -1;
027    private boolean urgentActive=false; // feature active, set false for background fetch
028    
029    /**
030     * Notify the table that the Node data fetch is more urgent
031     */
032    public void startUrgentFetch() {
033        urgentActive = true;
034        startBackgroundFetch();
035    }
036    
037    /**
038     * Notify the table that the Node data fetch is more urgent
039     * @param nodeNum the Node to prioritise in the fetch
040     */
041    protected void setUrgentNode( int nodeNum ){
042        urgentNode = nodeNum;
043        urgentActive = true;
044        startBackgroundFetch();
045    }
046    
047    /**
048     * Fetch data in order of priority based on what user is currently viewing
049     * @param nodenum number of Node to prioritise in the fetch
050     * @param urgentNodeBefore number of the Node in main table row above
051     * @param urgentNodeAfter number of the Node in main table row below
052     */
053    public void setUrgentFetch(int nodenum, int urgentNodeBefore, int urgentNodeAfter){
054        urgentNode = nodenum;
055        nodebefore = urgentNodeBefore;
056        nodeafter = urgentNodeAfter;
057        urgentActive = true;
058        startBackgroundFetch();
059    }
060    
061    /**
062     * Request the table send the next urgent fetch
063     */
064    public void triggerUrgentFetch(){
065        if (urgentActive) {
066            sendNextBackgroundFetch();
067        }
068    }
069    
070    /**
071     * Starts background fetching for all table data as per user prefs
072     * Call whenever a node has been added to table or node edited
073     */ 
074    public void startBackgroundFetch(){
075        if (!(this instanceof CbusNodeTableDataModel)){
076            return;
077        }
078        CbusPreferences pref = ((CbusNodeTableDataModel)this).preferences;
079        // reset if already running
080        if ( trickleFetch != null ){
081                trickleFetch.dispose();
082        }
083        trickleFetch = null;
084        if ( ( pref != null ) && (pref.getNodeBackgroundFetchDelay() > 0 ) ) {
085            trickleFetch = new CbusNodeTrickleFetch( _memo, this, pref.getNodeBackgroundFetchDelay() );
086        }
087    }
088    
089    private boolean hasActiveTimers(){
090        for (int i = 0; i < getRowCount(); i++) {
091            // _mainArray.get(i).startLoadFromXml();
092            _mainArray.get(i).getNodeBackupManager().doLoad();
093            if ( _mainArray.get(i).getNodeTimerManager().hasActiveTimers() ){
094                return true;
095            }
096            _mainArray.get(i).getNodeStats().checkNodeFinishedLoad();
097        }
098        return false;
099    }
100    
101    // prioritise command station 
102    // get parameters
103    // get node variables 1-16
104    private boolean sentCommandStationFetch(){
105        for (int i = 0; i < getRowCount(); i++) {
106            if ( _mainArray.get(i).getCsNum() > -1 ) { // is a command station
107                if ( sentOutstandingParam(_mainArray.get(i)) ) { // this param
108                    return true;
109                }
110                if (( _mainArray.get(i).getNodeParamManager().getParameter(6) > 15 ) // If CS does has have more than 15 NV's
111                    && ( _mainArray.get(i).getNodeNvManager().getNV(16) < 0 )) {
112                    _mainArray.get(i).getNodeNvManager().sendNextNVToFetch();
113                    return true;
114                }
115            }
116        }
117        return false;
118    }
119    
120    private boolean sentAnAllNodeParam(){
121        // Get all node parameters fetched so basic node details ( type, num nv's etc. ) are known
122        for (int i = 0; i < getRowCount(); i++) {
123            if ( sentOutstandingParam(_mainArray.get(i)) ) { // this param
124                return true;
125            }
126        }
127        return false;
128    }
129    
130    private boolean sentOutstandingNvs(CbusNode thisnode){
131        if ( thisnode!=null && thisnode.getNodeNvManager().getOutstandingNvCount() > 0 ){ // this nv
132            thisnode.getNodeNvManager().sendNextNVToFetch();
133            return true;
134        }
135        return false;
136    }
137    
138    private boolean sentOutstandingEvs(CbusNode thisnode) {
139        if ( thisnode == null ) {
140            return false;
141        }
142        if ( thisnode.getNodeEventManager().getOutstandingEvVars() > 0 ) { // this events
143            thisnode.getNodeEventManager().sendNextEvVarToFetch();
144            return true;
145        }
146        return sentOutstandingEvTotal(thisnode);
147    }
148
149    /**
150     * Re-request the total number of events from a node where the events are unknown,
151     * eg. invalidated following a fetch failure or an invalid event index,
152     * but the parameters are already complete so the total would not otherwise
153     * be requested again as part of the parameter fetch.
154     * <p>
155     * Not sent if the node has abandoned an event fetch following repeated timeouts,
156     * the node timers or a node reset will clear this.
157     * @param thisnode the node to check
158     * @return true if a request was sent, else false
159     */
160    private boolean sentOutstandingEvTotal(CbusNode thisnode) {
161        if ( thisnode.getNodeEventManager().getTotalNodeEvents() < 0
162            && thisnode.getNodeParamManager().getOutstandingParams() == 0
163            && thisnode.getNodeParamManager().getParameter(CbusNodeConstants.EV_PER_EN_IDX) > -1
164            && !thisnode.getNodeTimerManager().eventFetchAbandoned() ) {
165            thisnode.getNodeParamManager().requestEventTot();
166            return true;
167        }
168        return false;
169    }
170    
171    private boolean sentOutstandingNvsOrEvs(CbusNode thisnode) {
172        return sentOutstandingNvs(thisnode) || sentOutstandingEvs(thisnode);
173    }
174    
175    private boolean sentOutstandingParam(CbusNode thisnode) {
176        if ( thisnode!=null && thisnode.getNodeParamManager().getOutstandingParams() > 0 ) { // this params
177            thisnode.getNodeParamManager().sendRequestNextParam();
178            return true;
179        }
180        return false;
181    }
182    
183    private boolean sentUrgentFetches() {
184    
185        // If a node is selected in the node manager the details for this are fetched next
186        CbusNode _urgentNode = getNodeByNodeNum(urgentNode);
187        CbusNode _beforeNode = getNodeByNodeNum(nodebefore);
188        CbusNode _afterNode = getNodeByNodeNum(nodeafter);
189        
190        if (sentOutstandingNvsOrEvs(_urgentNode)) {
191            return true;
192        }
193        
194        if ( sentOutstandingNvs(_afterNode) || sentOutstandingNvs(_beforeNode) ){
195            return true;
196        }
197
198        if (sentOutstandingEvs(_afterNode) || sentOutstandingEvs(_beforeNode) ) {
199            return true;
200        }
201            
202        // the node selected in table has been synched, 
203        // along with the row above and below in case user scolls
204        urgentActive=false;
205    
206        return false;
207    
208    }
209    
210    /**
211     * Send the next parameter request, ev var request or nv request.
212     * Triggered from either background or active fetch.
213     * Triggers loading the node backup xml file
214     * Triggers the check for node data fetch complete
215     *
216     * The order of the fetch changes depending on
217     * If node is a Command station
218     * If a node is currently selected in a node table pane
219     * The node above or below the currently selected row
220     * If event or nv tab is displayed in a node table pane
221     *
222     * Default order is Params 0,1,3,6,5,7,2, event total, 
223     * remaining parameters, NVs, event index, event vars.
224     */ 
225    protected void sendNextBackgroundFetch(){
226        
227        if ( getAnyNodeInLearnMode()>0 || hasActiveTimers()){
228            return;
229        }
230        
231        if (sentCommandStationFetch() || sentAnAllNodeParam()){
232            return;
233        }
234        
235        if (sentUrgentFetches()){
236            return;
237        }
238        
239        // default lookup routine for NV's then Events.
240        // parameters should already be known
241        for (int i = 0; i < getRowCount(); i++) {
242            if (sentOutstandingNvsOrEvs(_mainArray.get(i))){
243                return;
244            }
245        }
246        
247        // if all done dispose trickle fetch
248        if ( trickleFetch != null ) {
249            trickleFetch.dispose();
250        }
251        trickleFetch = null;
252    }
253    
254    // private static final Logger log = LoggerFactory.getLogger(CbusBasicNodeTableFetch.class);
255}