001package jmri.jmrit.ctc;
002
003import java.util.ArrayList;
004import java.util.HashMap;
005import java.util.HashSet;
006import jmri.Sensor;
007import jmri.jmrit.ctc.ctcserialdata.TrafficLockingData;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011/**
012 *
013 * @author Gregory J. Bedlek Copyright (C) 2018, 2019
014 */
015
016public class TrafficLocking {
017    private final static Logger log = LoggerFactory.getLogger(TrafficLocking.class);
018
019    private static class TrafficLockingRecord {
020        private final SwitchIndicatorsRoute _mSwitchIndicatorsRoute;
021        private final NBHSensor _mOccupancyExternalSensor1;
022        private final NBHSensor _mOccupancyExternalSensor2;
023        private final NBHSensor _mOccupancyExternalSensor3;
024        private final NBHSensor _mOccupancyExternalSensor4;
025        private final NBHSensor _mOccupancyExternalSensor5;
026        private final NBHSensor _mOccupancyExternalSensor6;
027        private final NBHSensor _mOccupancyExternalSensor7;
028        private final NBHSensor _mOccupancyExternalSensor8;
029        private final NBHSensor _mOccupancyExternalSensor9;
030        private final NBHSensor _mOptionalSensor1;
031        private final NBHSensor _mOptionalSensor2;
032        private final boolean _mRuleEnabled;
033
034        public TrafficLockingRecord(String userIdentifier,
035                                    String parameter,
036                                    NBHSensor switchIndicator1,
037                                    NBHSensor switchIndicator2,
038                                    NBHSensor switchIndicator3,
039                                    NBHSensor switchIndicator4,
040                                    NBHSensor switchIndicator5,
041                                    NBHSensor occupancyExternalSensor1,
042                                    NBHSensor occupancyExternalSensor2,
043                                    NBHSensor occupancyExternalSensor3,
044                                    NBHSensor occupancyExternalSensor4,
045                                    NBHSensor occupancyExternalSensor5,
046                                    NBHSensor occupancyExternalSensor6,
047                                    NBHSensor occupancyExternalSensor7,
048                                    NBHSensor occupancyExternalSensor8,
049                                    NBHSensor occupancyExternalSensor9,
050                                    NBHSensor optionalSensor1,
051                                    NBHSensor optionalSensor2,
052                                    String ruleEnabled) {
053            _mSwitchIndicatorsRoute = new SwitchIndicatorsRoute(switchIndicator1, switchIndicator2, switchIndicator3, switchIndicator4, switchIndicator5, null);
054            _mOccupancyExternalSensor1 = occupancyExternalSensor1;
055            _mOccupancyExternalSensor2 = occupancyExternalSensor2;
056            _mOccupancyExternalSensor3 = occupancyExternalSensor3;
057            _mOccupancyExternalSensor4 = occupancyExternalSensor4;
058            _mOccupancyExternalSensor5 = occupancyExternalSensor5;
059            _mOccupancyExternalSensor6 = occupancyExternalSensor6;
060            _mOccupancyExternalSensor7 = occupancyExternalSensor7;
061            _mOccupancyExternalSensor8 = occupancyExternalSensor8;
062            _mOccupancyExternalSensor9 = occupancyExternalSensor9;
063            _mOptionalSensor1 = optionalSensor1;
064            _mOptionalSensor2 = optionalSensor2;
065            _mRuleEnabled = !ruleEnabled.equals(Bundle.getMessage("TLE_RuleDisabled")); // NOI18N  Any problem, default is ENABLED!
066        }
067
068        public boolean isEnabled() { return _mRuleEnabled; }
069        public boolean isValid(boolean fleetingEnabled) {
070            if (!_mRuleEnabled) return false;    // If disabled, treat as invalid so we skip this rule and try the next rule.
071
072            if (!_mSwitchIndicatorsRoute.isRouteSelected()
073            || !isOptionalSensorActive(_mOptionalSensor1)
074            || !isOptionalSensorActive(_mOptionalSensor2)) return false;
075            return true;
076        }
077
078//  Put all non null and valid OCCUPANCY NBHSensor's in a HashSet and return it (the "ROUTE"!) for use by LockedRoutesManager.
079        public HashSet<Sensor> getOccupancySensors() {
080            HashSet<Sensor> returnValue = new HashSet<>();
081            if (_mOccupancyExternalSensor1 != null && _mOccupancyExternalSensor1.valid()) returnValue.add(_mOccupancyExternalSensor1.getBean());
082            if (_mOccupancyExternalSensor2 != null && _mOccupancyExternalSensor2.valid()) returnValue.add(_mOccupancyExternalSensor2.getBean());
083            if (_mOccupancyExternalSensor3 != null && _mOccupancyExternalSensor3.valid()) returnValue.add(_mOccupancyExternalSensor3.getBean());
084            if (_mOccupancyExternalSensor4 != null && _mOccupancyExternalSensor4.valid()) returnValue.add(_mOccupancyExternalSensor4.getBean());
085            if (_mOccupancyExternalSensor5 != null && _mOccupancyExternalSensor5.valid()) returnValue.add(_mOccupancyExternalSensor5.getBean());
086            if (_mOccupancyExternalSensor6 != null && _mOccupancyExternalSensor6.valid()) returnValue.add(_mOccupancyExternalSensor6.getBean());
087            if (_mOccupancyExternalSensor7 != null && _mOccupancyExternalSensor7.valid()) returnValue.add(_mOccupancyExternalSensor7.getBean());
088            if (_mOccupancyExternalSensor8 != null && _mOccupancyExternalSensor8.valid()) returnValue.add(_mOccupancyExternalSensor8.getBean());
089            if (_mOccupancyExternalSensor9 != null && _mOccupancyExternalSensor9.valid()) returnValue.add(_mOccupancyExternalSensor9.getBean());
090            returnValue.remove(null);   // Safety: Remove null entry if it exists (There will ONLY be one in a set!)
091            return returnValue;
092        }
093
094//  Quick and Dirty Routine: If it doesn't exist, it's lit.  If it exists, ACTIVE = lit.  Can't use CTCMain.getSensorKnownState() because of this.
095        private boolean isOptionalSensorActive(NBHSensor sensor) {
096            if (sensor.valid()) return sensor.getKnownState() == Sensor.ACTIVE;
097            return true;    // Doesn't exist.
098        }
099
100    }
101
102    private final ArrayList<TrafficLockingRecord> _mLeftTrafficLockingRulesArrayList = new ArrayList<>();
103    private final ArrayList<TrafficLockingRecord> _mRightTrafficLockingRulesArrayList = new ArrayList<>();
104    private final String _mUserIdentifier;
105    private final ArrayList<TrafficLockingData> _mLeftTrafficLockingRulesList;
106    private final ArrayList<TrafficLockingData> _mRightTrafficLockingRulesList;
107    private final LockedRoutesManager _mLockedRoutesManager;
108
109    public TrafficLocking(String userIdentifier, ArrayList<TrafficLockingData> _mTRL_LeftTrafficLockingRules, ArrayList<TrafficLockingData> _mTRL_RightTrafficLockingRules, LockedRoutesManager lockedRoutesManager)
110    {
111        _mUserIdentifier = userIdentifier;                                      // Squirrel it
112        _mLeftTrafficLockingRulesList = _mTRL_LeftTrafficLockingRules;      // away for later
113        _mRightTrafficLockingRulesList = _mTRL_RightTrafficLockingRules;    // "fileReadComplete"
114        _mLockedRoutesManager = lockedRoutesManager;
115    }
116
117    public void removeAllListeners() {}   // None done.
118
119//  Since the user may specify "forward referenced" O/S sections (i.e. an entry references an O.S. section that hasn't been read in and created yet),
120//  we delay processing of everything until after the file has been completely read in.  Here we do the real work:
121    public void fileReadComplete(HashMap<Integer, CodeButtonHandler> cbHashMap, HashMap<Integer, SwitchDirectionIndicators> swdiHashMap) {
122        addAllTrafficLockingEntries(_mUserIdentifier, _mLeftTrafficLockingRulesList, "leftTrafficLockingRulesList", cbHashMap, swdiHashMap, _mLeftTrafficLockingRulesArrayList);     // NOI18N
123        addAllTrafficLockingEntries(_mUserIdentifier, _mRightTrafficLockingRulesList, "rightTrafficLockingRulesList", cbHashMap, swdiHashMap, _mRightTrafficLockingRulesArrayList);  // NOI18N
124    }
125
126    private void addAllTrafficLockingEntries(   String                                                  userIdentifier,
127                                                ArrayList<TrafficLockingData>                           trafficLockingRulesList,
128                                                String                                                  parameter,
129                                                HashMap<Integer, CodeButtonHandler>                     cbHashMap,
130                                                HashMap<Integer, SwitchDirectionIndicators>             swdiHashMap,
131                                                ArrayList<TrafficLockingRecord>                         trafficLockingRecordsArrayList) {  // <- Output
132        trafficLockingRulesList.forEach(row -> {
133            // Convert TrafficLockingData into a set of fixed size ArrayLists
134            ArrayList<NBHSensor> occupancySensors = row.getOccupancySensors();
135            ArrayList<NBHSensor> optionalSensors = row.getOptionalSensors();
136            ArrayList<Integer> ids = row.getUniqueIDs();
137            ArrayList<String> alignments = row.getAlignments();
138
139            int osSection1UniqueID = ids.get(0);
140            int osSection2UniqueID = ids.get(1);
141            int osSection3UniqueID = ids.get(2);
142            int osSection4UniqueID = ids.get(3);
143            int osSection5UniqueID = ids.get(4);
144
145            TrafficLockingRecord trafficLockingRecord
146                = new TrafficLockingRecord( userIdentifier,
147                                            parameter,
148                                            getSwitchDirectionIndicatorSensor(osSection1UniqueID, alignments.get(0), swdiHashMap),
149                                            getSwitchDirectionIndicatorSensor(osSection2UniqueID, alignments.get(1), swdiHashMap),
150                                            getSwitchDirectionIndicatorSensor(osSection3UniqueID, alignments.get(2), swdiHashMap),
151                                            getSwitchDirectionIndicatorSensor(osSection4UniqueID, alignments.get(3), swdiHashMap),
152                                            getSwitchDirectionIndicatorSensor(osSection5UniqueID, alignments.get(4), swdiHashMap),
153                                            occupancySensors.get(0),
154                                            occupancySensors.get(1),
155                                            occupancySensors.get(2),
156                                            occupancySensors.get(3),
157                                            occupancySensors.get(4),
158                                            occupancySensors.get(5),
159                                            occupancySensors.get(6),
160                                            occupancySensors.get(7),
161                                            occupancySensors.get(8),
162                                            optionalSensors.get(0),
163                                            optionalSensors.get(1),
164                                            row._mRuleEnabled);
165            if (!trafficLockingRecord.getOccupancySensors().isEmpty()) {
166                trafficLockingRecordsArrayList.add(trafficLockingRecord);
167            }
168        });
169    }
170
171    public TrafficLockingInfo valid(int presentSignalDirectionLever, boolean fleetingEnabled) {
172        if (presentSignalDirectionLever == CTCConstants.LEFTTRAFFIC) return validForTraffic(_mLeftTrafficLockingRulesArrayList, false, fleetingEnabled);
173        return validForTraffic(_mRightTrafficLockingRulesArrayList, true, fleetingEnabled);
174    }
175
176    private TrafficLockingInfo validForTraffic(ArrayList<TrafficLockingRecord> trafficLockingRecordArrayList, boolean rightTraffic, boolean fleetingEnabled) {
177        TrafficLockingInfo returnValue = new TrafficLockingInfo(true);          // ASSUME valid return status
178        if (trafficLockingRecordArrayList.isEmpty()) return returnValue; // No rules, OK all of the time.
179//  If ALL are disabled, then treat as if nothing in there, always allow, otherwise NONE would be valid!
180        boolean anyEnabled = false;
181        for (int index = 0; index < trafficLockingRecordArrayList.size(); index++) {
182            if (trafficLockingRecordArrayList.get(index).isEnabled()) { anyEnabled = true; break; }
183        }
184        if (!anyEnabled) return returnValue; // None enabled, always allow.
185
186        for (int index = 0; index < trafficLockingRecordArrayList.size(); index++) {
187            TrafficLockingRecord trafficLockingRecord = trafficLockingRecordArrayList.get(index);
188            if (trafficLockingRecord.isValid(fleetingEnabled)) {
189//  Ah, we found a rule that matches the route.  See if that route
190//  is in conflict with any other routes presently in effect:
191                String ruleNumber = Integer.toString(index+1);
192                returnValue._mLockedRoute = _mLockedRoutesManager.checkRouteAndAllocateIfAvailable(trafficLockingRecord.getOccupancySensors(), _mUserIdentifier, "Rule #" + ruleNumber, rightTraffic);
193                if (returnValue._mLockedRoute != null) { // OK:
194                    if (jmri.InstanceManager.getDefault(CTCMain.class)._mCTCDebug_TrafficLockingRuleTriggeredDisplayLoggingEnabled) log.info("Rule {} valid", ruleNumber);
195                    return returnValue;
196                }
197            }
198        }
199        returnValue._mReturnStatus = false;
200        return returnValue;
201    }
202
203    private NBHSensor getSwitchDirectionIndicatorSensor(int uniqueID, String switchAlignment, HashMap<Integer, SwitchDirectionIndicators> swdiHashMap) {
204        if (uniqueID < 0) return null;
205        boolean isNormalAlignment = !switchAlignment.equals(Bundle.getMessage("TLE_Reverse"));  // NOI18N
206        SwitchDirectionIndicators switchDirectionIndicators = swdiHashMap.get(uniqueID);
207        if (switchDirectionIndicators == null) return null;     // Safety, technically shouldn't happen....
208        return switchDirectionIndicators.getProperIndicatorSensor(isNormalAlignment);
209    }
210}