001/**
002 *  @author Gregory J. Bedlek Copyright (C) 2018, 2019, 2020
003 */
004
005package jmri.jmrit.ctc;
006
007import java.beans.PropertyChangeEvent;
008import java.beans.PropertyChangeListener;
009import java.util.ArrayList;
010import java.util.HashMap;
011import java.util.LinkedList;
012
013import jmri.*;
014import jmri.jmrit.ctc.ctcserialdata.CTCSerialData;
015import jmri.jmrit.ctc.ctcserialdata.CodeButtonHandlerData;
016import jmri.jmrit.ctc.ctcserialdata.OtherData;
017import jmri.util.swing.JmriJOptionPane;
018
019public class CTCMain {
020
021    private final CTCSerialData _mCTCSerialData;
022    private final ArrayList<CodeButtonHandler> _mCodeButtonHandlersArrayList = new ArrayList<>();       // "Const" after initialization completes.
023    private NBHSensor _mCTCDebugSystemReloadInternalSensor = null;
024    private final PropertyChangeListener _mCTCDebugSystemReloadInternalSensorPropertyChangeListener = (PropertyChangeEvent e) -> { handleCTCDebugSystemReload(e); };
025    private NBHSensor _mCTCDebug_TrafficLockingRuleTriggeredDisplayInternalSensor = null;
026    private final PropertyChangeListener _mCTCDebug_TrafficLockingRuleTriggeredDisplayInternalSensorPropertyChangeListener = (PropertyChangeEvent e) -> { handleLogging(e); };
027    private final HashMap<Integer, SignalDirectionIndicatorsInterface> _mSIDIHashMap = new HashMap<>(); // "Const" after initialization completes.
028    private final HashMap<Integer, SwitchDirectionIndicators> _mSWDIHashMap = new HashMap<>();          // "Const" after initialization completes.
029    private final HashMap<Integer, CodeButtonHandler> _mCBHashMap = new HashMap<>();                    // "Const" after initialization completes.
030    private final LockedRoutesManager _mLockedRoutesManager = new LockedRoutesManager();
031    private javax.swing.Timer _mLockTurnoutsTimer = null;
032    private final CTCExceptionBuffer _mCTCExceptionBuffer = new CTCExceptionBuffer();
033
034//  So that external python script can set locks on all of the lockable turnouts:
035    public void externalLockTurnout() {
036        for (CodeButtonHandler codeButtonHandler : _mCodeButtonHandlersArrayList) {
037            codeButtonHandler.externalLockTurnout();
038        }
039    }
040
041    public CTCMain() {
042        CtcManager mgr = InstanceManager.getDefault(CtcManager.class);
043        _mCTCSerialData = mgr.getCTCSerialData();
044        InstanceManager.store(_mCTCExceptionBuffer, CTCExceptionBuffer.class);
045    }
046
047    private void handleCTCDebugSystemReload(PropertyChangeEvent e) {
048        if (e.getPropertyName().equals("KnownState") && (int)e.getNewValue() == Sensor.ACTIVE) {    // NOI18N
049            log.info("{} : Reload", Bundle.getMessage("CTCMainShuttingDown"));          // NOI18N
050            shutdown();
051            startup();
052        }
053    }
054
055    public boolean _mCTCDebug_TrafficLockingRuleTriggeredDisplayLoggingEnabled = false;
056    private void handleLogging(PropertyChangeEvent e) {
057        if (e.getPropertyName().equals("KnownState")) {         // NOI18N
058            _mCTCDebug_TrafficLockingRuleTriggeredDisplayLoggingEnabled = (int)e.getNewValue() == Sensor.ACTIVE;
059            if (_mCTCDebug_TrafficLockingRuleTriggeredDisplayLoggingEnabled) _mLockedRoutesManager.dumpAllRoutes();
060        }
061    }
062
063//  This will insure all objects are disconnected from propertyChangeListeners:
064    private void shutdown() {
065        for (CodeButtonHandler codeButtonHandler : _mCodeButtonHandlersArrayList) {
066            codeButtonHandler.removeAllListeners();
067        }
068        _mLockedRoutesManager.removeAllListeners();
069        _mCTCDebugSystemReloadInternalSensor.removePropertyChangeListener(_mCTCDebugSystemReloadInternalSensorPropertyChangeListener);
070        _mCTCDebug_TrafficLockingRuleTriggeredDisplayInternalSensor.removePropertyChangeListener(_mCTCDebug_TrafficLockingRuleTriggeredDisplayInternalSensorPropertyChangeListener);
071    }
072
073    void startup() {
074        _mLockedRoutesManager.clearAllLockedRoutes();
075        SignalDirectionIndicators.resetSignalsUsed();
076
077//  One of's:
078        OtherData otherData = _mCTCSerialData.getOtherData();
079        Fleeting fleeting = new Fleeting(   otherData._mFleetingToggleInternalSensor,
080                                            otherData._mDefaultFleetingEnabled);
081
082        ArrayList <CodeButtonHandlerData> codeButtonHandlerDataList = _mCTCSerialData.getCodeButtonHandlerDataArrayList();
083        LinkedList <TrafficLocking> trafficLockingFileReadComplete = new LinkedList<>();
084
085//  For each code button defined:
086        codeButtonHandlerDataList.forEach((codeButtonHandlerData) -> {
087
088            boolean slavedSwitch = codeButtonHandlerData._mOSSectionSwitchSlavedToUniqueID != -1;
089            String userIdentifier = codeButtonHandlerData.myShortStringNoComma() + ": ";
090            boolean turnoutLockingOnlyEnabled
091                    = !codeButtonHandlerData._mSIDI_Enabled
092                    && !codeButtonHandlerData._mSIDL_Enabled
093                    && !codeButtonHandlerData._mSWDI_Enabled
094                    && !codeButtonHandlerData._mSWDL_Enabled
095                    && !codeButtonHandlerData._mCO_Enabled
096                    && !codeButtonHandlerData._mTRL_Enabled
097                    && codeButtonHandlerData._mTUL_Enabled
098                    && !codeButtonHandlerData._mIL_Enabled;
099
100// Slave Switch: null
101            SignalDirectionIndicatorsInterface signalDirectionIndicators = (codeButtonHandlerData._mSIDI_Enabled && !slavedSwitch) ?
102                new SignalDirectionIndicators(  userIdentifier,
103                                                codeButtonHandlerData._mSIDI_LeftInternalSensor,
104                                                codeButtonHandlerData._mSIDI_NormalInternalSensor,
105                                                codeButtonHandlerData._mSIDI_RightInternalSensor,
106                                                codeButtonHandlerData._mSIDI_CodingTimeInMilliseconds,
107                                                codeButtonHandlerData._mSIDI_TimeLockingTimeInMilliseconds,
108                                                codeButtonHandlerData._mSIDI_TrafficDirection,
109                                                codeButtonHandlerData._mSIDI_LeftRightTrafficSignals,
110                                                codeButtonHandlerData._mSIDI_RightLeftTrafficSignals,
111                                                fleeting)
112                : new SignalDirectionIndicatorsNull();
113            _mSIDIHashMap.put(codeButtonHandlerData._mUniqueID, signalDirectionIndicators);
114
115// Slave Switch: null
116            SignalDirectionLever signalDirectionLever = (codeButtonHandlerData._mSIDL_Enabled && !slavedSwitch) ?
117                new SignalDirectionLever(   userIdentifier,
118                                            codeButtonHandlerData._mSIDL_LeftInternalSensor,
119                                            codeButtonHandlerData._mSIDL_NormalInternalSensor,
120                                            codeButtonHandlerData._mSIDL_RightInternalSensor)
121                : null;
122
123// Slave Switch: Valid
124            SwitchDirectionIndicators switchDirectionIndicators = codeButtonHandlerData._mSWDI_Enabled ?
125                new SwitchDirectionIndicators(  userIdentifier,
126                                                codeButtonHandlerData._mSWDI_NormalInternalSensor,
127                                                codeButtonHandlerData._mSWDI_ReversedInternalSensor,
128                                                codeButtonHandlerData._mSWDI_ExternalTurnout,
129                                                codeButtonHandlerData._mSWDI_CodingTimeInMilliseconds,
130                                                codeButtonHandlerData._mSWDI_FeedbackDifferent)
131                : null;
132            if (switchDirectionIndicators != null) _mSWDIHashMap.put(codeButtonHandlerData._mUniqueID, switchDirectionIndicators);
133
134// Slave Switch: Valid
135            SwitchDirectionLever switchDirectionLever = codeButtonHandlerData._mSWDL_Enabled ?
136                new SwitchDirectionLever(   userIdentifier,
137                                            codeButtonHandlerData._mSWDL_InternalSensor)
138                : null;
139
140// Slave Switch: null
141            CallOn callOn = (codeButtonHandlerData._mCO_Enabled && !slavedSwitch) ?
142                new CallOn( _mLockedRoutesManager,
143                            userIdentifier,
144                            codeButtonHandlerData._mCO_CallOnToggleInternalSensor,
145                            codeButtonHandlerData._mCO_GroupingsList,
146                            otherData._mSignalSystemType)
147                : null;
148
149// Slave Switch: Valid
150            TurnoutLock turnoutLock = codeButtonHandlerData._mTUL_Enabled ?
151                new TurnoutLock(userIdentifier,
152                                codeButtonHandlerData._mTUL_DispatcherInternalSensorLockToggle,
153                                codeButtonHandlerData._mTUL_ExternalTurnout,
154                                codeButtonHandlerData._mTUL_ExternalTurnoutFeedbackDifferent,
155                                codeButtonHandlerData._mTUL_DispatcherInternalSensorUnlockedIndicator,
156                                codeButtonHandlerData._mTUL_NoDispatcherControlOfSwitch,
157                                codeButtonHandlerData._mTUL_ndcos_WhenLockedSwitchStateIsClosed ? Turnout.CLOSED : Turnout.THROWN,
158                                codeButtonHandlerData._mTUL_LockImplementation,
159                                otherData._mTUL_EnabledAtStartup,
160                                codeButtonHandlerData._mTUL_AdditionalExternalTurnout1,
161                                codeButtonHandlerData._mTUL_AdditionalExternalTurnout1FeedbackDifferent,
162                                codeButtonHandlerData._mTUL_AdditionalExternalTurnout2,
163                                codeButtonHandlerData._mTUL_AdditionalExternalTurnout2FeedbackDifferent,
164                                codeButtonHandlerData._mTUL_AdditionalExternalTurnout3,
165                                codeButtonHandlerData._mTUL_AdditionalExternalTurnout3FeedbackDifferent)
166                : null;
167
168// Slave Switch: duplicate other referenced entry, otherwise handle IL normally:
169            IndicationLockingSignals indicationLockingSignals = null;   // Default if not enabled
170            if (slavedSwitch) {
171                CodeButtonHandlerData slavedSwitchCodeButtonHandlerData = _mCTCSerialData.getCodeButtonHandlerDataViaUniqueID(codeButtonHandlerData._mOSSectionSwitchSlavedToUniqueID);
172                if (slavedSwitchCodeButtonHandlerData != null)  { // Safety check
173                    indicationLockingSignals = new IndicationLockingSignals(userIdentifier,
174                                                                            slavedSwitchCodeButtonHandlerData._mIL_Signals,
175                                                                            codeButtonHandlerData._mSWDI_ExternalTurnout,
176                                                                            otherData._mSignalSystemType);
177                }
178            } else if (codeButtonHandlerData._mIL_Enabled) {
179                indicationLockingSignals = new IndicationLockingSignals(userIdentifier,
180                                                                        codeButtonHandlerData._mIL_Signals,
181                                                                        codeButtonHandlerData._mSWDI_ExternalTurnout,
182                                                                        otherData._mSignalSystemType);
183            }
184
185// Slave Switch: null
186            TrafficLocking trafficLocking = (codeButtonHandlerData._mTRL_Enabled && !slavedSwitch) ?
187                new TrafficLocking( userIdentifier,
188                                    codeButtonHandlerData._mTRL_LeftTrafficLockingRules,
189                                    codeButtonHandlerData._mTRL_RightTrafficLockingRules,
190                                    _mLockedRoutesManager)
191                    : null;
192            if (trafficLocking != null) trafficLockingFileReadComplete.add(trafficLocking);
193
194            CodeButtonHandler codeButtonHandler = new CodeButtonHandler(turnoutLockingOnlyEnabled,
195                                                                        _mLockedRoutesManager,
196                                                                        userIdentifier,
197                                                                        codeButtonHandlerData._mUniqueID,
198                                                                        codeButtonHandlerData._mCodeButtonInternalSensor,
199                                                                        codeButtonHandlerData._mCodeButtonDelayTime,
200                                                                        codeButtonHandlerData._mOSSectionOccupiedExternalSensor,
201                                                                        codeButtonHandlerData._mOSSectionOccupiedExternalSensor2,
202                                                                        signalDirectionIndicators,
203                                                                        signalDirectionLever,
204                                                                        switchDirectionIndicators,
205                                                                        switchDirectionLever,
206                                                                        fleeting,
207                                                                        callOn,
208                                                                        trafficLocking,
209                                                                        turnoutLock,
210                                                                        indicationLockingSignals);
211            _mCodeButtonHandlersArrayList.add(codeButtonHandler);
212            _mCBHashMap.put(codeButtonHandlerData._mUniqueID, codeButtonHandler);
213        });
214
215        _mCTCDebugSystemReloadInternalSensor = otherData._mCTCDebugSystemReloadInternalSensor;
216        _mCTCDebugSystemReloadInternalSensor.setKnownState(Sensor.INACTIVE);
217        _mCTCDebugSystemReloadInternalSensor.addPropertyChangeListener(_mCTCDebugSystemReloadInternalSensorPropertyChangeListener);
218        _mCTCDebug_TrafficLockingRuleTriggeredDisplayInternalSensor = otherData._mCTCDebug_TrafficLockingRuleTriggeredDisplayInternalSensor;
219        _mCTCDebug_TrafficLockingRuleTriggeredDisplayInternalSensor.setKnownState(Sensor.INACTIVE);
220        _mCTCDebug_TrafficLockingRuleTriggeredDisplayInternalSensor.addPropertyChangeListener(_mCTCDebug_TrafficLockingRuleTriggeredDisplayInternalSensorPropertyChangeListener);
221
222        for (TrafficLocking trafficLocking : trafficLockingFileReadComplete) { // Call these routines to give them a chance to initialize:
223            trafficLocking.fileReadComplete(_mCBHashMap, _mSWDIHashMap);
224        }
225
226/*  As a final item, if the developer wants us to lock all of the lockable
227    turnouts after a time period, create a GUI timer to do that, so that
228    when we call the objects, they are called on the GUI thread for safety.
229    In the called routines, sensors will be updated, but I don't know how
230    thread safe they are, or whether they will directly update GUI objects,
231    since GUI objects "visually back" the sensors.
232*/
233        if (otherData._mTUL_SecondsToLockTurnouts > 0) { // Enabled:
234            _mLockTurnoutsTimer = new javax.swing.Timer(otherData._mTUL_SecondsToLockTurnouts * 1000, lockTurnoutsTimerTicked);
235            _mLockTurnoutsTimer.setRepeats(false);
236            _mLockTurnoutsTimer.start();
237        }
238
239//  Finally, display errors to the user:
240        if (!_mCTCExceptionBuffer.isEmpty()) {
241            CTCExceptionBuffer.ExceptionBufferRecordSeverity exceptionBufferRecordSeverity2 = _mCTCExceptionBuffer.getHighestExceptionBufferRecordSeverity();
242            int messageType;
243            switch (exceptionBufferRecordSeverity2) {
244                case ERROR:
245                    messageType = JmriJOptionPane.ERROR_MESSAGE;
246                    break;
247                case WARN:
248                    messageType = JmriJOptionPane.WARNING_MESSAGE;
249                    break;
250                default:    // And INFO
251                    messageType = JmriJOptionPane.INFORMATION_MESSAGE;
252                    break;
253            }
254            JmriJOptionPane.showMessageDialog(null, _mCTCExceptionBuffer.getAllMessages(), Bundle.getMessage("CTCMainRuntimeStartupIssues"), messageType);  // NOI18N
255        }
256        log.info("CTC {} {}", OtherData.CTC_VERSION, Bundle.getMessage("CTCMainStarted"));   // NOI18N
257    }
258
259//  One shot routine:
260    private final java.awt.event.ActionListener lockTurnoutsTimerTicked = new java.awt.event.ActionListener() {
261        @Override
262        public void actionPerformed(java.awt.event.ActionEvent evt) {
263//  Shut down this timer so this doesn't happen again:
264            _mLockTurnoutsTimer.stop();
265            _mLockTurnoutsTimer.removeActionListener(lockTurnoutsTimerTicked);
266            _mLockTurnoutsTimer = null;
267            externalLockTurnout();
268        }
269    };
270
271    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CTCMain.class);
272
273}