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