001package jmri.jmrit.logixng.actions;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyVetoException;
005import java.beans.VetoableChangeListener;
006import java.util.*;
007
008import javax.annotation.Nonnull;
009
010import jmri.*;
011import jmri.jmrit.audio.AudioListener;
012import jmri.jmrit.audio.AudioSource;
013import jmri.jmrit.logixng.*;
014import jmri.jmrit.logixng.util.ReferenceUtil;
015import jmri.jmrit.logixng.util.parser.*;
016import jmri.jmrit.logixng.util.parser.ExpressionNode;
017import jmri.jmrit.logixng.util.parser.RecursiveDescentParser;
018import jmri.util.ThreadingUtil;
019import jmri.util.TypeConversionUtil;
020
021/**
022 * This action controls an audio object.
023 *
024 * @author Daniel Bergqvist Copyright 2021
025 */
026public class ActionAudio extends AbstractDigitalAction implements VetoableChangeListener {
027
028    private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct;
029    private NamedBeanHandle<Audio> _audioHandle;
030    private String _reference = "";
031    private String _localVariable = "";
032    private String _formula = "";
033    private ExpressionNode _expressionNode;
034    private NamedBeanAddressing _operationAddressing = NamedBeanAddressing.Direct;
035    private Operation _operation = Operation.Play;
036    private String _operationReference = "";
037    private String _operationLocalVariable = "";
038    private String _operationFormula = "";
039    private ExpressionNode _operationExpressionNode;
040
041    public ActionAudio(String sys, String user)
042            throws BadUserNameException, BadSystemNameException {
043        super(sys, user);
044    }
045
046    @Override
047    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException {
048        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
049        String sysName = systemNames.get(getSystemName());
050        String userName = userNames.get(getSystemName());
051        if (sysName == null) sysName = manager.getAutoSystemName();
052        ActionAudio copy = new ActionAudio(sysName, userName);
053        copy.setComment(getComment());
054        if (_audioHandle != null) copy.setAudio(_audioHandle);
055        copy.setOperation(_operation);
056        copy.setAddressing(_addressing);
057        copy.setFormula(_formula);
058        copy.setLocalVariable(_localVariable);
059        copy.setReference(_reference);
060        copy.setOperationAddressing(_operationAddressing);
061        copy.setOperationFormula(_operationFormula);
062        copy.setOperationLocalVariable(_operationLocalVariable);
063        copy.setOperationReference(_operationReference);
064        return manager.registerAction(copy);
065    }
066
067    public void setAudio(@Nonnull String audioName) {
068        assertListenersAreNotRegistered(log, "setAudio");
069        Audio audio = InstanceManager.getDefault(AudioManager.class).getAudio(audioName);
070        if (audio != null) {
071            setAudio(audio);
072        } else {
073            removeAudio();
074            log.warn("audio \"{}\" is not found", audioName);
075        }
076    }
077
078    public void setAudio(@Nonnull NamedBeanHandle<Audio> handle) {
079        assertListenersAreNotRegistered(log, "setAudio");
080        _audioHandle = handle;
081        InstanceManager.getDefault(AudioManager.class).addVetoableChangeListener(this);
082    }
083
084    public void setAudio(@Nonnull Audio audio) {
085        assertListenersAreNotRegistered(log, "setAudio");
086        setAudio(InstanceManager.getDefault(NamedBeanHandleManager.class)
087                .getNamedBeanHandle(audio.getDisplayName(), audio));
088    }
089
090    public void removeAudio() {
091        assertListenersAreNotRegistered(log, "setAudio");
092        if (_audioHandle != null) {
093            InstanceManager.getDefault(AudioManager.class).removeVetoableChangeListener(this);
094            _audioHandle = null;
095        }
096    }
097
098    public NamedBeanHandle<Audio> getAudio() {
099        return _audioHandle;
100    }
101
102    public void setAddressing(NamedBeanAddressing addressing) throws ParserException {
103        _addressing = addressing;
104        parseFormula();
105    }
106
107    public NamedBeanAddressing getAddressing() {
108        return _addressing;
109    }
110
111    public void setReference(@Nonnull String reference) {
112        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
113            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
114        }
115        _reference = reference;
116    }
117
118    public String getReference() {
119        return _reference;
120    }
121
122    public void setLocalVariable(@Nonnull String localVariable) {
123        _localVariable = localVariable;
124    }
125
126    public String getLocalVariable() {
127        return _localVariable;
128    }
129
130    public void setFormula(@Nonnull String formula) throws ParserException {
131        _formula = formula;
132        parseFormula();
133    }
134
135    public String getFormula() {
136        return _formula;
137    }
138
139    private void parseFormula() throws ParserException {
140        if (_addressing == NamedBeanAddressing.Formula) {
141            Map<String, Variable> variables = new HashMap<>();
142
143            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
144            _expressionNode = parser.parseExpression(_formula);
145        } else {
146            _expressionNode = null;
147        }
148    }
149
150    public void setOperationAddressing(NamedBeanAddressing addressing) throws ParserException {
151        _operationAddressing = addressing;
152        parseOperationFormula();
153    }
154
155    public NamedBeanAddressing getOperationAddressing() {
156        return _operationAddressing;
157    }
158
159    public void setOperation(Operation state) {
160        _operation = state;
161    }
162
163    public Operation getOperation() {
164        return _operation;
165    }
166
167    public void setOperationReference(@Nonnull String reference) {
168        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
169            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
170        }
171        _operationReference = reference;
172    }
173
174    public String getOperationReference() {
175        return _operationReference;
176    }
177
178    public void setOperationLocalVariable(@Nonnull String localVariable) {
179        _operationLocalVariable = localVariable;
180    }
181
182    public String getOperationLocalVariable() {
183        return _operationLocalVariable;
184    }
185
186    public void setOperationFormula(@Nonnull String formula) throws ParserException {
187        _operationFormula = formula;
188        parseOperationFormula();
189    }
190
191    public String getOperationFormula() {
192        return _operationFormula;
193    }
194
195    private void parseOperationFormula() throws ParserException {
196        if (_operationAddressing == NamedBeanAddressing.Formula) {
197            Map<String, Variable> variables = new HashMap<>();
198
199            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
200            _operationExpressionNode = parser.parseExpression(_operationFormula);
201        } else {
202            _operationExpressionNode = null;
203        }
204    }
205
206    @Override
207    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
208        if ("CanDelete".equals(evt.getPropertyName())) { // No I18N
209            if (evt.getOldValue() instanceof Audio) {
210                if (evt.getOldValue().equals(getAudio().getBean())) {
211                    PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null);
212                    throw new PropertyVetoException(Bundle.getMessage("Audio_AudioInUseAudioActionVeto", getDisplayName()), e); // NOI18N
213                }
214            }
215        } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N
216            if (evt.getOldValue() instanceof Audio) {
217                if (evt.getOldValue().equals(getAudio().getBean())) {
218                    removeAudio();
219                }
220            }
221        }
222    }
223
224    /** {@inheritDoc} */
225    @Override
226    public Category getCategory() {
227        return Category.ITEM;
228    }
229
230    private String getNewState() throws JmriException {
231
232        switch (_operationAddressing) {
233            case Reference:
234                return ReferenceUtil.getReference(getConditionalNG().getSymbolTable(), _operationReference);
235
236            case LocalVariable:
237                SymbolTable symbolTable = getConditionalNG().getSymbolTable();
238                return TypeConversionUtil
239                        .convertToString(symbolTable.getValue(_operationLocalVariable), false);
240
241            case Formula:
242                return _operationExpressionNode != null
243                        ? TypeConversionUtil.convertToString(
244                                _operationExpressionNode.calculate(
245                                        getConditionalNG().getSymbolTable()), false)
246                        : null;
247
248            default:
249                throw new IllegalArgumentException("invalid _addressing state: " + _operationAddressing.name());
250        }
251    }
252
253    /** {@inheritDoc} */
254    @Override
255    public void execute() throws JmriException {
256        Audio audio;
257
258//        System.out.format("ActionAudio.execute: %s%n", getLongDescription());
259
260        switch (_addressing) {
261            case Direct:
262                audio = _audioHandle != null ? _audioHandle.getBean() : null;
263                break;
264
265            case Reference:
266                String ref = ReferenceUtil.getReference(
267                        getConditionalNG().getSymbolTable(), _reference);
268                audio = InstanceManager.getDefault(AudioManager.class)
269                        .getNamedBean(ref);
270                break;
271
272            case LocalVariable:
273                SymbolTable symbolTable = getConditionalNG().getSymbolTable();
274                audio = InstanceManager.getDefault(AudioManager.class)
275                        .getNamedBean(TypeConversionUtil
276                                .convertToString(symbolTable.getValue(_localVariable), false));
277                break;
278
279            case Formula:
280                audio = _expressionNode != null ?
281                        InstanceManager.getDefault(AudioManager.class)
282                                .getNamedBean(TypeConversionUtil
283                                        .convertToString(_expressionNode.calculate(
284                                                getConditionalNG().getSymbolTable()), false))
285                        : null;
286                break;
287
288            default:
289                throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name());
290        }
291
292//        System.out.format("ActionAudio.execute: audio: %s%n", audio);
293
294        if (audio == null) {
295//            log.warn("audio is null");
296            return;
297        }
298
299        String name = (_operationAddressing != NamedBeanAddressing.Direct)
300                ? getNewState() : null;
301
302        Operation operation;
303        if ((_operationAddressing == NamedBeanAddressing.Direct)) {
304            operation = _operation;
305        } else {
306            operation = Operation.valueOf(name);
307        }
308
309        ThreadingUtil.runOnLayoutWithJmriException(() -> {
310            if (audio.getSubType() == Audio.SOURCE) {
311                AudioSource audioSource = (AudioSource) audio;
312                switch (operation) {
313                    case Play:
314                        audioSource.play();
315                        break;
316                    case Stop:
317                        audioSource.stop();
318                        break;
319                    case PlayToggle:
320                        audioSource.togglePlay();
321                        break;
322                    case Pause:
323                        audioSource.pause();
324                        break;
325                    case Resume:
326                        audioSource.resume();
327                        break;
328                    case PauseToggle:
329                        audioSource.togglePause();
330                        break;
331                    case Rewind:
332                        audioSource.rewind();
333                        break;
334                    case FadeIn:
335                        audioSource.fadeIn();
336                        break;
337                    case FadeOut:
338                        audioSource.fadeOut();
339                        break;
340                    case ResetPosition:
341                        audioSource.resetCurrentPosition();
342                        break;
343                    default:
344                        break;
345                }
346            } else if (audio.getSubType() == Audio.LISTENER) {
347                AudioListener audioListener = (AudioListener) audio;
348                switch (operation) {
349                    case ResetPosition:
350                        audioListener.resetCurrentPosition();
351                        break;
352                    default:
353                        break; // nothing needed for others
354                }
355            }
356        });
357    }
358
359    @Override
360    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
361        throw new UnsupportedOperationException("Not supported.");
362    }
363
364    @Override
365    public int getChildCount() {
366        return 0;
367    }
368
369    @Override
370    public String getShortDescription(Locale locale) {
371        return Bundle.getMessage(locale, "ActionAudio_Short");
372    }
373
374    @Override
375    public String getLongDescription(Locale locale) {
376        String namedBean;
377        String operation;
378
379        switch (_addressing) {
380            case Direct:
381                String audioName;
382                if (_audioHandle != null) {
383                    audioName = _audioHandle.getBean().getDisplayName();
384                } else {
385                    audioName = Bundle.getMessage(locale, "BeanNotSelected");
386                }
387                namedBean = Bundle.getMessage(locale, "AddressByDirect", audioName);
388                break;
389
390            case Reference:
391                namedBean = Bundle.getMessage(locale, "AddressByReference", _reference);
392                break;
393
394            case LocalVariable:
395                namedBean = Bundle.getMessage(locale, "AddressByLocalVariable", _localVariable);
396                break;
397
398            case Formula:
399                namedBean = Bundle.getMessage(locale, "AddressByFormula", _formula);
400                break;
401
402            default:
403                throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name());
404        }
405
406        switch (_operationAddressing) {
407            case Direct:
408                operation = Bundle.getMessage(locale, "AddressByDirect", _operation._text);
409                break;
410
411            case Reference:
412                operation = Bundle.getMessage(locale, "AddressByReference", _operationReference);
413                break;
414
415            case LocalVariable:
416                operation = Bundle.getMessage(locale, "AddressByLocalVariable", _operationLocalVariable);
417                break;
418
419            case Formula:
420                operation = Bundle.getMessage(locale, "AddressByFormula", _operationFormula);
421                break;
422
423            default:
424                throw new IllegalArgumentException("invalid _stateAddressing state: " + _operationAddressing.name());
425        }
426
427        return Bundle.getMessage(locale, "ActionAudio_Long", operation, namedBean);
428    }
429
430    /** {@inheritDoc} */
431    @Override
432    public void setup() {
433        // Do nothing
434    }
435
436    /** {@inheritDoc} */
437    @Override
438    public void registerListenersForThisClass() {
439    }
440
441    /** {@inheritDoc} */
442    @Override
443    public void unregisterListenersForThisClass() {
444    }
445
446    /** {@inheritDoc} */
447    @Override
448    public void disposeMe() {
449    }
450
451
452    public enum Operation {
453        Play(Bundle.getMessage("ActionAudio_Operation_Play")),
454        PlayToggle(Bundle.getMessage("ActionAudio_Operation_PlayToggle")),
455        Pause(Bundle.getMessage("ActionAudio_Operation_Pause")),
456        PauseToggle(Bundle.getMessage("ActionAudio_Operation_PauseToggle")),
457        Resume(Bundle.getMessage("ActionAudio_Operation_Resume")),
458        Stop(Bundle.getMessage("ActionAudio_Operation_Stop")),
459        FadeIn(Bundle.getMessage("ActionAudio_Operation_FadeIn")),
460        FadeOut(Bundle.getMessage("ActionAudio_Operation_FadeOut")),
461        Rewind(Bundle.getMessage("ActionAudio_Operation_Rewind")),
462        ResetPosition(Bundle.getMessage("ActionAudio_Operation_ResetPosition"));
463
464        private final String _text;
465
466        private Operation(String text) {
467            this._text = text;
468        }
469
470        @Override
471        public String toString() {
472            return _text;
473        }
474
475    }
476
477    /** {@inheritDoc} */
478    @Override
479    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
480        log.debug("getUsageReport :: ActionAudio: bean = {}, report = {}", cdl, report);
481        if (getAudio() != null && bean.equals(getAudio().getBean())) {
482            report.add(new NamedBeanUsageReport("LogixNGAction", cdl, getLongDescription()));
483        }
484    }
485
486    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionAudio.class);
487
488}