001package jmri.jmrix.dcc4pc;
002
003import java.util.ArrayDeque;
004import java.util.ArrayList;
005import java.util.Arrays;
006import java.util.Deque;
007import java.util.List;
008
009/**
010 * Decodes the Raw RailCom encoding of a DCC4PC RailCom reader (Omnibus
011 * specification section 9.3). Each packet gives one DCC packet the reader saw,
012 * the state of every enabled input and the RailCom bytes each input with
013 * RailCom data received in the cutout that followed.
014 * <p>
015 * The encoding is stateful: type 1 packets carry no input states and inherit
016 * them from the packet before, and the number of duplicate-flag and length
017 * bytes that follow depends on those states. RailCom data may also be sent as
018 * "duplicate of the 1st, 2nd or 3rd previous" for an input. The decoder
019 * therefore only works on an unbroken stream that starts right after a Reset
020 * Encoding State, which sets every input to unoccupied. A Get Data reply may
021 * end part way through a packet; the leftover bytes are kept and joined to the
022 * next reply.
023 *
024 * @author swalton00 Copyright (C) 2026
025 */
026public class Dcc4PcRawDecoder {
027
028    public static final int STATE_UNOCCUPIED = 0;
029    public static final int STATE_OCCUPIED = 1;
030    public static final int STATE_ORIENTATION_A = 2;
031    public static final int STATE_ORIENTATION_B = 3;
032
033    /** The reader's output buffer overflowed and packets were lost here. */
034    public static final int OVERFLOW = 0x81;
035
036    private static final int HISTORY = 3;
037
038    public enum Kind {
039        DCC, OVERFLOW, UNKNOWN_COMMAND
040    }
041
042    /**
043     * RailCom bytes for one input. {@code duplicateOf} is 0 for new data, else
044     * 1-3 for a repeat of that earlier packet; {@code data} is null if the
045     * repeated packet is older than the history kept. {@code sourceDcc} is the
046     * DCC packet the bytes followed.
047     */
048    public static class InputData {
049
050        public final int input;
051        public final int duplicateOf;
052        public final int[] data;
053        public final int[] sourceDcc;
054
055        InputData(int input, int duplicateOf, int[] data, int[] sourceDcc) {
056            this.input = input;
057            this.duplicateOf = duplicateOf;
058            this.data = data;
059            this.sourceDcc = sourceDcc;
060        }
061    }
062
063    /**
064     * One decoded packet. For DCC packets {@code states[i]} is the state of
065     * {@code getInputs()[i]}; {@code explicitStates} says whether this packet
066     * carried them (type 2) or inherited them (type 1).
067     */
068    public static class Packet {
069
070        public final Kind kind;
071        public final int[] dcc;
072        public final boolean explicitStates;
073        public final int[] states;
074        public final List<InputData> railCom;
075
076        Packet(Kind kind, int[] dcc, boolean explicitStates, int[] states, List<InputData> railCom) {
077            this.kind = kind;
078            this.dcc = dcc;
079            this.explicitStates = explicitStates;
080            this.states = states;
081            this.railCom = railCom;
082        }
083    }
084
085    private static class Entry {
086
087        final int[] data;
088        final int[] dcc;
089
090        Entry(int[] data, int[] dcc) {
091            this.data = data;
092            this.dcc = dcc;
093        }
094    }
095
096    private final int[] inputs;
097    private final int[] states;
098    private final List<Deque<Entry>> history = new ArrayList<>();
099    private int[] pending = new int[0];
100
101    /**
102     * @param enabledInputs the inputs enabled on the reader, numbered from 1,
103     *                      in ascending order
104     */
105    public Dcc4PcRawDecoder(int[] enabledInputs) {
106        inputs = enabledInputs.clone();
107        Arrays.sort(inputs);
108        states = new int[inputs.length];
109        for (int i = 0; i < inputs.length; i++) {
110            history.add(new ArrayDeque<>());
111        }
112    }
113
114    /**
115     * @return the enabled inputs, numbered from 1, in the order their states
116     *         are reported
117     */
118    public int[] getInputs() {
119        return inputs.clone();
120    }
121
122    /**
123     * Start again as the reader does after a Reset Encoding State: every input
124     * unoccupied, no partial packet and no RailCom history.
125     */
126    public void reset() {
127        pending = new int[0];
128        Arrays.fill(states, STATE_UNOCCUPIED);
129        for (Deque<Entry> h : history) {
130            h.clear();
131        }
132    }
133
134    /**
135     * Decode a chunk of Get Data reply bytes.
136     *
137     * @param chunk the reply bytes, each 0-255
138     * @return every packet completed by this chunk
139     */
140    public List<Packet> decode(int[] chunk) {
141        int[] buf = new int[pending.length + chunk.length];
142        System.arraycopy(pending, 0, buf, 0, pending.length);
143        for (int i = 0; i < chunk.length; i++) {
144            buf[pending.length + i] = chunk[i] & 0xFF;
145        }
146        List<Packet> packets = new ArrayList<>();
147        int pos = 0;
148        while (pos < buf.length) {
149            int first = buf[pos];
150            if ((first & 0x80) != 0) {
151                if (first == OVERFLOW) {
152                    // packets were lost, but the reader counts duplicates and states from
153                    // what it delivered (spec 9.3 example, packet 4), so both still hold
154                    packets.add(new Packet(Kind.OVERFLOW, new int[0], false, states.clone(), new ArrayList<>()));
155                    pos++;
156                    continue;
157                }
158                // an unknown command's length is unknown too, so the rest of the buffer goes with it
159                packets.add(new Packet(Kind.UNKNOWN_COMMAND, new int[0], false, states.clone(), new ArrayList<>()));
160                pos = buf.length;
161                break;
162            }
163            int[] length = new int[1];
164            Packet p = parseDcc(buf, pos, length);
165            if (p == null) {
166                break;
167            }
168            packets.add(p);
169            pos += length[0];
170        }
171        pending = Arrays.copyOfRange(buf, pos, buf.length);
172        return packets;
173    }
174
175    /**
176     * Parse a type 1 or 2 packet at {@code start}. The decoder's state changes
177     * only once the whole packet is present.
178     *
179     * @return the packet, or null if it is not complete yet
180     */
181    private Packet parseDcc(int[] buf, int start, int[] length) {
182        int pos = start;
183        int first = buf[pos++];
184        boolean explicit = (first & 0x40) != 0;
185        int dccLength = (first & 0x3F) + 1;
186        if (pos + dccLength > buf.length) {
187            return null;
188        }
189        int[] dcc = Arrays.copyOfRange(buf, pos, pos + dccLength);
190        pos += dccLength;
191        int[] newStates = states.clone();
192        if (explicit) {
193            int stateBytes = (inputs.length * 2 + 7) / 8;
194            if (pos + stateBytes > buf.length) {
195                return null;
196            }
197            for (int i = 0; i < inputs.length; i++) {
198                newStates[i] = field(buf, pos, i, 2);
199            }
200            pos += stateBytes;
201        }
202        List<Integer> withData = new ArrayList<>();
203        for (int i = 0; i < inputs.length; i++) {
204            if (newStates[i] >= STATE_ORIENTATION_A) {
205                withData.add(i);
206            }
207        }
208        int dupBytes = (withData.size() * 2 + 7) / 8;
209        if (pos + dupBytes > buf.length) {
210            return null;
211        }
212        int[] dup = new int[withData.size()];
213        int fresh = 0;
214        for (int k = 0; k < dup.length; k++) {
215            dup[k] = field(buf, pos, k, 2);
216            if (dup[k] == 0) {
217                fresh++;
218            }
219        }
220        pos += dupBytes;
221        int lengthBytes = (fresh * 4 + 7) / 8;
222        if (pos + lengthBytes > buf.length) {
223            return null;
224        }
225        int[] lengths = new int[fresh];
226        int total = 0;
227        for (int f = 0; f < fresh; f++) {
228            lengths[f] = field(buf, pos, f, 4);
229            total += lengths[f];
230        }
231        pos += lengthBytes;
232        if (pos + total > buf.length) {
233            return null;
234        }
235        // complete: resolve the data, then commit states and history
236        List<InputData> railCom = new ArrayList<>();
237        int[][] resolved = new int[dup.length][];
238        int f = 0;
239        for (int k = 0; k < dup.length; k++) {
240            Deque<Entry> h = history.get(withData.get(k));
241            int[] source;
242            if (dup[k] == 0) {
243                resolved[k] = Arrays.copyOfRange(buf, pos, pos + lengths[f]);
244                source = dcc;
245                pos += lengths[f];
246                f++;
247            } else {
248                Entry e = dup[k] <= h.size() ? new ArrayList<>(h).get(dup[k] - 1) : null;
249                resolved[k] = e == null ? null : e.data;
250                source = e == null ? null : e.dcc;
251            }
252            railCom.add(new InputData(inputs[withData.get(k)], dup[k], resolved[k], source));
253        }
254        for (int k = 0; k < dup.length; k++) {
255            Deque<Entry> h = history.get(withData.get(k));
256            h.addFirst(new Entry(resolved[k], dcc));
257            while (h.size() > HISTORY) {
258                h.removeLast();
259            }
260        }
261        System.arraycopy(newStates, 0, states, 0, states.length);
262        length[0] = pos - start;
263        return new Packet(Kind.DCC, dcc, explicit, newStates, railCom);
264    }
265
266    /**
267     * @return the {@code index}-th {@code width}-bit field (2 or 4) starting at
268     *         {@code offset}, first field in the low bits
269     */
270    private static int field(int[] buf, int offset, int index, int width) {
271        int perByte = 8 / width;
272        int b = buf[offset + index / perByte];
273        return (b >> ((index % perByte) * width)) & ((1 << width) - 1);
274    }
275}