001package jmri.jmrit.ctc.topology;
002
003import java.util.*;
004import jmri.*;
005import jmri.jmrit.ctc.ctcserialdata.CTCSerialData;
006import jmri.jmrit.display.layoutEditor.*;
007
008/**
009 *
010 * IF AND ONLY IF LayoutDesign is available:
011 *
012 * This object creates a list of objects that describe the path
013 * from the passed O.S. section to ALL other adjacent O.S. sections in a
014 * specified direction.  It finds all sensors and turnouts (and their alignments).
015 *
016 * Ultimately, this will provide information to fill in completely
017 * (for the direction indicated: left / right traffic) the table
018 * _mTRL_TrafficLockingRulesSSVList in FrmTRL_Rules.java
019 *
020 * Sorry to say that as of 7/16/2020, LayoutBlock routine
021 * "getNeighbourAtIndex" does NOT work in complex track situations
022 * (one that I know of: Double crossovers), which leads to a
023 * failure to "auto-generate" properly Signal Mast Logic for specific
024 * signal masts.  I wanted to avoid this.
025 *
026 * By this time the user SHOULD HAVE been done with all ABS (or APB, etc) rules
027 * for signals.  We rely on this to generate our information.
028 *
029 * Also, NEVER allocate the terminating O.S. section, otherwise the dispatcher
030 * cannot clear the terminating O.S. section IN THE SAME DIRECTION as the
031 * originating O.S. section!
032 *
033 * @author Gregory J. Bedlek Copyright (C) 2018, 2019, 2020
034 */
035
036public class Topology {
037    private final CTCSerialData _mCTCSerialData;
038    private final String _mNormal;
039    private final String _mReverse;
040    private final SignalMastLogicManager _mSignalMastLogicManager = InstanceManager.getDefault(jmri.SignalMastLogicManager.class);
041    private final LayoutBlockManager _mLayoutBlockManager = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class);
042    private ArrayList<Block> _mStartingBlocks = new ArrayList<>();
043
044    /**
045     * DO NOT USE, only for test suite!  Object will crash if anything referenced in it.
046     * This constructor will be replaced by something more useful to the test suite
047     * "someday" when the test suite it coded for it.
048     */
049    public Topology() {
050        _mCTCSerialData = null;
051        _mNormal = null;
052        _mReverse = null;
053    }
054
055
056    /**
057     * @param CTCSerialData                     The one and only.
058     * @param OSSectionOccupiedExternalSensors  List of sensors to start from.
059     * @param normal                            Bundle.getMessage("TLE_Normal")
060     * @param reverse                           Bundle.getMessage("TLE_Reverse")
061     */
062    public Topology(CTCSerialData CTCSerialData, ArrayList<String> OSSectionOccupiedExternalSensors, String normal, String reverse) {
063        _mCTCSerialData = CTCSerialData;
064        _mNormal = normal;
065        _mReverse = reverse;
066        for (String OSSectionOccupiedExternalSensor : OSSectionOccupiedExternalSensors) {
067            Sensor startingOSSectionSensor = InstanceManager.getDefault(SensorManager.class).getSensor(OSSectionOccupiedExternalSensor);
068            if (null != startingOSSectionSensor) { // Available:
069                LayoutBlock startingLayoutBlock = _mLayoutBlockManager.getBlockWithSensorAssigned(startingOSSectionSensor);
070                if (null != startingLayoutBlock) { // Available:
071                    _mStartingBlocks.add(startingLayoutBlock.getBlock());
072                }
073            }
074        }
075    }
076
077
078    /**
079     * @return boolean - true if available, else false.
080     */
081    public boolean isTopologyAvailable() { return !_mStartingBlocks.isEmpty(); }
082
083    /**
084     *
085     * @param leftTraffic Traffic direction ON THE CTC PANEL that we are generating the rules for.
086     * @return TopologyInfo.  All of the possible paths from this O.S. section to all
087     * destination(s) in the direction indicated.
088     *
089     * WE HAVE TO GO in the opposite direction to get the mast for the O.S. section we are in!
090     * There can ONLY be one signal in that direction, so upon encountering it, PROCESS ONLY IT!
091     * (we could check if there is another, and issue an error, but I believe that the Panel Editor
092     * would prevent this!)
093     *
094     * JMRI: West is left, East is right.
095     */
096    public ArrayList<TopologyInfo> getTrafficLockingRules(boolean leftTraffic) {
097        ArrayList<TopologyInfo> returnValue = new ArrayList<>();
098        for (Block startingBlock: _mStartingBlocks) {
099            for (Path path : startingBlock.getPaths()) {
100                Block neighborBlock = path.getBlock();
101                if (inSameDirectionGenerally(getDirectionArrayListFrom(leftTraffic ? Path.EAST : Path.WEST), path.getToBlockDirection())) {
102                    SignalMast facingSignalMast = _mLayoutBlockManager.getFacingSignalMast(neighborBlock, startingBlock);
103                    if (null != facingSignalMast) { // Safety
104                        SignalMastLogic facingSignalMastLogic = _mSignalMastLogicManager.getSignalMastLogic(facingSignalMast);
105                        if (null != facingSignalMastLogic) { // Safety
106                            processDestinations(returnValue, facingSignalMastLogic);
107                        }
108                    }
109                }
110            }
111        }
112        return returnValue;
113    }
114
115
116    /**
117     * Once we've "backed up" to look forward from the starting O.S. section and gotten a facingSignalMastLogic, this
118     * routine fills in "topologyInfo" with everything it finds from that point on.  Handles intermediate blocks also
119     * (ABS, APB, etc.).  Goes until there is no more.
120     *
121     * @param topologyInfos             Entry(s) added to this ArrayList as they are encountered.  It is NOT cleared first,
122     *                                  as there may be entries from prior calls to this routine in here.
123     * @param facingSignalMastLogic     Facing signal mast logic from O.S. section code.
124     */
125    private void processDestinations(ArrayList<TopologyInfo> topologyInfos, SignalMastLogic facingSignalMastLogic) {
126        for (SignalMast destinationSignalMast : facingSignalMastLogic.getDestinationList()) {
127            TopologyInfo topologyInfo = new TopologyInfo(_mCTCSerialData, destinationSignalMast.getUserName(), _mNormal, _mReverse);
128            createRules(topologyInfo, facingSignalMastLogic, destinationSignalMast);
129            for (SignalMast tempSignalMast = destinationSignalMast; isIntermediateSignalMast(tempSignalMast); ) {
130                SignalMastLogic tempSignalMastLogic = _mSignalMastLogicManager.getSignalMastLogic(tempSignalMast);
131                if (null != tempSignalMastLogic) { // Safety:
132 // No safety check needed here, "isIntermediateSignalMast" GUARANTEES that there is EXACTLY one entry in "getDestinationList" list:
133                    SignalMast onlyTerminatingSignalMast = tempSignalMastLogic.getDestinationList().get(0);
134                    createRules(topologyInfo, tempSignalMastLogic, onlyTerminatingSignalMast);
135                    tempSignalMast = onlyTerminatingSignalMast;       // Next iteration, start where we left off.
136                } else {
137                    break;  // Stop immediately, got an issue.
138                }
139            }
140            if (topologyInfo.nonEmpty()) {
141                topologyInfos.add(topologyInfo);
142            }
143        }
144    }
145
146
147    /**
148     * Simple routine to create all of the rules from the past information in "topologyInfo".
149     * @param topologyInfo     What to fill in.
150     * @param signalMastLogic  From this
151     * @param signalMast       And this.
152     */
153    private void createRules(TopologyInfo topologyInfo, SignalMastLogic signalMastLogic, SignalMast signalMast) {
154        topologyInfo.addBlocks(signalMastLogic.getBlocks(signalMast));
155        topologyInfo.addBlocks(signalMastLogic.getAutoBlocks(signalMast));
156        topologyInfo.addTurnouts(signalMastLogic, signalMast);
157    }
158
159
160    /**
161     * Is the past Signal Mast a intermediate signal?  (ABS / APB or some such)?
162     * If there are no turnouts, and there is ONLY ONE destination signal, then true, else false.
163     *
164     * @param signalMast
165     * @return true if so, else false.  False also returned if invalid parameter in some way.
166     */
167//  Plagerization of DefaultSignalMastLogicManager/discoverSignallingDest "intermediateSignal" property code.
168    private boolean isIntermediateSignalMast(SignalMast signalMast) {
169        if (null != signalMast) { // Safety
170            SignalMastLogic signalMastLogic = _mSignalMastLogicManager.getSignalMastLogic(signalMast);
171            if (null != signalMastLogic) { // Safety:
172                return signalMastLogic.getDestinationList().size() == 1
173                        && signalMastLogic.getAutoTurnouts(signalMastLogic.getDestinationList().get(0)).isEmpty()
174                        && signalMastLogic.getTurnouts(signalMastLogic.getDestinationList().get(0)).isEmpty();
175            }
176        }
177        return false;   // OOPPSS invalid, can't determine, assume NOT a intermediate signal.
178    }
179
180
181    /**
182     *
183     * @param direction Direction to generate list from.
184     * @return IF passed a valid direction, a 3 element set of "generally in the same direction" directions, else an EMPTY set (NOT null!)
185     */
186    private ArrayList<Integer> getDirectionArrayListFrom(int direction) {
187        switch (direction) {
188            case Path.NORTH:
189                return new ArrayList<>(Arrays.asList(Path.NORTH_WEST, Path.NORTH, Path.NORTH_EAST));
190            case Path.NORTH_EAST:
191                return new ArrayList<>(Arrays.asList(Path.NORTH, Path.NORTH_EAST, Path.EAST));
192            case Path.EAST:
193                return new ArrayList<>(Arrays.asList(Path.NORTH_EAST, Path.EAST, Path.SOUTH_EAST));
194            case Path.SOUTH_EAST:
195                return new ArrayList<>(Arrays.asList(Path.EAST, Path.SOUTH_EAST, Path.SOUTH));
196            case Path.SOUTH:
197                return new ArrayList<>(Arrays.asList(Path.SOUTH_EAST, Path.SOUTH, Path.SOUTH_WEST));
198            case Path.SOUTH_WEST:
199                return new ArrayList<>(Arrays.asList(Path.SOUTH, Path.SOUTH_WEST, Path.WEST));
200            case Path.WEST:
201                return new ArrayList<>(Arrays.asList(Path.SOUTH_WEST, Path.WEST, Path.NORTH_WEST));
202            case Path.NORTH_WEST:
203                return new ArrayList<>(Arrays.asList(Path.WEST, Path.NORTH_WEST, Path.NORTH));
204            default:
205                return new ArrayList<>();    // Huh?
206        }
207    }
208
209
210    /**
211     *
212     * @param possibleDirections The set of possible directions to check.  Caveat Emptor: IF this
213     *                           array has no entries, then this routine returns false.
214     *
215     * @param direction Direction to check.
216     * @return True if direction in "possibleDirections", else false.
217     */
218    private boolean inSameDirectionGenerally(ArrayList<Integer> possibleDirections, int direction) {
219        if (possibleDirections.isEmpty()) return false;
220        return possibleDirections.contains(direction);
221    }
222}