001package jmri.implementation;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.beans.PropertyChangeSupport;
006import java.util.List;
007import java.util.Set;
008import javax.annotation.concurrent.GuardedBy;
009import jmri.Block;
010import jmri.BlockManager;
011import jmri.CabSignal;
012import jmri.InstanceManager;
013import jmri.LocoAddress;
014import jmri.SignalMast;
015import jmri.Path;
016import jmri.jmrit.display.layoutEditor.LayoutBlockManager;
017import org.slf4j.Logger;
018import org.slf4j.LoggerFactory;
019
020/**
021 * Default implementation of a Cab Signal Object, describing the state of the 
022 * track ahead relative to a locomotive with a given address.  This is 
023 * effectively a mobile signal mast.
024 *
025 * @author Steve Young Copyright (C) 2018
026 * @author Paul Bender Copyright (C) 2019
027 */
028public class DefaultCabSignal implements CabSignal, PropertyChangeListener {
029
030    private LocoAddress _address = null;
031    @GuardedBy("this")
032    private Block _currentBlock = null;
033    private Block _nextBlock = null;
034    private SignalMast _nextMast = null;
035    private boolean _cabSignalActive = true;
036    private boolean _masterPausedButtonActive = false;
037    private PropertyChangeListener _cconSignalMastListener = null;
038
039    public DefaultCabSignal(LocoAddress address){
040       _address = address;
041    }
042
043    /**
044     * A method for cleaning up the cab signal 
045     */
046    @Override
047    public void dispose(){
048        if (_nextMast != null) {
049            _nextMast.removePropertyChangeListener(_cconSignalMastListener);
050        }
051       _address = null;
052       _currentBlock = null;
053       _nextBlock = null;
054       _nextMast = null;
055       _cabSignalActive = true;
056       _masterPausedButtonActive = false;
057    }
058
059    /**
060     * Get the LocoAddress associated with the consist
061     *
062     * @return the cab signal address
063     */
064    @Override
065    public LocoAddress getCabSignalAddress(){
066        return _address;
067    }
068
069    /**
070     * Set the Block of the locomotive
071     *
072     * @param position is a Block the locomotive is in.
073     */
074    @Override
075    synchronized public void setBlock(Block position){
076        log.debug("CabSignal for {} set block {}",getCabSignalAddress(),position);
077        Block oldCurrentBlock = _currentBlock;
078        if(_currentBlock!=null){
079           _currentBlock.removePropertyChangeListener(this);
080        }
081        _currentBlock = position;
082        if(_currentBlock!=null) {
083           _currentBlock.addPropertyChangeListener(this);
084           if(!_currentBlock.equals(oldCurrentBlock)) {
085              firePropertyChange("CurrentBlock",_currentBlock,oldCurrentBlock);
086           }
087       } else {
088           // currentblock is null, notify if old block was not.
089           if(oldCurrentBlock!=null){
090              firePropertyChange("CurrentBlock",_currentBlock,oldCurrentBlock);
091           }
092       }
093       getNextBlock(); // calculate the next block and fire an appropriate property change.
094       // calculate the next mast and fire an appropriate property change.
095       forwardCabSignalToLayout();
096    }
097
098    /**
099     * Set the Block of the locomotive by searching the block list.
100     */
101    @Override
102    synchronized public void setBlock(){
103        BlockManager bmgr = jmri.InstanceManager.getDefault(jmri.BlockManager.class);
104        Set<Block> blockSet = bmgr.getNamedBeanSet();
105        LocoAddress addr = getCabSignalAddress();
106        for (Block blockVal : blockSet) {
107            if ( blockVal.getValue() != null ) {
108                Object val = blockVal.getValue();
109                log.debug("CabSignal for {} searching block {} value {}",
110                           addr,blockVal,val);
111                if (val instanceof jmri.AddressedIdTag) {
112                    if( ((jmri.AddressedIdTag)val).getLocoAddress().toString().equals( 
113                         addr.toString())){
114                       setBlock(blockVal); 
115                       return;
116                    }
117                } else if (blockVal.getValue().equals(addr) ||
118                    blockVal.getValue().toString().equals(addr.toString()) || 
119                    blockVal.getValue().toString().equals("" + addr.getNumber())) {
120                    setBlock(blockVal);
121                    return;
122                }
123            }
124        }
125        // address not found in any block, set block to null
126        setBlock(null);
127    }
128
129    /**
130     * Get the Block position of the locomotive associated with the cab signal.
131     *
132     * @return The current Block position
133     */
134    @Override
135    synchronized public Block getBlock(){
136        return _currentBlock;
137    }
138
139    /**
140     * Get the Next Block the locomotive is expected to enter.
141     * This value is calculated from the current block and direction 
142     * of travel.
143     *
144     * @return The next Block position
145     */
146    @Override
147    public Block getNextBlock(){
148        Block oldNextBlock = _nextBlock;
149        if(getBlock()==null){
150           _nextBlock = null; // no current block, so can't have a next block.
151        } else {
152          _nextBlock = nextBlockOnPath(getBlock(),getBlock().getDirection());
153        }
154    
155        if(_nextBlock!=null) {
156            if(!_nextBlock.equals(oldNextBlock)) {
157               firePropertyChange("NextBlock",_nextBlock,oldNextBlock);
158            }
159        } else {
160            // currentNextBlock is null, notify if old next block was not.
161            if(oldNextBlock!=null){
162               firePropertyChange("NextBlock",_nextBlock,oldNextBlock);
163            }
164        }
165        return _nextBlock;
166    }
167
168    private Block nextBlockOnPath(Block current, int fromdirection){
169        List<Path> thispaths = current.getPaths();
170        for (final Path testpath : thispaths) {
171            if (testpath.checkPathSet()) {
172                Block blockTest = testpath.getBlock();
173                int dirftTest = testpath.getFromBlockDirection();
174                int dirtoTest = testpath.getToBlockDirection();
175                if ((((fromdirection & Path.NORTH) != 0) && ((dirtoTest & Path.NORTH) != 0)) ||
176                    (((fromdirection & Path.SOUTH) != 0) && ((dirtoTest & Path.SOUTH) != 0)) ||
177                    (((fromdirection & Path.EAST) != 0) && ((dirtoTest & Path.EAST) != 0)) ||
178                    (((fromdirection & Path.WEST) != 0) && ((dirtoTest & Path.WEST) != 0)) ||
179                    (((fromdirection & Path.CW) != 0) && ((dirtoTest & Path.CW) != 0)) ||
180                    (((fromdirection & Path.CCW) != 0) && ((dirtoTest & Path.CCW) != 0)) ||
181                    (((fromdirection & Path.LEFT) != 0) && ((dirtoTest & Path.LEFT) != 0)) ||
182                    (((fromdirection & Path.RIGHT) != 0) && ((dirtoTest & Path.RIGHT) != 0)) ||
183                    (((fromdirection & Path.UP) != 0) && ((dirtoTest & Path.UP) != 0)) ||
184                    (((fromdirection & Path.DOWN) != 0) && ((dirtoTest & Path.DOWN) != 0)))
185                { // most reliable
186                    blockTest.setDirection(dirtoTest);
187                    return blockTest;
188                }
189                if ((fromdirection & dirftTest) == 0) { // less reliable
190                    blockTest.setDirection(dirtoTest);
191                    return blockTest;
192                }
193                if ((fromdirection != dirftTest)){ // least reliable but copes with 180 degrees 
194                    blockTest.setDirection(dirtoTest);
195                    return blockTest;
196                }
197            }
198        }
199      return null;
200    }
201
202    /**
203     * Get the Next Signal Mast the locomotive is expected to pass.
204     * This value is calculated from the current block and direction 
205     * of travel.
206     *
207     * @return The next SignalMast position
208     */
209    @Override
210    public SignalMast getNextMast(){
211        SignalMast oldNextMast = _nextMast;
212        if (_nextMast != null) {
213            _nextMast.removePropertyChangeListener(_cconSignalMastListener);
214        }
215        _nextMast=null;
216        if(getBlock()!=null){
217           LayoutBlockManager lbm = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class);
218        
219            Block b = getBlock();
220            Block nB = getNextBlock();
221            while(_nextMast == null && nB !=null ) {
222                _nextMast = lbm.getFacingSignalMast(b, nB);
223                b = nB;
224                nB = nextBlockOnPath(b,b.getDirection()); 
225            }
226            if ( _nextMast == null) {
227                // use block b which is the last non-null block in the path
228                _nextMast = lbm.getSignalMastAtEndBumper(b,null);
229            }
230           
231            if ( _nextMast != null) {
232                // add signal changelistener
233                _nextMast.addPropertyChangeListener(_cconSignalMastListener = (PropertyChangeEvent e) -> {
234                // aspect changed?, need to notify
235                firePropertyChange("MastChanged",e.getNewValue(),e.getOldValue());
236                forwardCabSignalToLayout();
237                });
238            }
239        }
240        if(_nextMast!=null) {
241            if(!_nextMast.equals(oldNextMast)) {
242                firePropertyChange("NextMast",_nextMast,oldNextMast);
243            }
244        } else {
245            // currentNextMast is null, notify if old next mast was not.
246            if(oldNextMast!=null){
247               firePropertyChange("NextMast",_nextMast,oldNextMast);
248            }
249        }
250        return _nextMast;
251    }
252
253    /**
254     * Forward the current cab signal value to the layout.
255     */
256    @Override
257    public void forwardCabSignalToLayout() {
258        if (!isCabSignalActive() ) {
259            return;
260        }
261        if (_masterPausedButtonActive) {
262            return;
263        }
264
265        LocoAddress locoaddr = getCabSignalAddress();
266        SignalMast mast = getNextMast();
267
268        if (mast != null) {
269            log.debug("cab {} aspect {}",locoaddr,mast.getAspect());
270        }
271        // and forward the message on to the layout.
272        forwardAspectToLayout();
273    }
274
275    /**
276     * Forward the command to the layout that sets the displayed signal
277     * aspect for this address
278     */
279    protected void forwardAspectToLayout(){
280        // this method is to be over-written by subclasses that actually
281        // talk to layout hardware.
282    }
283
284
285    /*
286     * get whether this cab signal is on or off
287     *
288     * @return true if on, false if off
289     */
290    @Override
291    public boolean isCabSignalActive(){
292        return _cabSignalActive;
293    }
294
295    /*
296     * set whether this cab signal is on or off
297     *
298     * @param active true if on, false if off
299     */
300    @Override
301    public void setCabSignalActive(boolean active){
302        _cabSignalActive = active;
303        if(_cabSignalActive) {
304           getNextMast(); // refreshes block, mast, and sends if master button not paused
305        }
306        else {
307            resetLayoutCabSignal(); // send data invalid to layout
308        }
309    }
310    
311    /*
312     * Set when initialised and when Master PAUSED button is toggled
313     *
314     * @param active true if paused, false if resumed
315     */
316    @Override
317    public void setMasterCabSigPauseActive (boolean active) {
318        _masterPausedButtonActive = active;
319        if ( !isCabSignalActive() ){
320            return; // if cabsig has already been disabled no action needed
321        }
322        if ( _masterPausedButtonActive ) {
323            log.debug("master paused");
324            resetLayoutCabSignal(); // send data invalid to layout
325        }
326        else {
327            log.debug("master not paused");
328            getNextMast(); // refreshes block, mast, and sends if single cabsig enabled
329        }
330    }
331
332    /**
333     * Forward the command to the layout that clears any displayed signal
334     * for this address
335     */
336    protected void resetLayoutCabSignal(){
337        // this method is to be over-written by subclasses that actually
338        // talk to layout hardware.
339    }
340
341
342    PropertyChangeSupport pcs = new PropertyChangeSupport(this);
343
344    /**
345     * Add a listener for consist events
346     *
347     * @param l is a PropertyChangeListener object
348     */
349    @Override
350    public synchronized void addPropertyChangeListener(PropertyChangeListener l) {
351        pcs.addPropertyChangeListener(l);
352    }
353
354    /**
355     * Remove a listener for cab signal events
356     *
357     * @param l is a PropertyChangeListener object
358     */
359    @Override
360    public synchronized void removePropertyChangeListener(PropertyChangeListener l) {
361        pcs.removePropertyChangeListener(l);
362    }
363
364    protected void firePropertyChange(String p, Object old, Object n) {
365        log.debug("sending property {} new value {} old value {}",p,old,n);
366        pcs.firePropertyChange(p, old, n);
367    }
368
369    //PropertyChangeListener interface
370    @Override
371    public void propertyChange(PropertyChangeEvent event){
372       if(event.getSource() instanceof Block) {
373          if (event.getPropertyName().equals("value")){
374             setBlock(); // change the block.
375           }
376
377           // block value is changed before direction is set
378           if ((event.getPropertyName().equals("state")) || (event.getPropertyName().equals("direction"))) {
379              // update internal state to cascade changes.
380              getNextBlock();
381              forwardCabSignalToLayout();
382           }
383       } else if(event.getSource() instanceof SignalMast) {
384           // update internal state to cascade changes.
385           forwardCabSignalToLayout();
386       }
387    }
388
389    private static final Logger log = LoggerFactory.getLogger(DefaultCabSignal.class);
390
391
392}