001package jmri.implementation;
002
003import java.util.ArrayDeque;
004import java.util.ArrayList;
005import java.util.Deque;
006import java.util.List;
007
008import jmri.ProgListener;
009import jmri.Programmer;
010import jmri.jmrix.AbstractProgrammerFacade;
011import jmri.util.ThreadingUtil;
012
013/**
014 * Programmer facade for single index multi-CV access.
015 * <p>
016 * Used through the String write/read/confirm interface. Accepts one address
017 * format in LOCONETBDOPSWMODE mode only:
018 * <ul>
019 * <li> 115.25.16<br>
020 * The 115 is the device type (115 is a DS64); 25 is the first OpSw for 
021 * setting the offset address; 16 is the address to be written.
022 * This will write/read 32 bits from the associated Op Switch word.
023 * </ul>
024 * All others pass through to the next facade or programmer. E.g. 123 will do a
025 * write/read/confirm to 123, or some other facade can provide "normal" indexed
026 * addressing.
027 *
028 * @see jmri.implementation.ProgrammerFacadeSelector
029 *
030 * @author Bob Jacobsen Copyright (C) 2013, 2016, 2026
031 */
032public class DigitraxOpSwWordFacade extends AbstractProgrammerFacade implements ProgListener {
033
034    /**
035     * @param prog the programmer this facade is attached to
036     */
037    public DigitraxOpSwWordFacade(Programmer prog) {
038        super(prog);
039    }
040
041    String _cv;  // the CV being processed
042    int _val;    // value to be written or being read
043    ProgState state = ProgState.NOTPROGRAMMING;
044    
045    int accumulator;   // when accumulating a value during read
046    String typeValue;  // device type value
047    int pageAddress;   // if > 0, this is the OpSw to start writing the page number, 
048                          // else this is an unpaged access
049    int pageNumber;    // the page number to be written to the address
050    
051    // members for handling the programmer interface
052    static class OpSwCommand {
053        OpSwCommand(String cv, int value, boolean read) {
054            this.cv = cv;
055            this.value = value;
056            this.read = read;
057        }
058        String cv;
059        int value;
060        boolean read;  // write if false
061        int bit; // 0-31 number to shift the returned bit
062    }
063    
064    Deque<OpSwCommand> commands = new ArrayDeque<OpSwCommand>();
065
066    private void parseCV(String cv) throws IllegalArgumentException {
067        pageAddress = -1;
068        
069        if (cv.contains(".")) {
070            String[] splits = cv.split("\\.");
071            if (splits.length == 3) {
072                typeValue   = splits[0];
073                pageAddress = Integer.parseInt(splits[1]);
074                pageNumber  = Integer.parseInt(splits[2]);
075            } else {
076                _cv = cv;  // this is a pass through operation
077            }
078        } else {
079            _cv = cv;
080        }
081    }
082
083    // typical typeValue value is "155"
084    List<OpSwCommand> addressToCommands(String typeValue, int address) {
085        var result = new ArrayList<OpSwCommand>();
086        for (int i = 0; i <= 7; i++) {
087            int bit = (address>>i)&0x01;
088            String cv = typeValue+"."+Integer.toString(i+pageAddress);
089            result.add(new OpSwCommand(cv, bit, false));
090        }
091        return result;
092    }
093
094    int offsetConstant = 33-0;  // OpsSwitch number for LSB of data value
095                                // could also be pageAddress+8
096
097    OpSwCommand oneBit(String typeValue, int swNum, int data, boolean read) {
098        int bitNum = swNum-33; // is this a constant?
099        int bit = (data>>bitNum)&0x01;
100        String cv = typeValue+"."+Integer.toString(swNum);
101        var retval = new OpSwCommand(cv, bit, read);
102        retval.bit = bitNum;
103        return retval;
104    }
105    
106    // programming interface
107    @Override
108    synchronized public void writeCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
109        log.debug("writeCV with {}", p);
110        _val = val;
111        useProgrammer(p);
112        pageAddress= -1;
113        parseCV(CV);
114        if (pageAddress == -1) { // this is pass through
115            state = ProgState.PROGRAMMING;
116            prog.writeCV(_cv, val, this);
117        } else {
118            commands = new ArrayDeque<OpSwCommand>();
119            
120            commands.addAll(addressToCommands(typeValue, pageNumber));
121            
122            for (int i = 46; i <= 48; i++) commands.add(oneBit(typeValue, i, val, false));
123            
124            for (int i = 33; i <= 45; i++) commands.add(oneBit(typeValue, i, val, false));
125            
126            for (int i = 62; i <= 64; i++) commands.add(oneBit(typeValue, i, val, false));
127
128            for (int i = 49; i <= 61; i++) commands.add(oneBit(typeValue, i, val, false));
129
130            state = ProgState.SENDWRITESEQUENCE;
131            prog.writeCV(commands.getFirst().cv, commands.getFirst().value, this);
132        }
133    }
134
135    @Override
136    synchronized public void readCV(String CV, jmri.ProgListener p) throws jmri.ProgrammerException {
137       readCV(CV, p, 0);
138    }
139
140    int accumulation;  // accumulate the read result
141    @Override
142    synchronized public void readCV(String CV, jmri.ProgListener p, int val) throws jmri.ProgrammerException {
143        log.debug("readCV with {}", p);
144        useProgrammer(p);
145        pageAddress= -1;
146        accumulation = 0;
147        parseCV(CV);
148        if (pageAddress == -1) {
149            state = ProgState.PROGRAMMING;
150            prog.readCV(_cv, this, val);
151        } else {
152            commands = new ArrayDeque<OpSwCommand>();
153            
154            commands.addAll(addressToCommands(typeValue, pageNumber));
155            
156            for (int i = 46; i <= 48; i++) commands.add(oneBit(typeValue, i, val, true));
157            
158            for (int i = 33; i <= 45; i++) commands.add(oneBit(typeValue, i, val, true));
159            
160            for (int i = 62; i <= 64; i++) commands.add(oneBit(typeValue, i, val, true));
161
162            for (int i = 49; i <= 61; i++) commands.add(oneBit(typeValue, i, val, true));
163
164            state = ProgState.SENDREADSEQUENCE;
165            prog.writeCV(commands.getFirst().cv, commands.getFirst().value, this); // first address bit written
166        }
167    }
168
169    @Override
170    synchronized public void confirmCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
171        useProgrammer(p);
172        pageAddress= -1;
173        parseCV(CV);
174        if (pageAddress == -1) {
175            state = ProgState.PROGRAMMING;
176            prog.confirmCV(_cv, val, this);
177        } else {
178            commands = new ArrayDeque<OpSwCommand>();
179            
180            commands.addAll(addressToCommands(typeValue, pageNumber));
181            
182            for (int i = 46; i <= 48; i++) commands.add(oneBit(typeValue, i, val, true));
183            
184            for (int i = 33; i <= 45; i++) commands.add(oneBit(typeValue, i, val, true));
185            
186            for (int i = 62; i <= 64; i++) commands.add(oneBit(typeValue, i, val, true));
187
188            for (int i = 49; i <= 61; i++) commands.add(oneBit(typeValue, i, val, true));
189
190            state = ProgState.SENDWRITESEQUENCE;
191            prog.confirmCV(commands.getFirst().cv, commands.getFirst().value, this);
192        }
193    }
194    
195    private jmri.ProgListener _usingProgrammer = null;
196
197    // internal method to remember who's using the programmer
198    protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException {
199        log.debug("useProgrammer for {} with {}", p, _usingProgrammer, new Exception("traceback"));
200        // test for only one!
201        if (_usingProgrammer != null && _usingProgrammer != p) {        
202            log.debug("programmer already in use by {}", _usingProgrammer);
203            throw new jmri.ProgrammerException("programmer in use");
204        } else {
205            _usingProgrammer = p;
206        }
207    }
208
209    enum ProgState {
210
211        PROGRAMMING, // doing last read/write, next reply is end; typically a single operation
212        NOTPROGRAMMING,  // idle
213        SENDWRITESEQUENCE,
214        SENDREADSEQUENCE
215    }
216
217    public static int delayInterval = 10; // public static so can be changed in a script
218    
219    /** {@inheritDoc}
220     * Note this assumes that there's only one phase to the operation
221     */
222    @Override
223    synchronized public void programmingOpReply(int value, int status) {
224        log.debug("notifyProgListenerEnd value {} status {}", value, status);
225
226        if (_usingProgrammer == null) {
227            log.error("No listener to notify, reset and ignore");
228            state = ProgState.NOTPROGRAMMING;
229            return;
230        }
231        
232        // Complete processing later 
233        final int myValue = value;
234        final int myStatus = status;
235        ThreadingUtil.runOnGUIDelayed(() -> {
236            processProgrammingOpReply(myValue, myStatus);
237        }, delayInterval);
238    }
239    
240    // After a delay, this processes the reply on the Layout Thread
241    protected void processProgrammingOpReply(int value, int status) {
242        if (status != OK) {
243            log.debug("Reset and pass abort up");
244            jmri.ProgListener temp = _usingProgrammer;
245            _usingProgrammer = null; // done
246            state = ProgState.NOTPROGRAMMING;
247            commands.clear();
248            temp.programmingOpReply(value, status);
249            return;
250        }
251
252        switch (state) {
253            case PROGRAMMING:
254                // the programmingOpReply handler might send an immediate reply, so
255                // clear the current listener _first_
256                jmri.ProgListener temp = _usingProgrammer;
257                _usingProgrammer = null; // done
258                state = ProgState.NOTPROGRAMMING;
259                commands.clear();  // just in case
260                temp.programmingOpReply(value, status);
261                break;
262
263            case SENDWRITESEQUENCE:
264                // drop last-sent item
265                commands.pollFirst();
266                // and send next
267                var next = commands.peekFirst();
268                if (next == null) {
269                    // operation is done
270                    // the programmingOpReply handler might send an immediate reply, so
271                    // clear the current listener _first_
272                    temp = _usingProgrammer;
273                    _usingProgrammer = null; // done
274                    state = ProgState.NOTPROGRAMMING;
275                    commands.clear();  // just in case
276                    log.debug("_usingProgrammer: {}", _usingProgrammer);
277                    temp.programmingOpReply(accumulation, status);
278                    return;
279                    
280                } else {
281                    // send that one
282                    try {
283                        prog.writeCV(next.cv, next.value, this);
284                    } catch (jmri.ProgrammerException e) {
285                        log.error("Exception doing write OpSw", e);
286                    }   
287                }
288                return;
289      
290            case SENDREADSEQUENCE:
291                // drop last-sent item
292                var last = commands.pollFirst();
293                // if last was a read, accumulate value
294                if (last.read) {
295                    accumulation = accumulation | ((value&0x01)<<last.bit);
296                }
297                // and send next
298                next = commands.peekFirst();
299                if (next == null) {
300                    // operation is done
301                    // the programmingOpReply handler might send an immediate reply, so
302                    // clear the current listener _first_
303                    temp = _usingProgrammer;
304                    _usingProgrammer = null; // done
305                    state = ProgState.NOTPROGRAMMING;
306                    commands.clear();  // just in case
307                    log.debug("_usingProgrammer: {}", _usingProgrammer);
308                    temp.programmingOpReply(accumulation, status);
309                    return;
310                    
311                } else {
312                    // send that one
313                    try {
314                        if (next.read) {
315                            prog.readCV(next.cv, this, next.value);
316                        } else {
317                            prog.writeCV(next.cv, next.value, this);
318                        }
319                    } catch (jmri.ProgrammerException e) {
320                        log.error("Exception doing write OpSw", e);
321                    }   
322                }
323                return;
324        
325            default:
326                log.error("Unexpected state on reply: {}", state);
327                // clean up as much as possible
328                _usingProgrammer = null;
329                state = ProgState.NOTPROGRAMMING;
330                commands.clear();
331                return;
332        }
333    }
334
335    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DigitraxOpSwWordFacade.class);
336
337}