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