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