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