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