001package jmri.jmrit.display.logixng;
002
003import java.beans.*;
004import java.util.*;
005
006import javax.annotation.CheckForNull;
007import javax.annotation.Nonnull;
008
009import jmri.*;
010import jmri.jmrit.display.EditorManager;
011import static jmri.jmrit.display.EditorManager.EDITORS;
012import jmri.jmrit.display.layoutEditor.LayoutEditor;
013import jmri.jmrit.display.layoutEditor.LayoutTurnout;
014import jmri.jmrit.logixng.*;
015import jmri.jmrit.logixng.actions.AbstractDigitalAction;
016import jmri.jmrit.logixng.util.ReferenceUtil;
017import jmri.jmrit.logixng.util.parser.*;
018import jmri.jmrit.logixng.util.parser.ExpressionNode;
019import jmri.jmrit.logixng.util.parser.RecursiveDescentParser;
020import jmri.util.ThreadingUtil;
021import jmri.util.TypeConversionUtil;
022
023/**
024 * This action controls various things of a LayoutTurnout on a LayoutEditor panel.
025 *
026 * @author Daniel Bergqvist Copyright 2022
027 */
028public class ActionLayoutTurnout extends AbstractDigitalAction
029        implements PropertyChangeListener, VetoableChangeListener {
030
031    private String _layoutEditorName;
032    private LayoutEditor _layoutEditor;
033    private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct;
034    private String _layoutTurnoutName;
035    private LayoutTurnout _layoutTurnout;
036    private String _reference = "";
037    private String _localVariable = "";
038    private String _formula = "";
039    private ExpressionNode _expressionNode;
040    private NamedBeanAddressing _stateAddressing = NamedBeanAddressing.Direct;
041    private Operation _operation = Operation.Enable;
042    private String _stateReference = "";
043    private String _stateLocalVariable = "";
044    private String _stateFormula = "";
045    private ExpressionNode _stateExpressionNode;
046
047    public ActionLayoutTurnout(String sys, String user)
048            throws BadUserNameException, BadSystemNameException {
049        super(sys, user);
050    }
051
052    @Override
053    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException {
054        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
055        String sysName = systemNames.get(getSystemName());
056        String userName = userNames.get(getSystemName());
057        if (sysName == null) sysName = manager.getAutoSystemName();
058        ActionLayoutTurnout copy = new ActionLayoutTurnout(sysName, userName);
059        copy.setComment(getComment());
060        if (_layoutEditor != null) {
061            copy.setLayoutEditor(_layoutEditor.getName());
062        }
063        copy.setLayoutTurnout(_layoutTurnout);
064        copy.setOperation(_operation);
065        copy.setAddressing(_addressing);
066        copy.setFormula(_formula);
067        copy.setLocalVariable(_localVariable);
068        copy.setReference(_reference);
069        copy.setStateAddressing(_stateAddressing);
070        copy.setStateFormula(_stateFormula);
071        copy.setStateLocalVariable(_stateLocalVariable);
072        copy.setStateReference(_stateReference);
073        return manager.registerAction(copy);
074    }
075
076    public void setLayoutEditor(@CheckForNull String layoutEditorName) {
077        assertListenersAreNotRegistered(log, "setEditor");
078
079        InstanceManager.getDefault(EditorManager.class)
080                .removePropertyChangeListener(EDITORS, this);
081
082        _layoutEditorName = layoutEditorName;
083
084        if (layoutEditorName != null) {
085            _layoutEditor = InstanceManager.getDefault(EditorManager.class)
086                    .get(LayoutEditor.class, layoutEditorName);
087        } else {
088            _layoutEditor = null;
089        }
090        if (_layoutEditor != null) {
091            InstanceManager.getDefault(EditorManager.class)
092                    .addPropertyChangeListener(EDITORS, this);
093        } else {
094            _layoutTurnout = null;
095        }
096//        InstanceManager.turnoutManagerInstance().addVetoableChangeListener(this);
097    }
098
099    public String getLayoutEditorName() {
100        if (_layoutEditor != null) {
101            return _layoutEditor.getName();
102        } else {
103            return null;
104        }
105    }
106
107    public LayoutTurnout findLayoutTurnout(String name) {
108        if (_layoutEditor != null) {
109            for (LayoutTurnout lt : _layoutEditor.getLayoutTurnouts()) {
110                String turnoutName = lt.getTurnoutName();
111                if (!turnoutName.isBlank() && name.equals(turnoutName)) {
112                    return lt;
113                }
114            }
115        }
116        return null;
117    }
118
119    public LayoutTurnout findLayoutTurnout(jmri.Turnout turnout) {
120        if (_layoutEditor != null) {
121            for (LayoutTurnout lt : _layoutEditor.getLayoutTurnouts()) {
122                String turnoutName = lt.getTurnoutName();
123                if (!turnoutName.isBlank()
124                        && (turnoutName.equals(turnout.getSystemName())
125                            || turnoutName.equals(turnout.getUserName()))) {
126                    return lt;
127                }
128            }
129        }
130        return null;
131    }
132
133    public void setLayoutTurnout(@CheckForNull String layoutTurnoutName) {
134        assertListenersAreNotRegistered(log, "setLayoutTurnout");
135        _layoutTurnoutName = layoutTurnoutName;
136        if ((layoutTurnoutName != null) && (_layoutEditor != null)) {
137            this._layoutTurnout = findLayoutTurnout(layoutTurnoutName);
138        } else {
139            this._layoutTurnout = null;
140        }
141//        InstanceManager.turnoutManagerInstance().addVetoableChangeListener(this);
142    }
143
144    public void setLayoutTurnout(@CheckForNull LayoutTurnout layoutTurnout) {
145        assertListenersAreNotRegistered(log, "setLayoutTurnout");
146        if ((layoutTurnout != null) && (_layoutEditor != null)) {
147            this._layoutTurnout = layoutTurnout;
148        } else {
149            this._layoutTurnout = null;
150        }
151//        InstanceManager.turnoutManagerInstance().addVetoableChangeListener(this);
152    }
153
154    public LayoutTurnout getLayoutTurnout() {
155        return _layoutTurnout;
156    }
157
158    public void setAddressing(NamedBeanAddressing addressing) throws ParserException {
159        _addressing = addressing;
160        parseFormula();
161    }
162
163    public NamedBeanAddressing getAddressing() {
164        return _addressing;
165    }
166
167    public void setReference(@Nonnull String reference) {
168        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
169            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
170        }
171        _reference = reference;
172    }
173
174    public String getReference() {
175        return _reference;
176    }
177
178    public void setLocalVariable(@Nonnull String localVariable) {
179        _localVariable = localVariable;
180    }
181
182    public String getLocalVariable() {
183        return _localVariable;
184    }
185
186    public void setFormula(@Nonnull String formula) throws ParserException {
187        _formula = formula;
188        parseFormula();
189    }
190
191    public String getFormula() {
192        return _formula;
193    }
194
195    private void parseFormula() throws ParserException {
196        if (_addressing == NamedBeanAddressing.Formula) {
197            Map<String, Variable> variables = new HashMap<>();
198
199            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
200            _expressionNode = parser.parseExpression(_formula);
201        } else {
202            _expressionNode = null;
203        }
204    }
205
206    public void setStateAddressing(NamedBeanAddressing addressing) throws ParserException {
207        _stateAddressing = addressing;
208        parseStateFormula();
209    }
210
211    public NamedBeanAddressing getStateAddressing() {
212        return _stateAddressing;
213    }
214
215    public void setOperation(Operation isControlling) {
216        _operation = isControlling;
217    }
218
219    public Operation getOperation() {
220        return _operation;
221    }
222
223    public void setStateReference(@Nonnull String reference) {
224        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
225            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
226        }
227        _stateReference = reference;
228    }
229
230    public String getStateReference() {
231        return _stateReference;
232    }
233
234    public void setStateLocalVariable(@Nonnull String localVariable) {
235        _stateLocalVariable = localVariable;
236    }
237
238    public String getStateLocalVariable() {
239        return _stateLocalVariable;
240    }
241
242    public void setStateFormula(@Nonnull String formula) throws ParserException {
243        _stateFormula = formula;
244        parseStateFormula();
245    }
246
247    public String getStateFormula() {
248        return _stateFormula;
249    }
250
251    private void parseStateFormula() throws ParserException {
252        if (_stateAddressing == NamedBeanAddressing.Formula) {
253            Map<String, Variable> variables = new HashMap<>();
254
255            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
256            _stateExpressionNode = parser.parseExpression(_stateFormula);
257        } else {
258            _stateExpressionNode = null;
259        }
260    }
261
262    @Override
263    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
264/*
265        if ("CanDelete".equals(evt.getPropertyName())) { // No I18N
266            if (evt.getOldValue() instanceof Turnout) {
267                if (evt.getOldValue().equals(getTurnout().getBean())) {
268                    PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null);
269                    throw new PropertyVetoException(Bundle.getMessage("Turnout_TurnoutInUseTurnoutExpressionVeto", getDisplayName()), e); // NOI18N
270                }
271            }
272        } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N
273            if (evt.getOldValue() instanceof Turnout) {
274                if (evt.getOldValue().equals(getTurnout().getBean())) {
275                    removeTurnout();
276                }
277            }
278        }
279*/
280    }
281
282    /** {@inheritDoc} */
283    @Override
284    public Category getCategory() {
285        return CategoryDisplay.DISPLAY;
286    }
287
288    private String getNewState() throws JmriException {
289
290        switch (_stateAddressing) {
291            case Reference:
292                return ReferenceUtil.getReference(
293                        getConditionalNG().getSymbolTable(), _stateReference);
294
295            case LocalVariable:
296                SymbolTable symbolTable = getConditionalNG().getSymbolTable();
297                return TypeConversionUtil
298                        .convertToString(symbolTable.getValue(_stateLocalVariable), false);
299
300            case Formula:
301                return _stateExpressionNode != null
302                        ? TypeConversionUtil.convertToString(
303                                _stateExpressionNode.calculate(
304                                        getConditionalNG().getSymbolTable()), false)
305                        : null;
306
307            default:
308                throw new IllegalArgumentException("invalid _addressing state: " + _stateAddressing.name());
309        }
310    }
311
312    /** {@inheritDoc} */
313    @Override
314    public void execute() throws JmriException {
315        LayoutTurnout layoutTurnout;
316
317//        System.out.format("ActionLayoutTurnout.execute: %s%n", getLongDescription());
318
319        Object value;
320
321        switch (_addressing) {
322            case Direct:
323                layoutTurnout = this._layoutTurnout;
324                break;
325
326            case Reference:
327                String ref = ReferenceUtil.getReference(
328                        getConditionalNG().getSymbolTable(), _reference);
329                layoutTurnout = findLayoutTurnout(ref);
330                break;
331
332            case LocalVariable:
333                SymbolTable symbolTable = getConditionalNG().getSymbolTable();
334                value = symbolTable.getValue(_localVariable);
335                if (value instanceof jmri.Turnout) {
336                    layoutTurnout = findLayoutTurnout((jmri.Turnout)value);
337                } else {
338                    layoutTurnout = findLayoutTurnout(TypeConversionUtil
339                                    .convertToString(value, false));
340                }
341                break;
342
343            case Formula:
344                if (_expressionNode != null) {
345                    value = _expressionNode.calculate(getConditionalNG().getSymbolTable());
346                    if (value instanceof jmri.Turnout) {
347                        layoutTurnout = findLayoutTurnout((jmri.Turnout)value);
348                    } else {
349                        layoutTurnout = findLayoutTurnout(TypeConversionUtil
350                                        .convertToString(value, false));
351                    }
352                } else {
353                    layoutTurnout = null;
354                }
355                break;
356
357            default:
358                throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name());
359        }
360
361//        System.out.format("ActionLayoutTurnout.execute: layoutTurnout: %s%n", layoutTurnout);
362
363        if (layoutTurnout == null) {
364            log.debug("layoutTurnout is null");
365            return;
366        }
367
368        String name = (_stateAddressing != NamedBeanAddressing.Direct)
369                ? getNewState() : null;
370
371        Operation operation;
372        if ((_stateAddressing == NamedBeanAddressing.Direct)) {
373            operation = _operation;
374        } else {
375            operation = Operation.valueOf(name);
376        }
377
378        ThreadingUtil.runOnGUI(() -> {
379            switch (operation) {
380                case Disable:
381                    layoutTurnout.setDisabled(true);
382                    break;
383                case Enable:
384                    layoutTurnout.setDisabled(false);
385                    break;
386/*
387                case Hide:
388                    layoutTurnout.setHidden(true);
389                    break;
390                case Show:
391                    layoutTurnout.setHidden(false);
392                    break;
393*/
394                default:
395                    throw new RuntimeException("operation has invalid value: "+operation.name());
396            }
397        });
398    }
399
400    @Override
401    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
402        throw new UnsupportedOperationException("Not supported.");
403    }
404
405    @Override
406    public int getChildCount() {
407        return 0;
408    }
409
410    @Override
411    public String getShortDescription(Locale locale) {
412        return Bundle.getMessage(locale, "ActionLayoutTurnout_Short");
413    }
414
415    @Override
416    public String getLongDescription(Locale locale) {
417        String editorName = _layoutEditor != null
418                ? _layoutEditor.getName() : Bundle.getMessage(locale, "BeanNotSelected");
419        String positonableName;
420        String state;
421
422        switch (_addressing) {
423            case Direct:
424                String layoutTurnoutName;
425                if (this._layoutTurnout != null) {
426                    layoutTurnoutName = this._layoutTurnout.getTurnoutName();
427                } else {
428                    layoutTurnoutName = Bundle.getMessage(locale, "BeanNotSelected");
429                }
430                positonableName = Bundle.getMessage(locale, "AddressByDirect", layoutTurnoutName);
431                break;
432
433            case Reference:
434                positonableName = Bundle.getMessage(locale, "AddressByReference", _reference);
435                break;
436
437            case LocalVariable:
438                positonableName = Bundle.getMessage(locale, "AddressByLocalVariable", _localVariable);
439                break;
440
441            case Formula:
442                positonableName = Bundle.getMessage(locale, "AddressByFormula", _formula);
443                break;
444
445            default:
446                throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name());
447        }
448
449        switch (_stateAddressing) {
450            case Direct:
451                state = Bundle.getMessage(locale, "AddressByDirect", _operation._text);
452                break;
453
454            case Reference:
455                state = Bundle.getMessage(locale, "AddressByReference", _stateReference);
456                break;
457
458            case LocalVariable:
459                state = Bundle.getMessage(locale, "AddressByLocalVariable", _stateLocalVariable);
460                break;
461
462            case Formula:
463                state = Bundle.getMessage(locale, "AddressByFormula", _stateFormula);
464                break;
465
466            default:
467                throw new IllegalArgumentException("invalid _stateAddressing state: " + _stateAddressing.name());
468        }
469
470        return Bundle.getMessage(locale, "ActionLayoutTurnout_Long", editorName, positonableName, state);
471    }
472
473    /** {@inheritDoc} */
474    @Override
475    public void setup() {
476        if ((_layoutEditorName != null) && (_layoutEditor == null)) {
477            setLayoutEditor(_layoutEditorName);
478        }
479        if ((_layoutTurnoutName != null) && (_layoutTurnout == null)) {
480            setLayoutTurnout(_layoutTurnoutName);
481        }
482    }
483
484    /** {@inheritDoc} */
485    @Override
486    public void registerListenersForThisClass() {
487    }
488
489    /** {@inheritDoc} */
490    @Override
491    public void unregisterListenersForThisClass() {
492    }
493
494    /** {@inheritDoc} */
495    @Override
496    public void propertyChange(PropertyChangeEvent evt) {
497        if (EDITORS.equals(evt.getPropertyName())) {
498            if (evt.getOldValue() == _layoutEditor) {
499                _layoutEditor = null;
500                _layoutTurnout = null;
501                InstanceManager.getDefault(EditorManager.class)
502                        .removePropertyChangeListener(EDITORS, this);
503            }
504        }
505    }
506
507    /** {@inheritDoc} */
508    @Override
509    public void disposeMe() {
510        InstanceManager.getDefault(EditorManager.class)
511                .removePropertyChangeListener(EDITORS, this);
512    }
513
514
515    public enum Operation {
516        Disable(Bundle.getMessage("ActionLayoutTurnout_Disable")),
517        Enable(Bundle.getMessage("ActionLayoutTurnout_Enable"));
518//        Hide(Bundle.getMessage("ActionLayoutTurnout_Hide")),
519//        Show(Bundle.getMessage("ActionLayoutTurnout_Show"));
520
521        private final String _text;
522
523        private Operation(String text) {
524            this._text = text;
525        }
526
527        @Override
528        public String toString() {
529            return _text;
530        }
531
532    }
533
534    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionLayoutTurnout.class);
535
536}