001package jmri.jmrit.logixng.actions;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.util.*;
006
007import javax.annotation.Nonnull;
008
009import jmri.InstanceManager;
010import jmri.JmriException;
011import jmri.jmrit.Sound;
012import jmri.jmrit.logixng.*;
013import jmri.jmrit.logixng.util.LogixNG_SelectEnum;
014import jmri.jmrit.logixng.util.ReferenceUtil;
015import jmri.jmrit.logixng.util.parser.*;
016import jmri.util.ThreadingUtil;
017import jmri.util.TypeConversionUtil;
018
019/**
020 * Plays a sound.
021 *
022 * @author Daniel Bergqvist Copyright 2021
023 */
024public class ActionSound extends AbstractDigitalAction
025        implements PropertyChangeListener {
026
027    private final LogixNG_SelectEnum<Operation> _selectEnum =
028            new LogixNG_SelectEnum<>(this, Operation.values(), Operation.Play, this);
029
030    private NamedBeanAddressing _soundAddressing = NamedBeanAddressing.Direct;
031    private String _sound = "";
032    private String _soundReference = "";
033    private String _soundLocalVariable = "";
034    private String _soundFormula = "";
035    private ExpressionNode _soundExpressionNode;
036
037    public ActionSound(String sys, String user)
038            throws BadUserNameException, BadSystemNameException {
039        super(sys, user);
040    }
041
042    @Override
043    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException {
044        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
045        String sysName = systemNames.get(getSystemName());
046        String userName = userNames.get(getSystemName());
047        if (sysName == null) sysName = manager.getAutoSystemName();
048        ActionSound copy = new ActionSound(sysName, userName);
049        copy.setComment(getComment());
050        copy.setSound(_sound);
051        _selectEnum.copy(copy._selectEnum);
052        copy.setSoundAddressing(_soundAddressing);
053        copy.setSoundFormula(_soundFormula);
054        copy.setSoundLocalVariable(_soundLocalVariable);
055        copy.setSoundReference(_soundReference);
056        return manager.registerAction(copy);
057    }
058
059    public LogixNG_SelectEnum<Operation> getSelectEnum() {
060        return _selectEnum;
061    }
062
063    public void setSoundAddressing(NamedBeanAddressing addressing) throws ParserException {
064        _soundAddressing = addressing;
065        parseSoundFormula();
066    }
067
068    public NamedBeanAddressing getSoundAddressing() {
069        return _soundAddressing;
070    }
071
072    public void setSound(String sound) {
073        if (sound == null) _sound = "";
074        else _sound = sound;
075    }
076
077    public String getSound() {
078        return _sound;
079    }
080
081    public void setSoundReference(@Nonnull String reference) {
082        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
083            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
084        }
085        _soundReference = reference;
086    }
087
088    public String getSoundReference() {
089        return _soundReference;
090    }
091
092    public void setSoundLocalVariable(@Nonnull String localVariable) {
093        _soundLocalVariable = localVariable;
094    }
095
096    public String getSoundLocalVariable() {
097        return _soundLocalVariable;
098    }
099
100    public void setSoundFormula(@Nonnull String formula) throws ParserException {
101        _soundFormula = formula;
102        parseSoundFormula();
103    }
104
105    public String getSoundFormula() {
106        return _soundFormula;
107    }
108
109    private void parseSoundFormula() throws ParserException {
110        if (_soundAddressing == NamedBeanAddressing.Formula) {
111            Map<String, Variable> variables = new HashMap<>();
112
113            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
114            _soundExpressionNode = parser.parseExpression(_soundFormula);
115        } else {
116            _soundExpressionNode = null;
117        }
118    }
119
120    /** {@inheritDoc} */
121    @Override
122    public Category getCategory() {
123        return Category.ITEM;
124    }
125
126    private String getTheSound() throws JmriException {
127
128        switch (_soundAddressing) {
129            case Direct:
130                return _sound;
131
132            case Reference:
133                return ReferenceUtil.getReference(getConditionalNG().getSymbolTable(), _soundReference);
134
135            case LocalVariable:
136                SymbolTable symbolTable = getConditionalNG().getSymbolTable();
137                return TypeConversionUtil
138                        .convertToString(symbolTable.getValue(_soundLocalVariable), false);
139
140            case Formula:
141                return _soundExpressionNode != null
142                        ? TypeConversionUtil.convertToString(
143                                _soundExpressionNode.calculate(
144                                        getConditionalNG().getSymbolTable()), false)
145                        : "";
146
147            default:
148                throw new IllegalArgumentException("invalid _soundAddressing state: " + _soundAddressing.name());
149        }
150    }
151
152    /** {@inheritDoc} */
153    @Override
154    public void execute() throws JmriException {
155
156        Operation operation = _selectEnum.evaluateEnum(getConditionalNG());
157        String path = getTheSound();
158
159        ThreadingUtil.runOnLayoutWithJmriException(() -> {
160            switch (operation) {
161                case Play:
162                    if (!path.equals("")) {
163                        try {
164                            new Sound(path).play();
165                        } catch (NullPointerException ex) {
166                            throw new JmriException(Bundle.getMessage("ActionSound_Error_SoundNotFound", path));
167                        }
168                    }
169                    break;
170
171                default:
172                    throw new IllegalArgumentException("invalid operation: " + operation.name());
173            }
174        });
175    }
176
177    @Override
178    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
179        throw new UnsupportedOperationException("Not supported.");
180    }
181
182    @Override
183    public int getChildCount() {
184        return 0;
185    }
186
187    @Override
188    public String getShortDescription(Locale locale) {
189        return Bundle.getMessage(locale, "ActionSound_Short");
190    }
191
192    @Override
193    public String getLongDescription(Locale locale) {
194        String operation = _selectEnum.getDescription(locale);
195        String sound;
196
197        switch (_soundAddressing) {
198            case Direct:
199                sound = Bundle.getMessage(locale, "AddressByDirect", _sound);
200                break;
201
202            case Reference:
203                sound = Bundle.getMessage(locale, "AddressByReference", _soundReference);
204                break;
205
206            case LocalVariable:
207                sound = Bundle.getMessage(locale, "AddressByLocalVariable", _soundLocalVariable);
208                break;
209
210            case Formula:
211                sound = Bundle.getMessage(locale, "AddressByFormula", _soundFormula);
212                break;
213
214            default:
215                throw new IllegalArgumentException("invalid _stateAddressing state: " + _soundAddressing.name());
216        }
217
218        if (_selectEnum.getAddressing() == NamedBeanAddressing.Direct) {
219            if (_selectEnum.getEnum() == Operation.Play) {
220                return Bundle.getMessage(locale, "ActionSound_Long_Play", sound);
221            } else {
222                return Bundle.getMessage(locale, "ActionSound_Long", operation, sound);
223            }
224        } else {
225            return Bundle.getMessage(locale, "ActionSound_LongUnknownOper", operation, sound);
226        }
227    }
228
229    /** {@inheritDoc} */
230    @Override
231    public void setup() {
232        // Do nothing
233    }
234
235    /** {@inheritDoc} */
236    @Override
237    public void registerListenersForThisClass() {
238        if (!_listenersAreRegistered) {
239            _listenersAreRegistered = true;
240        }
241        _selectEnum.registerListeners();
242    }
243
244    /** {@inheritDoc} */
245    @Override
246    public void unregisterListenersForThisClass() {
247        if (_listenersAreRegistered) {
248            _listenersAreRegistered = false;
249        }
250        _selectEnum.unregisterListeners();
251    }
252
253    /** {@inheritDoc} */
254    @Override
255    public void firePropertyChange(String p, Object old, Object n) {
256        super.firePropertyChange(p, old, n);
257    }
258
259    /** {@inheritDoc} */
260    @Override
261    public void disposeMe() {
262        // Do nothing
263    }
264
265
266    public enum Operation {
267        Play(Bundle.getMessage("ActionSound_Operation_Play"));
268
269        private final String _text;
270
271        private Operation(String text) {
272            this._text = text;
273        }
274
275        @Override
276        public String toString() {
277            return _text;
278        }
279
280    }
281
282    /** {@inheritDoc} */
283    @Override
284    public void propertyChange(PropertyChangeEvent evt) {
285        getConditionalNG().execute();
286    }
287
288//    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionSound.class);
289
290}