001package jmri.jmrit.logixng.actions;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.util.*;
006
007import javax.annotation.Nonnull;
008
009import jmri.*;
010import jmri.jmrit.logix.Warrant;
011import jmri.jmrit.logix.WarrantManager;
012import jmri.jmrit.logixng.*;
013import jmri.jmrit.logixng.util.*;
014import jmri.jmrit.logixng.util.ReferenceUtil;
015import jmri.jmrit.logixng.util.parser.*;
016import jmri.jmrit.logixng.util.parser.ExpressionNode;
017import jmri.jmrit.logixng.util.parser.RecursiveDescentParser;
018import jmri.util.ThreadingUtil;
019import jmri.util.TypeConversionUtil;
020
021/**
022 * This action triggers a warrant.
023 *
024 * @author Daniel Bergqvist Copyright 2021
025 * @author Dave Sand Copyright 2021
026 * @author Pete Cressman Copyright (C) 2022
027 */
028public class ActionWarrant extends AbstractDigitalAction
029        implements PropertyChangeListener {
030
031    private final LogixNG_SelectNamedBean<Warrant> _selectNamedBean =
032            new LogixNG_SelectNamedBean<>(
033                    this, Warrant.class, InstanceManager.getDefault(WarrantManager.class), this);
034
035    private final LogixNG_SelectEnum<DirectOperation> _selectEnum =
036            new LogixNG_SelectEnum<>(this, DirectOperation.values(), DirectOperation.AllocateWarrantRoute, this);
037
038    private final LogixNG_SelectNamedBean<Memory> _selectMemoryNamedBean =
039            new LogixNG_SelectNamedBean<>(
040                    this, Memory.class, InstanceManager.getDefault(MemoryManager.class), this);
041
042    private NamedBeanAddressing _dataAddressing = NamedBeanAddressing.Direct;
043    private String _dataReference = "";
044    private String _dataLocalVariable = "";
045    private String _dataFormula = "";
046    private ExpressionNode _dataExpressionNode;
047
048    private String _trainData = "";
049    private ControlAutoTrain _controlAutoTrain = ControlAutoTrain.Halt;
050
051    public ActionWarrant(String sys, String user)
052            throws BadUserNameException, BadSystemNameException {
053        super(sys, user);
054    }
055
056    @Override
057    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException {
058        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
059        String sysName = systemNames.get(getSystemName());
060        String userName = userNames.get(getSystemName());
061        if (sysName == null) sysName = manager.getAutoSystemName();
062        ActionWarrant copy = new ActionWarrant(sysName, userName);
063        copy.setComment(getComment());
064        _selectNamedBean.copy(copy._selectNamedBean);
065        _selectMemoryNamedBean.copy(copy._selectMemoryNamedBean);
066        _selectEnum.copy(copy._selectEnum);
067
068        copy.setDataAddressing(_dataAddressing);
069        copy.setDataReference(_dataReference);
070        copy.setDataLocalVariable(_dataLocalVariable);
071        copy.setDataFormula(_dataFormula);
072
073        copy.setTrainData(_trainData);
074        copy.setControlAutoTrain(_controlAutoTrain);
075
076        return manager.registerAction(copy);
077    }
078
079    public LogixNG_SelectNamedBean<Warrant> getSelectNamedBean() {
080        return _selectNamedBean;
081    }
082
083    public LogixNG_SelectNamedBean<Memory> getSelectMemoryNamedBean() {
084        return _selectMemoryNamedBean;
085    }
086
087    public LogixNG_SelectEnum<DirectOperation> getSelectEnum() {
088        return _selectEnum;
089    }
090
091    public void setDataAddressing(NamedBeanAddressing addressing) throws ParserException {
092        _dataAddressing = addressing;
093        parseDataFormula();
094    }
095
096    public NamedBeanAddressing getDataAddressing() {
097        return _dataAddressing;
098    }
099
100    public void setDataReference(@Nonnull String reference) {
101        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
102            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
103        }
104        _dataReference = reference;
105    }
106
107    public String getDataReference() {
108        return _dataReference;
109    }
110
111    public void setDataLocalVariable(@Nonnull String localVariable) {
112        _dataLocalVariable = localVariable;
113    }
114
115    public String getDataLocalVariable() {
116        return _dataLocalVariable;
117    }
118
119    public void setDataFormula(@Nonnull String formula) throws ParserException {
120        _dataFormula = formula;
121        parseDataFormula();
122    }
123
124    public String getDataFormula() {
125        return _dataFormula;
126    }
127
128    private void parseDataFormula() throws ParserException {
129        if (_dataAddressing == NamedBeanAddressing.Formula) {
130            Map<String, Variable> variables = new HashMap<>();
131
132            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
133            _dataExpressionNode = parser.parseExpression(_dataFormula);
134        } else {
135            _dataExpressionNode = null;
136        }
137    }
138
139    public void setTrainData(@Nonnull String trainData) {
140        _trainData = trainData;
141    }
142
143    public String getTrainData() {
144        return _trainData;
145    }
146
147    public void setControlAutoTrain(ControlAutoTrain controlAutoTrain) {
148        _controlAutoTrain = controlAutoTrain;
149    }
150
151    public ControlAutoTrain getControlAutoTrain() {
152        return _controlAutoTrain;
153    }
154
155    /** {@inheritDoc} */
156    @Override
157    public Category getCategory() {
158        return Category.ITEM;
159    }
160
161
162    private String getNewData(DirectOperation theOper, SymbolTable symbolTable)
163            throws JmriException {
164
165        switch (_dataAddressing) {
166            case Direct:
167                switch(theOper) {
168                    case SetTrainId:
169                    case SetTrainName:
170                        return _trainData;
171                    case ControlAutoTrain:
172                        return _controlAutoTrain.name();
173                    default:
174                        return "";
175                }
176
177            case Reference:
178                return ReferenceUtil.getReference(symbolTable, _dataReference);
179
180            case LocalVariable:
181                return TypeConversionUtil
182                        .convertToString(symbolTable.getValue(_dataLocalVariable), false);
183
184            case Formula:
185                return _dataExpressionNode != null
186                        ? TypeConversionUtil.convertToString(
187                                _dataExpressionNode.calculate(symbolTable), false)
188                        : null;
189
190            default:
191                throw new IllegalArgumentException("invalid _dataAddressing state: " + _dataAddressing.name());
192        }
193    }
194
195
196    /** {@inheritDoc} */
197    @Override
198    public void execute() throws JmriException {
199        final ConditionalNG conditionalNG = getConditionalNG();
200        Warrant warrant = _selectNamedBean.evaluateNamedBean(conditionalNG);
201
202        if (warrant == null) {
203            return;
204        }
205
206        SymbolTable symbolTable = conditionalNG.getSymbolTable();
207
208        // Variables used in lambda must be effectively final
209        DirectOperation theOper = _selectEnum.evaluateEnum(conditionalNG);
210
211        if (!theOper.equals(DirectOperation.GetTrainLocation)) {
212            if (warrant.getRunMode() == Warrant.MODE_RUN && !theOper.equals(DirectOperation.ControlAutoTrain)) {
213                throw new JmriException("Cannot \"" + theOper.toString() + "\" when warrant is running - " + warrant.getDisplayName());  // NOI18N
214//                log.info("Cannot \"{}\" when warrant is running - {}", theOper.toString(), warrant.getDisplayName());
215//                return;
216            }
217        }
218
219        ThreadingUtil.runOnLayoutWithJmriException(() -> {
220            String msg;
221            String err;
222
223            switch (theOper) {
224                case AllocateWarrantRoute:
225                    warrant.allocateRoute(false, null);
226                    break;
227
228                case DeallocateWarrant:
229                    warrant.deAllocate();
230                    break;
231
232                case SetRouteTurnouts:
233                    msg = warrant.setRoute(false, null);
234                    if (msg != null) {
235                        log.warn("Warrant {} unable to Set Route - {}", warrant.getDisplayName(), msg);  // NOI18N
236                    }
237                    break;
238
239                case AutoRunTrain:
240                    jmri.jmrit.logix.WarrantTableFrame frame = jmri.jmrit.logix.WarrantTableFrame.getDefault();
241                    err = frame.runTrain(warrant, Warrant.MODE_RUN);
242                    if (err != null) {
243                        warrant.stopWarrant(true, true);
244                        throw new JmriException("runAutoTrain error - " + err);  // NOI18N
245                    }
246                    break;
247
248                case ManuallyRunTrain:
249                        err = warrant.setRoute(false, null);
250                        if (err == null) {
251                            err = warrant.setRunMode(Warrant.MODE_MANUAL, null, null, null, false);
252                        }
253                        if (err != null) {
254                            throw new JmriException("runManualTrain error - " + err);  // NOI18N
255                        }
256                    break;
257
258                case ControlAutoTrain:
259                    int controlAction = 0;
260                    switch (_controlAutoTrain) {
261                        case Halt:
262                            controlAction = Warrant.HALT;
263                            break;
264                        case Resume:
265                            controlAction = Warrant.RESUME;
266                            break;
267                        case Stop:
268                            controlAction = Warrant.STOP;
269                            break;
270                        case EStop:
271                            controlAction = Warrant.ESTOP;
272                            break;
273                        case SpeedUp:
274                            controlAction = Warrant.SPEED_UP;
275                            break;
276                        case MoveToNext:
277                            controlAction = Warrant.RETRY_FWD;
278                            break;
279                        case Abort:
280                            controlAction = Warrant.ABORT;
281                            break;
282                        default:
283                            throw new IllegalArgumentException("invalid train control action: " + _controlAutoTrain);
284                    }
285                    if (!warrant.controlRunTrain(controlAction)) {
286                        log.info("Warrant {} {}({}) failed. - {}", warrant.getDisplayName(),
287                                theOper.toString(), _controlAutoTrain.toString(), warrant.getMessage());
288                        throw new JmriException("Warrant " + warrant.getDisplayName() + " "
289                              + theOper.toString() +"(" + _controlAutoTrain.toString() + ") failed. "
290                              + warrant.getMessage());
291                    }
292                    break;
293
294                case SetTrainId:
295                    if(!warrant.getSpeedUtil().setAddress(getNewData(theOper, symbolTable))) {
296                        throw new JmriException("invalid train ID in action - " + warrant.getDisplayName());  // NOI18N
297                    }
298                    break;
299
300                case SetTrainName:
301                    warrant.setTrainName(getNewData(theOper, symbolTable));
302                    break;
303
304                case GetTrainLocation:
305                    Memory memory = _selectMemoryNamedBean.evaluateNamedBean(conditionalNG);
306                    if (memory != null) {
307                        memory.setValue(warrant.getCurrentBlockName());
308                    } else {
309                        throw new JmriException("Memory for GetTrainLocation is null for warrant - " + warrant.getDisplayName());  // NOI18N
310                    }
311                    break;
312
313                default:
314                    throw new IllegalArgumentException("invalid oper state: " + theOper.name());
315            }
316        });
317    }
318
319    @Override
320    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
321        throw new UnsupportedOperationException("Not supported.");
322    }
323
324    @Override
325    public int getChildCount() {
326        return 0;
327    }
328
329    @Override
330    public String getShortDescription(Locale locale) {
331        return Bundle.getMessage(locale, "ActionWarrant_Short");
332    }
333
334    @Override
335    public String getLongDescription(Locale locale) {
336        String namedBean = _selectNamedBean.getDescription(locale);
337        String state = _selectEnum.getDescription(locale);
338        String getLocationMemory = _selectMemoryNamedBean.getDescription(locale);
339
340        if (_selectEnum.getAddressing() == NamedBeanAddressing.Direct) {
341            if (_selectEnum.getEnum() != null) {
342                switch (_selectEnum.getEnum()) {
343                    case SetTrainId:
344                        return getLongDataDescription(locale, "ActionWarrant_Long_Train_Id", namedBean, _trainData);
345                    case SetTrainName:
346                        return getLongDataDescription(locale, "ActionWarrant_Long_Train_Name", namedBean, _trainData);
347                    case ControlAutoTrain:
348                        return getLongDataDescription(locale, "ActionWarrant_Long_Control", namedBean, _controlAutoTrain.name());
349                    case GetTrainLocation:
350                        return getLongDataDescription(locale, "ActionWarrant_Long_Location", namedBean, getLocationMemory);
351                    default:
352                        // Fall thru and handle it in the end of the method
353                }
354            }
355        }
356
357        return Bundle.getMessage(locale, "ActionWarrant_Long", namedBean, state);
358    }
359
360    private String getLongDataDescription(Locale locale, String bundleKey, String namedBean, String value) {
361        switch (_dataAddressing) {
362            case Direct:
363                return Bundle.getMessage(locale, bundleKey, namedBean, value);
364            case Reference:
365                return Bundle.getMessage(locale, bundleKey, namedBean, Bundle.getMessage("AddressByReference", _dataReference));
366            case LocalVariable:
367                return Bundle.getMessage(locale, bundleKey, namedBean, Bundle.getMessage("AddressByLocalVariable", _dataLocalVariable));
368            case Formula:
369                return Bundle.getMessage(locale, bundleKey, namedBean, Bundle.getMessage("AddressByFormula", _dataFormula));
370            default:
371                throw new IllegalArgumentException("invalid _dataAddressing state: " + _dataAddressing.name());
372        }
373    }
374
375    /** {@inheritDoc} */
376    @Override
377    public void setup() {
378        // Do nothing
379    }
380
381    /** {@inheritDoc} */
382    @Override
383    public void registerListenersForThisClass() {
384        _selectNamedBean.registerListeners();
385        _selectEnum.registerListeners();
386        _selectMemoryNamedBean.addPropertyChangeListener("value", this);
387    }
388
389    /** {@inheritDoc} */
390    @Override
391    public void unregisterListenersForThisClass() {
392        _selectNamedBean.unregisterListeners();
393        _selectEnum.unregisterListeners();
394        _selectMemoryNamedBean.removePropertyChangeListener("value", this);
395    }
396
397    /** {@inheritDoc} */
398    @Override
399    public void disposeMe() {
400    }
401
402    public enum DirectOperation {
403        None(""),
404        AllocateWarrantRoute(Bundle.getMessage("ActionWarrant_AllocateWarrantRoute")),
405        DeallocateWarrant(Bundle.getMessage("ActionWarrant_DeallocateWarrant")),
406        SetRouteTurnouts(Bundle.getMessage("ActionWarrant_SetRouteTurnouts")),
407        AutoRunTrain(Bundle.getMessage("ActionWarrant_AutoRunTrain")),
408        ManuallyRunTrain(Bundle.getMessage("ActionWarrant_ManuallyRunTrain")),
409        ControlAutoTrain(Bundle.getMessage("ActionWarrant_ControlAutoTrain")),
410        SetTrainId(Bundle.getMessage("ActionWarrant_SetTrainId")),
411        SetTrainName(Bundle.getMessage("ActionWarrant_SetTrainName")),
412        GetTrainLocation(Bundle.getMessage("ActionWarrant_GetTrainLocation"));
413
414        private final String _text;
415
416        private DirectOperation(String text) {
417            this._text = text;
418        }
419
420        @Override
421        public String toString() {
422            return _text;
423        }
424
425    }
426
427    public enum ControlAutoTrain {
428        Halt(Bundle.getMessage("ActionWarrant_Halt_AutoTrain")),
429        Resume(Bundle.getMessage("ActionWarrant_Resume_AutoTrain")),
430        Abort(Bundle.getMessage("ActionWarrant_Abort_AutoTrain")),
431        Stop(Bundle.getMessage("ActionWarrant_Stop_AutoTrain")),
432        EStop(Bundle.getMessage("ActionWarrant_EStop_AutoTrain")),
433        MoveToNext(Bundle.getMessage("ActionWarrant_MoveToNext_AutoTrain")),
434        SpeedUp(Bundle.getMessage("ActionWarrant_SpeedUp_AutoTrain"));
435
436        private final String _text;
437
438        private ControlAutoTrain(String text) {
439            this._text = text;
440        }
441
442        @Override
443        public String toString() {
444            return _text;
445        }
446
447    }
448
449    /** {@inheritDoc} */
450    @Override
451    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
452        _selectNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Action);
453        _selectMemoryNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Action);
454    }
455
456    /** {@inheritDoc} */
457    @Override
458    public void propertyChange(PropertyChangeEvent evt) {
459        getConditionalNG().execute();
460    }
461
462    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionWarrant.class);
463
464}