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