001package jmri.jmrix.can.cbus.node;
002
003import javax.annotation.Nonnull;
004import org.slf4j.Logger;
005import org.slf4j.LoggerFactory;
006
007/**
008 * Class to manage Node Variables for a CbusNode.
009 *
010 * @author Steve Young Copyright (C) 2019,2020
011 */
012public class CbusNodeNVManager {
013    private final CbusBasicNodeWithManagers _node;
014    private int[] _nvArray;
015    private int[] newNvsToTeach;
016    private int nextNvInLoop;
017    private boolean TEACH_OUTSTANDING_NVS = false;
018    
019    /**
020     * Create a new CbusNodeNVManager
021     *
022     * @param node The Node
023     */
024    public CbusNodeNVManager ( CbusBasicNodeWithManagers node ){
025        _node = node;
026
027    }
028    
029    /**
030     * Reset this CbusNodeNVManager.
031     * Array is set to null and NV SendError Count 0.
032     */
033    protected void reset() {
034        _nvArray = null;
035        _node.getNodeTimerManager()._sendNVErrorCount = 0;
036    }
037    
038    /**
039     * Get Flag for if any outstanding Teach NV operations are due.
040     * @return true if outstanding teaches, else false.
041     */
042    protected boolean teachOutstandingNvs() {
043        return TEACH_OUTSTANDING_NVS;
044    }
045    
046    /**
047     * Set the Node Variables
048     * <p>
049     * 0th NV is total NVs
050     * so length of newnvs should already be num. of NV's +1
051     * 
052     * @param newnvs an int array, the first value being the total number
053     * 
054     */
055    public void setNVs( @Nonnull int[] newnvs ) {
056        
057        _nvArray = new int [(newnvs.length)]; // no need to compensate for index 0 being total
058        for (int i = 0; i < newnvs.length; i++) {
059            setNV(i,newnvs[i]);     // will notify SINGLENVUPDATE
060        }
061        
062        // Now ensure anyone only listening for ALLNVUPDATE sees the change
063        _node.notifyPropertyChangeListener("ALLNVUPDATE", null, null);
064    }
065    
066    /**
067     * Set a single Node Variable
068     * <p>
069     * so Index 1 is NV1 .. Index 255 is NV255
070     * Index 0 is set by Node Parameter
071     * 
072     * @param index NV Index
073     * @param newnv min 1, max 255
074     * 
075     */
076    public void setNV( int index, int newnv ) {
077        
078        if ( _nvArray == null ){
079            log.error("Attempted to set NV {} on a null NV Array on Node {}",index,_node);
080            return;
081        }
082        if (index == 0 ) {
083            if ( newnv != _node.getNodeParamManager().getParameter(6)
084                    && _node.getNodeParamManager().getParameter(6) != -1
085                    ){
086                log.error("Node {} NV Count mismatch. Parameters report {} NVs, received set for {} NVs",
087                        _node, _node.getNodeParamManager().getParameter(6),
088                        newnv);
089            }
090        }
091        if (index < 0 || index > 255) { // 0 is total
092            log.error("Attempted to set Invalid NV {} on Node {}",index,_node);
093            return;
094        }
095        if (newnv < -1 || newnv > 255) { // -1 is unset
096            log.error("Attempted to set NV {} Invalid Value {} on Node {}",index,newnv,_node);
097            return;
098        }
099        _nvArray[index]=newnv;
100        if (index > 0) {
101            // Ignore index 0 (number of NVs) as this would pass a negative value in the property change
102            _node.notifyPropertyChangeListener("SINGLENVUPDATE",null,( index -1));
103        }
104        
105    }
106    
107    /**
108     * Get the Node Variable int Array
109     * <p>
110     * 0th Index is total NVs
111     * so Index 1 is NV1 .. Index 255 is NV255
112     * 
113     * @return Array of NV's, first in index is Total NV's
114     */
115    public int[] getNvArray() {
116        return _nvArray;
117    }
118    
119    /**
120     * Number of Node Variables on the node.
121     * @return 0 if number of NV's unknown, else number of NV's.
122     */
123    public int getTotalNVs() {
124        if ( _nvArray==null){
125            return 0;
126        }
127        return _nvArray[0];
128    }
129    
130    /**
131     * Get a specific Node Variable
132     * @param index NV Index
133     * @return -1 if NV's unknown, else Node Variable value.
134     *
135     */
136    public int getNV ( int index ) {
137        if ( getTotalNVs() < 1 ){
138            return -1;
139        }
140        return _nvArray[index];
141    }
142    
143    /**
144     * Get number of difference between this and another Nodes Node Variables
145     * @param testAgainst The CBUS Node to test against
146     * @return number of different NV's
147     *
148     */
149    public int getNvDifference(CbusNode testAgainst){
150        int count = 0;
151        for (int i = 0; i < _nvArray.length; i++) {
152            if (getNV(i) != testAgainst.getNodeNvManager().getNV(i)){
153                count++;
154            }
155        }
156        return count;
157    }
158    
159    /**
160     * Number of unknown Node Variables.
161     * i.e. not yet fetched from a physical node.
162     * @return -1 if number of NV's unknown, 0 if all NV's known, else number of outstanding.
163     *
164     */
165    public int getOutstandingNvCount(){
166        int count = 0;
167        if ( _nvArray == null ){
168            return -1;
169        }
170        for (int i = 0; i < _nvArray.length; i++) {
171            if ( _nvArray[i] < 0 ) {
172                count ++;
173            }
174        }
175        return count;
176    }
177    
178    /**
179     * Send a request for the next unknown Node Variable.
180     * <p>
181     * Only triggered from CBUS Node Manager.
182     * <p>
183     * Does NOT send if the node has existing outstanding requests.
184     * Expected response from node NVANS
185     */
186    protected void sendNextNVToFetch(){
187        
188        if ( _node.getNodeTimerManager().hasActiveTimers() ) {
189            return;
190        }
191        
192        for (int i = 0; i < _nvArray.length; i++) {
193            if ( _nvArray[i] < 0 ) {
194                // start NV request timer
195                
196                _node.getNodeTimerManager().setNextNvVarTimeout();
197                _node.send.nVRD( _node.getNodeNumber(), i );
198                return;
199            }
200        }
201    }
202    
203    /**
204     * Send and teach updated Node Variables to this node
205     *
206     * @param newnv array of variables, index 0 i the array is total variables
207     */
208    public void sendNvsToNode( int[] newnv ) {
209        
210      //  log.info("start loop to send nv's , nv 1 is {}",newnv[1]);
211        newNvsToTeach = newnv;
212        nextNvInLoop = 1; // start from 1 not 0 as 0 is the total num. nv's
213        TEACH_OUTSTANDING_NVS = true;
214        _node.getNodeTimerManager()._sendNVErrorCount = 0 ;
215        
216        // check length of new array
217      //  log.info("array size {}",newNvsToTeach.length);
218        
219        sendNextNvToNode();
220        
221    }
222    
223    /**
224     * Loop for NV teaching
225     */
226    protected void sendNextNvToNode() {
227        
228        if ( _node.getNodeTimerManager().hasActiveTimers() ) {
229            return;
230        }
231        
232        for (int i = nextNvInLoop; i < _nvArray.length; i++) {
233            if ( newNvsToTeach[i] != _nvArray[i] ) {
234                _node.getNodeTimerManager().setsendEditNvTimeout();
235                _node.send.nVSET( _node.getNodeNumber() ,i, newNvsToTeach[i] );
236                nextNvInLoop = i;
237                return;
238            }
239        }
240        
241        log.info( "TEACH NV COMPLETE {}", Bundle.getMessage("NdCompleteNVar", String.valueOf(_node.getNodeTimerManager()._sendNVErrorCount) , _node ) );
242        TEACH_OUTSTANDING_NVS = false;
243        _node.notifyPropertyChangeListener("TEACHNVCOMPLETE", null, _node.getNodeTimerManager()._sendNVErrorCount);
244        
245        // refresh all nvs from node if error
246        if ( _node.getNodeTimerManager()._sendNVErrorCount > 0 ){ // user notified in _mainPane
247            
248            int [] myarray = new int[(_node.getNodeParamManager().getParameter(6)+1)]; // +1 to account for index 0 being the NV count
249            java.util.Arrays.fill(myarray, -1);
250            myarray[0] = _node.getNodeParamManager().getParameter(6);
251            setNVs(myarray);
252            
253            _node.getTableModel().startUrgentFetch();
254            
255        }
256        _node.getNodeTimerManager()._sendNVErrorCount = 0;
257    }
258    
259    /**
260     * Get the NV String in Hex Byte Format
261     * <p>
262     * eg. for NV array [3,1,2,255] returns "0102FF"
263     * 
264     * @return Full NV String WITHOUT leading number of NVs
265     */  
266    public String getNvHexString() {
267        if ( getNvArray() == null ) {
268            return "";
269        } else {
270            return jmri.util.StringUtil.hexStringFromInts(getNvArray()).replaceAll("\\s","").substring(2);
271        }
272    }
273    
274    /**
275     * @return descriptive string
276     */
277    @Override
278    public String toString() {
279        return "Node Variables";
280    }
281    
282    private static final Logger log = LoggerFactory.getLogger(CbusNodeNVManager.class);
283    
284}