001package jmri.jmrix.bidib;
002
003import java.util.HashSet;
004import java.util.Set;
005import java.util.List;
006import java.util.Collections;
007
008import jmri.CollectingReporter;
009import jmri.InstanceManager;
010import jmri.RailCom;
011import jmri.RailComManager;
012
013import org.bidib.jbidibc.messages.AddressData;
014import org.bidib.jbidibc.core.DefaultMessageListener;
015import org.bidib.jbidibc.core.MessageListener;
016import org.bidib.jbidibc.messages.enums.OccupationState;
017import org.bidib.jbidibc.messages.utils.NodeUtils;
018
019import org.slf4j.Logger;
020import org.slf4j.LoggerFactory;
021
022/**
023 * This class implements the Reporter Manager interface
024 * for BiDiB railcom feedback.
025 * <p>
026 * Reports from this reporter are of the type jmri.RailCom.
027 *
028 * @author Paul Bender Copyright (C) 2016
029 * @author Eckart Meyer Copyright (C) 2019-2025
030 * 
031 * based on jmri.jmrix.z21.Z21reporter and others
032 */
033public class BiDiBReporter extends jmri.implementation.AbstractRailComReporter implements BiDiBNamedBeanInterface, CollectingReporter {
034
035    BiDiBAddress addr;
036    private final char typeLetter;
037    private BiDiBTrafficController tc = null;
038    MessageListener messageListener = null;
039    private final Set<Object> entrySet = Collections.synchronizedSet(new HashSet<>());
040    
041    /**  
042     * Create a reporter instance. 
043     * 
044     * @param systemName name to be created
045     * @param mgr Reporter Manager, we get the memo object and the type letter (R) from the manager
046     */
047    public BiDiBReporter(String systemName, BiDiBReporterManager mgr) {
048        super(systemName);
049        tc = mgr.getMemo().getBiDiBTrafficController();
050        log.debug("New Reporter: {}", systemName);
051        addr = new BiDiBAddress(systemName, mgr.typeLetter(), mgr.getMemo());
052        log.info("New REPORTER created: {} -> {}", systemName, addr);
053        typeLetter = mgr.typeLetter();
054        
055        createReporterListener();
056    }
057
058    /**
059     * {@inheritDoc}
060     */
061    @Override
062    public boolean hasExitReports() {
063        return true;
064    }
065
066    /**
067     * {@inheritDoc}
068     */
069    @Override
070    public BiDiBAddress getAddr() {
071        return addr;
072    }
073    
074    /**
075     * {@inheritDoc}
076     */
077    @Override
078    public void nodeNew() {
079        //create a new BiDiBAddress
080        addr = new BiDiBAddress(getSystemName(), typeLetter, tc.getSystemConnectionMemo());
081        if (addr.isValid()) {
082            log.info("new reporter address created: {} -> {}", getSystemName(), addr);
083        }
084    }
085
086    /**
087     * {@inheritDoc}
088     */
089    @Override
090    public void nodeLost() {
091        notify_loco(null);
092    }
093
094    /**
095     * Notify loco address
096     * 
097     * @param tag found tag
098     */
099    public void notify_loco(RailCom tag) {
100        //log.trace("tag: {}", tag);
101        super.notify(tag);
102    }
103
104    private void createReporterListener() {
105        // create message listener for RailCom messages
106        messageListener = new DefaultMessageListener() {
107            @Override
108            public void address(byte[] address, int messageNum, int detectorNumber, List<AddressData> addressData) {
109                //log.trace("address: node UID: {}, node addr: {}, address: {}, detectorNumber: {}, addressData: {}", addr.getNodeUID(), addr.getNodeAddr(), address, detectorNumber, addressData);
110                if (NodeUtils.isAddressEqual(addr.getNodeAddr(), address)  &&  addr.getAddr() == detectorNumber) {
111                    log.info("REPORTER address was signalled, locos: {}, BM Number: {}, node: {}", addressData, detectorNumber, addr);
112                    synchronized(entrySet) {
113                        entrySet.clear();
114                        if (addressData.size() > 0) {
115                            for (AddressData l : addressData) {
116                                log.trace("loco addr: {}", l);
117                                if (l.getAddress() > 0) {
118                                    RailCom tag = (RailCom) InstanceManager.getDefault(RailComManager.class).provideIdTag("" + l.getAddress());
119                                    //tag.setActualSpeed(msg.getRailComSpeed(i));   
120                                    entrySet.add(tag);
121                                    notify_loco(tag);
122                                }
123                                else {
124                                    notify_loco(null);
125                                }
126                            }
127                        }
128                        else {
129                            notify_loco(null);
130                        }
131                    }
132                }
133            }
134            // occupation free is catched here to report the loco has left - obviusly an "address" message is not sent then
135            @Override
136            public void occupation(byte[] address, int messageNum, int detectorNumber, OccupationState occupationState, Integer timestamp) {
137                //log.trace("occupation: node UID: {}, node addr: {}, address: {}, detectorNumber: {}, occ state: {}, timestamp: {}", addr.getNodeUID(), addr.getNodeAddr(), address, detectorNumber, occupationState, timestamp);
138                if (NodeUtils.isAddressEqual(addr.getNodeAddr(), address)  &&  addr.getAddr() == detectorNumber) {
139                    if (occupationState == OccupationState.FREE) {
140                        log.debug("REPORTER occupation free signalled, state: {}, BM Number: {}, node: {}", occupationState, detectorNumber, addr);
141                        synchronized(entrySet) {
142                            entrySet.clear();
143                        }
144                        notify_loco(null);
145                    }
146                }
147            }
148            @Override
149            public void occupancyMultiple(byte[] address, int messageNum, int baseAddress, int detectorCount, byte[] detectorData) {
150                log.trace("occupation: node UID: {}, node addr: {}, address: {}, baseAddress: {}, detectorCount: {}, occ states: {}", 
151                        addr.getNodeUID(), addr.getNodeAddr(), address, baseAddress, 
152                        detectorCount, detectorData);
153                if (NodeUtils.isAddressEqual(addr.getNodeAddr(), address)  &&  !addr.isPortAddr()  &&  addr.getAddr() >= baseAddress  &&  addr.getAddr() < (baseAddress + detectorCount)) {
154                    // TODO: This is very inefficent, since this function is called for each sensor! We should place the listener at a more central instance like the sensor manager
155                    // our address is in the data bytes. Check which byte and then check, if the correspondent bit is set.
156                    //log.trace("multiple occupation was signalled, states: {}, BM base Number: {}, BM count: {}, node: {}", ByteUtils.bytesToHex(detectorData), baseAddress, detectorCount, addr);
157                    int relAddr = addr.getAddr() - baseAddress;
158                    byte b = detectorData[ relAddr / 8];
159                    boolean isOccupied = (b & (1 << (relAddr % 8))) != 0;
160                    log.debug("REPORTER multi occupation was signalled, state: {}, BM addr: {}, node: {}", isOccupied ? "OCCUPIED" : "FREE", addr.getAddr(), addr);
161                    if (!isOccupied) {
162                        synchronized(entrySet) {
163                            entrySet.clear();
164                        }
165                        notify_loco(null);
166                    }
167                }
168            }
169        };
170        tc.addMessageListener(messageListener);        
171    }
172    
173    // Collecting Reporter Interface methods
174    /**
175      * {@inheritDoc}
176      */
177     @Override
178     public java.util.Collection<Object> getCollection(){
179        return entrySet;
180     }
181
182    /**
183     * {@inheritDoc}
184     * 
185     * Remove the Message Listener for this reporter
186     */
187    @Override
188    public void dispose() {
189        if (messageListener != null) {
190            tc.removeMessageListener(messageListener);        
191            messageListener = null;
192        }
193        super.dispose();
194    }
195
196    // initialize logging
197    private static final Logger log = LoggerFactory.getLogger(BiDiBReporter.class);
198
199}