001package jmri.jmrit.display.logixng;
002
003import java.beans.VetoableChangeListener;
004import java.util.*;
005
006import javax.annotation.CheckForNull;
007import javax.annotation.Nonnull;
008
009import jmri.*;
010import jmri.jmrit.display.Editor;
011import jmri.jmrit.display.AudioIcon;
012import jmri.jmrit.display.Positionable;
013import jmri.jmrit.logixng.*;
014import jmri.jmrit.logixng.actions.AbstractDigitalAction;
015import jmri.jmrit.logixng.util.ReferenceUtil;
016import jmri.jmrit.logixng.util.parser.*;
017import jmri.jmrit.logixng.util.parser.ExpressionNode;
018import jmri.jmrit.logixng.util.parser.RecursiveDescentParser;
019import jmri.util.ThreadingUtil;
020import jmri.util.TypeConversionUtil;
021
022/**
023 * This action controls various things of a AudioIcon on a panel.
024 *
025 * @author Daniel Bergqvist Copyright 2023
026 */
027public class ActionAudioIcon extends AbstractDigitalAction implements VetoableChangeListener {
028
029    private String _editorName;
030    private Editor _editor;
031    private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct;
032    private String _positionableName;
033    private AudioIcon _audioIcon;
034    private String _reference = "";
035    private String _localVariable = "";
036    private String _formula = "";
037    private ExpressionNode _expressionNode;
038    private NamedBeanAddressing _stateAddressing = NamedBeanAddressing.Direct;
039    private Operation _operation = Operation.Play;
040    private String _stateReference = "";
041    private String _stateLocalVariable = "";
042    private String _stateFormula = "";
043    private ExpressionNode _stateExpressionNode;
044
045    public ActionAudioIcon(String sys, String user)
046            throws BadUserNameException, BadSystemNameException {
047        super(sys, user, CategoryDisplay.DISPLAY);
048    }
049
050    @Override
051    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException {
052        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
053        String sysName = systemNames.get(getSystemName());
054        String userName = userNames.get(getSystemName());
055        if (sysName == null) sysName = manager.getAutoSystemName();
056        ActionAudioIcon copy = new ActionAudioIcon(sysName, userName);
057        copy.setComment(getComment());
058        copy.setEditor(_editorName);
059        copy.setAudioIcon(_positionableName);
060        copy.setOperation(_operation);
061        copy.setAddressing(_addressing);
062        copy.setFormula(_formula);
063        copy.setLocalVariable(_localVariable);
064        copy.setReference(_reference);
065        copy.setStateAddressing(_stateAddressing);
066        copy.setStateFormula(_stateFormula);
067        copy.setStateLocalVariable(_stateLocalVariable);
068        copy.setStateReference(_stateReference);
069        return manager.registerAction(copy);
070    }
071
072    public void setEditor(@CheckForNull String editorName) {
073        assertListenersAreNotRegistered(log, "setEditor");
074        _editorName = editorName;
075        if (editorName != null) {
076            _editor = jmri.InstanceManager.getDefault(jmri.jmrit.display.EditorManager.class).getByName(editorName);
077        } else {
078            _editor = null;
079        }
080    }
081
082    public String getEditorName() {
083        return _editorName;
084    }
085
086    public void setAudioIcon(@CheckForNull String positionableName) {
087        assertListenersAreNotRegistered(log, "setAudioIcon");
088        _positionableName = positionableName;
089        if ((positionableName != null) && (_editor != null)) {
090            Positionable pos = _editor.getIdContents().get(_positionableName);
091            if (pos instanceof AudioIcon) {
092                _audioIcon = (AudioIcon)pos;
093            } else {
094                throw new IllegalArgumentException("positionableName is not an AudioIcon");
095            }
096        } else {
097            _audioIcon = null;
098        }
099    }
100
101    public String getAudioIconName() {
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    private String getNewState() throws JmriException {
230
231        switch (_stateAddressing) {
232            case Reference:
233                return ReferenceUtil.getReference(
234                        getConditionalNG().getSymbolTable(), _stateReference);
235
236            case LocalVariable:
237                SymbolTable symbolTable = getConditionalNG().getSymbolTable();
238                return TypeConversionUtil
239                        .convertToString(symbolTable.getValue(_stateLocalVariable), false);
240
241            case Formula:
242                return _stateExpressionNode != null
243                        ? TypeConversionUtil.convertToString(
244                                _stateExpressionNode.calculate(
245                                        getConditionalNG().getSymbolTable()), false)
246                        : null;
247
248            default:
249                throw new IllegalArgumentException("invalid _addressing state: " + _stateAddressing.name());
250        }
251    }
252
253    /** {@inheritDoc} */
254    @Override
255    public void execute() throws JmriException {
256        Positionable positionable;
257
258        switch (_addressing) {
259            case Direct:
260                positionable = this._audioIcon;
261                break;
262
263            case Reference:
264                String ref = ReferenceUtil.getReference(
265                        getConditionalNG().getSymbolTable(), _reference);
266                positionable = _editor.getIdContents().get(ref);
267                break;
268
269            case LocalVariable:
270                SymbolTable symbolTable = getConditionalNG().getSymbolTable();
271                positionable = _editor.getIdContents().get(TypeConversionUtil
272                                .convertToString(symbolTable.getValue(_localVariable), false));
273                break;
274
275            case Formula:
276                positionable = _expressionNode != null ?
277                        _editor.getIdContents().get(TypeConversionUtil
278                                        .convertToString(_expressionNode.calculate(
279                                                getConditionalNG().getSymbolTable()), false))
280                        : null;
281                break;
282
283            default:
284                throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name());
285        }
286
287        if (positionable == null) {
288            log.error("positionable is null");
289            return;
290        }
291
292        AudioIcon audioIcon;
293        if (positionable instanceof AudioIcon) {
294            audioIcon = (AudioIcon)positionable;
295        } else {
296            throw new IllegalArgumentException("positionableName is not an AudioIcon");
297        }
298
299
300        String name = (_stateAddressing != NamedBeanAddressing.Direct)
301                ? getNewState() : null;
302
303        Operation operation;
304        if ((_stateAddressing == NamedBeanAddressing.Direct)) {
305            operation = _operation;
306        } else {
307            operation = Operation.valueOf(name);
308        }
309
310        ThreadingUtil.runOnLayout(() -> {
311            switch (operation) {
312                case Play:
313                    audioIcon.play();
314                    break;
315                case Stop:
316                    audioIcon.stop();
317                    break;
318                default:
319                    throw new RuntimeException("operation has invalid value: "+operation.name());
320            }
321        });
322    }
323
324    @Override
325    public String getShortDescription(Locale locale) {
326        return Bundle.getMessage(locale, "ActionAudioIcon_Short");
327    }
328
329    @Override
330    public String getLongDescription(Locale locale) {
331        String editorName = _editorName != null ? _editorName : Bundle.getMessage(locale, "BeanNotSelected");
332        String positonableName;
333        String state;
334
335        switch (_addressing) {
336            case Direct:
337                String positionableName;
338                if (this._positionableName != null) {
339                    positionableName = this._positionableName;
340                } else {
341                    positionableName = Bundle.getMessage(locale, "BeanNotSelected");
342                }
343                positonableName = Bundle.getMessage(locale, "AddressByDirect", positionableName);
344                break;
345
346            case Reference:
347                positonableName = Bundle.getMessage(locale, "AddressByReference", _reference);
348                break;
349
350            case LocalVariable:
351                positonableName = Bundle.getMessage(locale, "AddressByLocalVariable", _localVariable);
352                break;
353
354            case Formula:
355                positonableName = Bundle.getMessage(locale, "AddressByFormula", _formula);
356                break;
357
358            default:
359                throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name());
360        }
361
362        switch (_stateAddressing) {
363            case Direct:
364                state = Bundle.getMessage(locale, "AddressByDirect", _operation._text);
365                break;
366
367            case Reference:
368                state = Bundle.getMessage(locale, "AddressByReference", _stateReference);
369                break;
370
371            case LocalVariable:
372                state = Bundle.getMessage(locale, "AddressByLocalVariable", _stateLocalVariable);
373                break;
374
375            case Formula:
376                state = Bundle.getMessage(locale, "AddressByFormula", _stateFormula);
377                break;
378
379            default:
380                throw new IllegalArgumentException("invalid _stateAddressing state: " + _stateAddressing.name());
381        }
382
383        return Bundle.getMessage(locale, "ActionAudioIcon_Long", editorName, positonableName, state);
384    }
385
386    /** {@inheritDoc} */
387    @Override
388    public void setup() {
389        if ((_editorName != null) && (_editor == null)) {
390            setEditor(_editorName);
391        }
392        if ((_positionableName != null) && (_audioIcon == null)) {
393            setAudioIcon(_positionableName);
394        }
395    }
396
397    public enum Operation {
398        Play(Bundle.getMessage("ActionAudioIcon_Play")),
399        Stop(Bundle.getMessage("ActionAudioIcon_Stop"));
400
401        private final String _text;
402
403        private Operation(String text) {
404            this._text = text;
405        }
406
407        @Override
408        public String toString() {
409            return _text;
410        }
411
412    }
413
414    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionAudioIcon.class);
415
416}