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 Positionables with a particular
026 * class name on a panel.
027 *
028 * @author Daniel Bergqvist Copyright 2023
029 */
030public class ActionPositionableByClass extends AbstractDigitalAction implements VetoableChangeListener {
031
032    private String _editorName;
033    private Editor _editor;
034    private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct;
035//    private NamedBeanHandle<Turnout> _turnoutHandle;
036    private String _className;
037//    private Positionable _positionable;
038    private String _reference = "";
039    private String _localVariable = "";
040    private String _formula = "";
041    private ExpressionNode _expressionNode;
042    private NamedBeanAddressing _stateAddressing = NamedBeanAddressing.Direct;
043    private Operation _operation = Operation.Enable;
044    private String _stateReference = "";
045    private String _stateLocalVariable = "";
046    private String _stateFormula = "";
047    private ExpressionNode _stateExpressionNode;
048
049    public ActionPositionableByClass(String sys, String user)
050            throws BadUserNameException, BadSystemNameException {
051        super(sys, user);
052    }
053
054    @Override
055    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException {
056        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
057        String sysName = systemNames.get(getSystemName());
058        String userName = userNames.get(getSystemName());
059        if (sysName == null) sysName = manager.getAutoSystemName();
060        ActionPositionableByClass copy = new ActionPositionableByClass(sysName, userName);
061        copy.setComment(getComment());
062        copy.setEditor(_editorName);
063        copy.setClassName(_className);
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 setEditor(@CheckForNull String editorName) {
077        assertListenersAreNotRegistered(log, "setEditor");
078        _editorName = editorName;
079        if (editorName != null) {
080            _editor = jmri.InstanceManager.getDefault(jmri.jmrit.display.EditorManager.class).getByName(editorName);
081        } else {
082            _editor = null;
083        }
084//        InstanceManager.turnoutManagerInstance().addVetoableChangeListener(this);
085    }
086
087    public String getEditorName() {
088        return _editorName;
089    }
090
091    public void setClassName(@CheckForNull String className) {
092        assertListenersAreNotRegistered(log, "setPositionable");
093        _className = className;
094//        InstanceManager.turnoutManagerInstance().addVetoableChangeListener(this);
095    }
096
097    public String getClassName() {
098        return _className;
099    }
100
101    public void setAddressing(NamedBeanAddressing addressing) throws ParserException {
102        _addressing = addressing;
103        parseFormula();
104    }
105
106    public NamedBeanAddressing getAddressing() {
107        return _addressing;
108    }
109
110    public void setReference(@Nonnull String reference) {
111        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
112            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
113        }
114        _reference = reference;
115    }
116
117    public String getReference() {
118        return _reference;
119    }
120
121    public void setLocalVariable(@Nonnull String localVariable) {
122        _localVariable = localVariable;
123    }
124
125    public String getLocalVariable() {
126        return _localVariable;
127    }
128
129    public void setFormula(@Nonnull String formula) throws ParserException {
130        _formula = formula;
131        parseFormula();
132    }
133
134    public String getFormula() {
135        return _formula;
136    }
137
138    private void parseFormula() throws ParserException {
139        if (_addressing == NamedBeanAddressing.Formula) {
140            Map<String, Variable> variables = new HashMap<>();
141
142            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
143            _expressionNode = parser.parseExpression(_formula);
144        } else {
145            _expressionNode = null;
146        }
147    }
148
149    public void setStateAddressing(NamedBeanAddressing addressing) throws ParserException {
150        _stateAddressing = addressing;
151        parseStateFormula();
152    }
153
154    public NamedBeanAddressing getStateAddressing() {
155        return _stateAddressing;
156    }
157
158    public void setOperation(Operation isControlling) {
159        _operation = isControlling;
160    }
161
162    public Operation getOperation() {
163        return _operation;
164    }
165
166    public void setStateReference(@Nonnull String reference) {
167        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
168            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
169        }
170        _stateReference = reference;
171    }
172
173    public String getStateReference() {
174        return _stateReference;
175    }
176
177    public void setStateLocalVariable(@Nonnull String localVariable) {
178        _stateLocalVariable = localVariable;
179    }
180
181    public String getStateLocalVariable() {
182        return _stateLocalVariable;
183    }
184
185    public void setStateFormula(@Nonnull String formula) throws ParserException {
186        _stateFormula = formula;
187        parseStateFormula();
188    }
189
190    public String getStateFormula() {
191        return _stateFormula;
192    }
193
194    private void parseStateFormula() throws ParserException {
195        if (_stateAddressing == NamedBeanAddressing.Formula) {
196            Map<String, Variable> variables = new HashMap<>();
197
198            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
199            _stateExpressionNode = parser.parseExpression(_stateFormula);
200        } else {
201            _stateExpressionNode = null;
202        }
203    }
204
205    @Override
206    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
207/*
208        if ("CanDelete".equals(evt.getPropertyName())) { // No I18N
209            if (evt.getOldValue() instanceof Turnout) {
210                if (evt.getOldValue().equals(getTurnout().getBean())) {
211                    PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null);
212                    throw new PropertyVetoException(Bundle.getMessage("Turnout_TurnoutInUseTurnoutExpressionVeto", getDisplayName()), e); // NOI18N
213                }
214            }
215        } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N
216            if (evt.getOldValue() instanceof Turnout) {
217                if (evt.getOldValue().equals(getTurnout().getBean())) {
218                    removeTurnout();
219                }
220            }
221        }
222*/
223    }
224
225    /** {@inheritDoc} */
226    @Override
227    public LogixNG_Category getCategory() {
228        return CategoryDisplay.DISPLAY;
229    }
230
231    private String getNewState() throws JmriException {
232
233        switch (_stateAddressing) {
234            case Reference:
235                return ReferenceUtil.getReference(
236                        getConditionalNG().getSymbolTable(), _stateReference);
237
238            case LocalVariable:
239                SymbolTable symbolTable = getConditionalNG().getSymbolTable();
240                return TypeConversionUtil
241                        .convertToString(symbolTable.getValue(_stateLocalVariable), false);
242
243            case Formula:
244                return _stateExpressionNode != null
245                        ? TypeConversionUtil.convertToString(
246                                _stateExpressionNode.calculate(
247                                        getConditionalNG().getSymbolTable()), false)
248                        : null;
249
250            default:
251                throw new IllegalArgumentException("invalid _addressing state: " + _stateAddressing.name());
252        }
253    }
254
255    /** {@inheritDoc} */
256    @Override
257    public void execute() throws JmriException {
258        String className;
259
260//        System.out.format("ActionPositionableByClass.execute: %s%n", getLongDescription());
261
262        switch (_addressing) {
263            case Direct:
264                className = _className;
265                break;
266
267            case Reference:
268                className = ReferenceUtil.getReference(
269                        getConditionalNG().getSymbolTable(), _reference);
270                break;
271
272            case LocalVariable:
273                SymbolTable symbolTable = getConditionalNG().getSymbolTable();
274                className = TypeConversionUtil.convertToString(
275                        symbolTable.getValue(_localVariable), false);
276                break;
277
278            case Formula:
279                className = _expressionNode != null ?
280                        TypeConversionUtil.convertToString(_expressionNode.calculate(
281                                getConditionalNG().getSymbolTable()), false)
282                        : null;
283                break;
284
285            default:
286                throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name());
287        }
288
289//        System.out.format("ActionPositionableByClass.execute: positionable: %s%n", positionable);
290
291        if (className == null) {
292            log.error("className is null");
293            return;
294        }
295
296        Set<Positionable> positionableSet = _editor.getPositionablesByClassName(className);
297
298        if (positionableSet == null) {
299            var lng = getConditionalNG();
300            var cng = getConditionalNG();
301            var m = getModule();
302            String errorMessage;
303            if (m != null) {
304                errorMessage = Bundle.getMessage(
305                        "ActionPositionableByClass_ErrorNoPositionables_Module",
306                        getLongDescription(), m.getDisplayName(), getSystemName());
307            } else {
308                errorMessage = Bundle.getMessage(
309                        "ActionPositionableByClass_ErrorNoPositionables_LogixNG",
310                        getLongDescription(), lng.getDisplayName(), cng.getDisplayName(), getSystemName());
311            }
312            List<String> list = Arrays.asList(errorMessage.split("\n"));
313            throw new JmriException(Bundle.getMessage("ActionPositionableByClass_ErrorNoPositionables"), list);
314        }
315
316        String name = (_stateAddressing != NamedBeanAddressing.Direct)
317                ? getNewState() : null;
318
319        Operation operation;
320        if ((_stateAddressing == NamedBeanAddressing.Direct)) {
321            operation = _operation;
322        } else {
323            operation = Operation.valueOf(name);
324        }
325
326        ThreadingUtil.runOnGUI(() -> {
327            for (Positionable p : positionableSet) {
328                switch (operation) {
329                    case Disable:
330                        p.setControlling(false);
331                        break;
332                    case Enable:
333                        p.setControlling(true);
334                        break;
335                    case Hide:
336                        p.setHidden(true);
337                        break;
338                    case Show:
339                        p.setHidden(false);
340                        break;
341                    default:
342                        throw new RuntimeException("operation has invalid value: "+operation.name());
343                }
344            }
345            if (_editor instanceof LayoutEditor) {
346                // Insure that the panel has been updated
347                ((LayoutEditor) _editor).redrawPanel();
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, "ActionPositionableByClass_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 className;
376                if (this._className != null) {
377                    className = this._className;
378                } else {
379                    className = Bundle.getMessage(locale, "BeanNotSelected");
380                }
381                positonableName = Bundle.getMessage(locale, "AddressByDirect", className);
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, "ActionPositionableByClass_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    }
431
432    /** {@inheritDoc} */
433    @Override
434    public void registerListenersForThisClass() {
435    }
436
437    /** {@inheritDoc} */
438    @Override
439    public void unregisterListenersForThisClass() {
440    }
441
442    /** {@inheritDoc} */
443    @Override
444    public void disposeMe() {
445    }
446
447
448    public enum Operation {
449        Disable(Bundle.getMessage("ActionPositionableByClass_Disable")),
450        Enable(Bundle.getMessage("ActionPositionableByClass_Enable")),
451        Hide(Bundle.getMessage("ActionPositionableByClass_Hide")),
452        Show(Bundle.getMessage("ActionPositionableByClass_Show"));
453
454        private final String _text;
455
456        private Operation(String text) {
457            this._text = text;
458        }
459
460        @Override
461        public String toString() {
462            return _text;
463        }
464
465    }
466
467    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionPositionableByClass.class);
468
469}