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.dispatcher.*;
011import jmri.jmrit.logixng.*;
012import jmri.jmrit.logixng.util.*;
013import jmri.jmrit.logixng.util.parser.*;
014import jmri.jmrit.logixng.util.parser.ExpressionNode;
015import jmri.jmrit.logixng.util.parser.RecursiveDescentParser;
016import jmri.util.TypeConversionUtil;
017
018/**
019 * This action triggers a Dispather ActiveTrain.
020 *
021 * @author Daniel Bergqvist Copyright 2021
022 * @author Dave Sand Copyright 2021
023 */
024public class ActionDispatcher extends AbstractDigitalAction
025        implements PropertyChangeListener {
026
027    private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct;
028    private String _trainInfoFileName = "";
029    private String _reference = "";
030    private String _localVariable = "";
031    private String _formula = "";
032    private ExpressionNode _expressionNode;
033
034    private final LogixNG_SelectEnum<DirectOperation> _selectEnum =
035            new LogixNG_SelectEnum<>(this, DirectOperation.values(), DirectOperation.None, this);
036
037    private NamedBeanAddressing _dataAddressing = NamedBeanAddressing.Direct;
038    private String _dataReference = "";
039    private String _dataLocalVariable = "";
040    private String _dataFormula = "";
041    private ExpressionNode _dataExpressionNode;
042
043    private boolean _resetOption = false;
044    private boolean _terminateOption = false;
045    private int _trainPriority = 5;
046
047    private final DispatcherActiveTrainManager _atManager;
048
049
050    public ActionDispatcher(String sys, String user)
051            throws BadUserNameException, BadSystemNameException {
052        super(sys, user);
053        _atManager = InstanceManager.getDefault(DispatcherActiveTrainManager.class);
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        ActionDispatcher copy = new ActionDispatcher(sysName, userName);
063        copy.setComment(getComment());
064
065        copy.setAddressing(_addressing);
066        copy.setTrainInfoFileName(_trainInfoFileName);
067        copy.setReference(_reference);
068        copy.setLocalVariable(_localVariable);
069        copy.setFormula(_formula);
070
071        _selectEnum.copy(copy._selectEnum);
072
073        copy.setDataAddressing(_dataAddressing);
074        copy.setDataReference(_dataReference);
075        copy.setDataLocalVariable(_dataLocalVariable);
076        copy.setDataFormula(_dataFormula);
077
078        copy.setResetOption(_resetOption);
079        copy.setTerminateOption(_terminateOption);
080        copy.setTrainPriority(_trainPriority);
081
082        return manager.registerAction(copy);
083    }
084
085    public void setTrainInfoFileName(@Nonnull String fileName) {
086        _trainInfoFileName = fileName;
087    }
088
089    public String getTrainInfoFileName() {
090        return _trainInfoFileName;
091    }
092
093
094    public void setAddressing(NamedBeanAddressing addressing) throws ParserException {
095        _addressing = addressing;
096        parseFormula();
097    }
098
099    public NamedBeanAddressing getAddressing() {
100        return _addressing;
101    }
102
103    public void setReference(@Nonnull String reference) {
104        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
105            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
106        }
107        _reference = reference;
108    }
109
110    public String getReference() {
111        return _reference;
112    }
113
114    public void setLocalVariable(@Nonnull String localVariable) {
115        _localVariable = localVariable;
116    }
117
118    public String getLocalVariable() {
119        return _localVariable;
120    }
121
122    public void setFormula(@Nonnull String formula) throws ParserException {
123        _formula = formula;
124        parseFormula();
125    }
126
127    public String getFormula() {
128        return _formula;
129    }
130
131    private void parseFormula() throws ParserException {
132        if (_addressing == NamedBeanAddressing.Formula) {
133            Map<String, Variable> variables = new HashMap<>();
134
135            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
136            _expressionNode = parser.parseExpression(_formula);
137        } else {
138            _expressionNode = null;
139        }
140    }
141
142
143    public LogixNG_SelectEnum<DirectOperation> getSelectEnum() {
144        return _selectEnum;
145    }
146
147
148    public void setDataAddressing(NamedBeanAddressing addressing) throws ParserException {
149        _dataAddressing = addressing;
150        parseDataFormula();
151    }
152
153    public NamedBeanAddressing getDataAddressing() {
154        return _dataAddressing;
155    }
156
157    public void setDataReference(@Nonnull String reference) {
158        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
159            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
160        }
161        _dataReference = reference;
162    }
163
164    public String getDataReference() {
165        return _dataReference;
166    }
167
168    public void setDataLocalVariable(@Nonnull String localVariable) {
169        _dataLocalVariable = localVariable;
170    }
171
172    public String getDataLocalVariable() {
173        return _dataLocalVariable;
174    }
175
176    public void setDataFormula(@Nonnull String formula) throws ParserException {
177        _dataFormula = formula;
178        parseDataFormula();
179    }
180
181    public String getDataFormula() {
182        return _dataFormula;
183    }
184
185    private void parseDataFormula() throws ParserException {
186        if (_dataAddressing == NamedBeanAddressing.Formula) {
187            Map<String, Variable> variables = new HashMap<>();
188
189            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
190            _dataExpressionNode = parser.parseExpression(_dataFormula);
191        } else {
192            _dataExpressionNode = null;
193        }
194    }
195
196
197    public void setTrainPriority(int trainPriority) {
198        _trainPriority = trainPriority;
199    }
200
201    public int getTrainPriority() {
202        return _trainPriority;
203    }
204
205    public void setResetOption(boolean resetOption) {
206        _resetOption = resetOption;
207    }
208
209    public boolean getResetOption() {
210        return _resetOption;
211    }
212
213    public void setTerminateOption(boolean terminateOption) {
214        _terminateOption = terminateOption;
215    }
216
217    public boolean getTerminateOption() {
218        return _terminateOption;
219    }
220
221    /** {@inheritDoc} */
222    @Override
223    public Category getCategory() {
224        return Category.ITEM;
225    }
226
227    private String getNewData(DirectOperation oper) throws JmriException {
228        switch (_dataAddressing) {
229            case Direct:
230                switch(oper) {
231                    case TrainPriority:
232                        return String.valueOf(getTrainPriority());
233
234                    case ResetWhenDoneOption:
235                        return getResetOption() ? "true" : "false";
236
237                    case TerminateWhenDoneOption:
238                        return getTerminateOption() ? "true" : "false";
239
240                    default:
241                        return "";
242                }
243
244            case Reference:
245                return ReferenceUtil.getReference(
246                        getConditionalNG().getSymbolTable(), _dataReference);
247
248            case LocalVariable:
249                SymbolTable symbolTable = getConditionalNG().getSymbolTable();
250                return TypeConversionUtil
251                        .convertToString(symbolTable.getValue(_dataLocalVariable), false);
252
253            case Formula:
254                return _dataExpressionNode != null
255                        ? TypeConversionUtil.convertToString(
256                                _dataExpressionNode.calculate(
257                                        getConditionalNG().getSymbolTable()), false)
258                        : "";
259
260            default:
261                throw new IllegalArgumentException("invalid _dataAddressing state: " + _dataAddressing.name());
262        }
263    }
264
265
266    /** {@inheritDoc} */
267    @Override
268    public void execute() throws JmriException {
269        String trainInfoFileName = "";
270
271        switch (_addressing) {
272            case Direct:
273                trainInfoFileName = _trainInfoFileName;
274                break;
275
276            case Reference:
277                trainInfoFileName = ReferenceUtil.getReference(
278                        getConditionalNG().getSymbolTable(), _reference);
279                break;
280
281            case LocalVariable:
282                SymbolTable symbolTable = getConditionalNG().getSymbolTable();
283                trainInfoFileName = TypeConversionUtil
284                                .convertToString(symbolTable.getValue(_localVariable), false);
285                break;
286
287            case Formula:
288                trainInfoFileName = _expressionNode != null ?
289                        TypeConversionUtil.convertToString(_expressionNode.calculate(
290                                getConditionalNG().getSymbolTable()), false)
291                        : "";
292                break;
293
294            default:
295                throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name());
296        }
297
298        if (trainInfoFileName.isEmpty()) {
299            return;
300        }
301
302        ActiveTrain activeTrain = _atManager.getActiveTrain(trainInfoFileName);
303
304        DirectOperation oper = _selectEnum.evaluateEnum(getConditionalNG());
305
306        String newData = getNewData(oper);
307
308        switch (oper) {
309            case LoadTrainFromFile:
310                if (activeTrain == null) {
311                    activeTrain = _atManager.createActiveTrain(trainInfoFileName);
312                    if (activeTrain == null) {
313                        log.warn("DispatcherAction: Unable to create an active train");
314                    }
315                } else {
316                    log.warn("DispatcherAction: The active train already exists");
317                }
318                return;
319
320            case TerminateTrain:
321                _atManager.terminateActiveTrain(trainInfoFileName);
322                return;
323
324            case TrainPriority:
325                if (activeTrain != null) {
326                    int newInt = Integer.parseInt(newData);
327                    if (newInt < 0) newInt = 0;
328                    if (newInt > 100) newInt = 100;
329                    activeTrain.setPriority(newInt);
330                }
331                return;
332
333            case ResetWhenDoneOption:
334                if (activeTrain != null) {
335                    if (newData.equals("true") || newData.equals("false")) {
336                        boolean reset = newData.equals("true");
337                        activeTrain.setResetWhenDone(reset);
338                    }
339                }
340                return;
341
342            case TerminateWhenDoneOption:
343                if (activeTrain != null) {
344                    if (newData.equals("true") || newData.equals("false")) {
345                        boolean term = newData.equals("true");
346                        activeTrain.setTerminateWhenDone(term);
347                    }
348                }
349                return;
350
351            default:
352                throw new IllegalArgumentException("invalid oper state: " + oper.name());
353        }
354    }
355
356    @Override
357    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
358        throw new UnsupportedOperationException("Not supported.");
359    }
360
361    @Override
362    public int getChildCount() {
363        return 0;
364    }
365
366    @Override
367    public String getShortDescription(Locale locale) {
368        return Bundle.getMessage(locale, "ActionDispatcher_Short");
369    }
370
371    @Override
372    public String getLongDescription(Locale locale) {
373
374// Start train using train info file {abc.xml}
375// Terminate train {transit/name}
376// Set priority for train {} to {nnn}
377// {[Enable|Disable]} "reset when done" for train {}}
378// {[Enable|Disable]} "terminate when done" for train using {}}
379
380        String fileName;
381        String state = _selectEnum.getDescription(locale);
382
383        switch (_addressing) {
384            case Direct:
385                fileName = Bundle.getMessage(locale, "AddressByDirect", _trainInfoFileName);
386                break;
387
388            case Reference:
389                fileName = Bundle.getMessage(locale, "AddressByReference", _reference);
390                break;
391
392            case LocalVariable:
393                fileName = Bundle.getMessage(locale, "AddressByLocalVariable", _localVariable);
394                break;
395
396            case Formula:
397                fileName = Bundle.getMessage(locale, "AddressByFormula", _formula);
398                break;
399
400            default:
401                throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name());
402        }
403
404        if (_selectEnum.getAddressing() == NamedBeanAddressing.Direct) {
405            switch (_selectEnum.getEnum()) {
406                case LoadTrainFromFile:
407                    return Bundle.getMessage("ActionDispatcher_Long_LoadTrain", fileName);
408
409                case TerminateTrain:
410                    return Bundle.getMessage("ActionDispatcher_Long_Terminate", fileName);
411
412                case TrainPriority:
413                    return getLongDataDescription(locale, "ActionDispatcher_Long_Priority",
414                            fileName, String.valueOf(getTrainPriority()));
415                case ResetWhenDoneOption:
416                    return getLongDataDescription(locale, "ActionDispatcher_Long_ResetOption",
417                            fileName, getResetOption() ? Bundle.getMessage("ActionDispatcher_Long_Enable") :
418                            Bundle.getMessage("ActionDispatcher_Long_Disable"));
419                case TerminateWhenDoneOption:
420                    return getLongDataDescription(locale, "ActionDispatcher_Long_TerminateOption",
421                            fileName, getTerminateOption() ? Bundle.getMessage("ActionDispatcher_Long_Enable") :
422                            Bundle.getMessage("ActionDispatcher_Long_Disable"));
423                default:
424                    throw new IllegalArgumentException("invalid enum: " + _selectEnum.getEnum().name());
425
426            }
427        }
428
429        return Bundle.getMessage(locale, "ActionDispatcher_Long", fileName, state);
430    }
431
432    private String getLongDataDescription(Locale locale, String bundleKey, String fileName, String value) {
433        switch (_dataAddressing) {
434            case Direct:
435                return Bundle.getMessage(locale, bundleKey, fileName, value);
436            case Reference:
437                return Bundle.getMessage(locale, bundleKey, fileName, Bundle.getMessage("AddressByReference", _dataReference));
438            case LocalVariable:
439                return Bundle.getMessage(locale, bundleKey, fileName, Bundle.getMessage("AddressByLocalVariable", _dataLocalVariable));
440            case Formula:
441                return Bundle.getMessage(locale, bundleKey, fileName, Bundle.getMessage("AddressByFormula", _dataFormula));
442            default:
443                throw new IllegalArgumentException("invalid _dataAddressing state: " + _dataAddressing.name());
444        }
445    }
446
447    /** {@inheritDoc} */
448    @Override
449    public void setup() {
450        // Do nothing
451    }
452
453    /** {@inheritDoc} */
454    @Override
455    public void registerListenersForThisClass() {
456        _selectEnum.registerListeners();
457    }
458
459    /** {@inheritDoc} */
460    @Override
461    public void unregisterListenersForThisClass() {
462        _selectEnum.unregisterListeners();
463    }
464
465    /** {@inheritDoc} */
466    @Override
467    public void disposeMe() {
468    }
469
470    public enum DirectOperation {
471        None(""),
472        LoadTrainFromFile(Bundle.getMessage("ActionDispatcher_LoadTrainFromFile")),
473        TerminateTrain(Bundle.getMessage("ActionDispatcher_TerminateTrain")),
474        TrainPriority(Bundle.getMessage("ActionDispatcher_TrainPriority")),
475        ResetWhenDoneOption(Bundle.getMessage("ActionDispatcher_ResetWhenDoneOption")),
476        TerminateWhenDoneOption(Bundle.getMessage("ActionDispatcher_TerminateWhenDoneOption"));
477
478        private final String _text;
479
480        private DirectOperation(String text) {
481            this._text = text;
482        }
483
484        @Override
485        public String toString() {
486            return _text;
487        }
488
489    }
490
491    /** {@inheritDoc} */
492    @Override
493    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
494    }
495
496    /** {@inheritDoc} */
497    @Override
498    public void propertyChange(PropertyChangeEvent evt) {
499        getConditionalNG().execute();
500    }
501
502    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionDispatcher.class);
503
504}