001package jmri.jmrit.logixng.expressions;
002
003import java.beans.*;
004import java.util.*;
005
006import javax.annotation.Nonnull;
007
008import jmri.*;
009import jmri.Audio;
010import jmri.AudioManager;
011import jmri.jmrit.logixng.*;
012import jmri.jmrit.logixng.util.LogixNG_SelectNamedBean;
013import jmri.jmrit.logixng.util.ReferenceUtil;
014import jmri.jmrit.logixng.util.parser.*;
015import jmri.jmrit.logixng.util.parser.ExpressionNode;
016import jmri.jmrit.logixng.util.parser.RecursiveDescentParser;
017import jmri.util.TypeConversionUtil;
018
019/**
020 * This expression evaluates the state of an Audio.
021 *
022 * @author Daniel Bergqvist Copyright 2023
023 */
024public class ExpressionAudio extends AbstractDigitalExpression
025        implements PropertyChangeListener {
026
027    private final LogixNG_SelectNamedBean<Audio> _selectNamedBean =
028            new LogixNG_SelectNamedBean<>(
029                    this, Audio.class, InstanceManager.getDefault(AudioManager.class), this);
030
031    private boolean _hasChangedState = false;
032
033    private Is_IsNot_Enum _is_IsNot = Is_IsNot_Enum.Is;
034
035    private NamedBeanAddressing _stateAddressing = NamedBeanAddressing.Direct;
036    private AudioState _audioState = AudioState.Initial;
037    private String _stateReference = "";
038    private String _stateLocalVariable = "";
039    private String _stateFormula = "";
040    private ExpressionNode _stateExpressionNode;
041
042    private boolean _checkOnlyOnChange;
043
044
045    public ExpressionAudio(String sys, String user)
046            throws BadUserNameException, BadSystemNameException {
047        super(sys, user);
048    }
049
050    @Override
051    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException {
052        DigitalExpressionManager manager = InstanceManager.getDefault(DigitalExpressionManager.class);
053        String sysName = systemNames.get(getSystemName());
054        String userName = userNames.get(getSystemName());
055        if (sysName == null) sysName = manager.getAutoSystemName();
056        ExpressionAudio copy = new ExpressionAudio(sysName, userName);
057        copy.setComment(getComment());
058
059        _selectNamedBean.copy(copy._selectNamedBean);
060
061        copy.set_Is_IsNot(_is_IsNot);
062
063        copy.setStateAddressing(_stateAddressing);
064        copy.setBeanState(_audioState);
065        copy.setStateReference(_stateReference);
066        copy.setStateLocalVariable(_stateLocalVariable);
067        copy.setStateFormula(_stateFormula);
068
069        copy.setCheckOnlyOnChange(_checkOnlyOnChange);
070
071        return manager.registerExpression(copy);
072    }
073
074    public LogixNG_SelectNamedBean<Audio> getSelectNamedBean() {
075        return _selectNamedBean;
076    }
077
078    public void set_Is_IsNot(Is_IsNot_Enum is_IsNot) {
079        _is_IsNot = is_IsNot;
080    }
081
082    public Is_IsNot_Enum get_Is_IsNot() {
083        return _is_IsNot;
084    }
085
086
087    public void setStateAddressing(NamedBeanAddressing addressing) throws ParserException {
088        _stateAddressing = addressing;
089        parseStateFormula();
090    }
091
092    public NamedBeanAddressing getStateAddressing() {
093        return _stateAddressing;
094    }
095
096    public void setBeanState(AudioState state) {
097        _audioState = state;
098    }
099
100    public AudioState getBeanState() {
101        return _audioState;
102    }
103
104    public void setStateReference(@Nonnull String reference) {
105        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
106            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
107        }
108        _stateReference = reference;
109    }
110
111    public String getStateReference() {
112        return _stateReference;
113    }
114
115    public void setStateLocalVariable(@Nonnull String localVariable) {
116        _stateLocalVariable = localVariable;
117    }
118
119    public String getStateLocalVariable() {
120        return _stateLocalVariable;
121    }
122
123    public void setStateFormula(@Nonnull String formula) throws ParserException {
124        _stateFormula = formula;
125        parseStateFormula();
126    }
127
128    public String getStateFormula() {
129        return _stateFormula;
130    }
131
132    private void parseStateFormula() throws ParserException {
133        if (_stateAddressing == NamedBeanAddressing.Formula) {
134            Map<String, Variable> variables = new HashMap<>();
135
136            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
137            _stateExpressionNode = parser.parseExpression(_stateFormula);
138        } else {
139            _stateExpressionNode = null;
140        }
141    }
142
143
144    public void setCheckOnlyOnChange(boolean triggerOnlyOnChange) {
145        _checkOnlyOnChange = triggerOnlyOnChange;
146    }
147
148    public boolean isCheckOnlyOnChange() {
149        return _checkOnlyOnChange;
150    }
151
152
153    /** {@inheritDoc} */
154    @Override
155    public Category getCategory() {
156        return Category.ITEM;
157    }
158
159    private String getNewState() throws JmriException {
160
161        switch (_stateAddressing) {
162            case Reference:
163                return ReferenceUtil.getReference(
164                        getConditionalNG().getSymbolTable(), _stateReference);
165
166            case LocalVariable:
167                SymbolTable symbolTable =
168                        getConditionalNG().getSymbolTable();
169                return TypeConversionUtil
170                        .convertToString(symbolTable.getValue(_stateLocalVariable), false);
171
172            case Formula:
173                return _stateExpressionNode != null
174                        ? TypeConversionUtil.convertToString(
175                                _stateExpressionNode.calculate(
176                                        getConditionalNG().getSymbolTable()), false)
177                        : null;
178
179            default:
180                throw new IllegalArgumentException("invalid _addressing state: " + _stateAddressing.name());
181        }
182    }
183
184    /** {@inheritDoc} */
185    @Override
186    public boolean evaluate() throws JmriException {
187        Audio audio = _selectNamedBean.evaluateNamedBean(getConditionalNG());
188
189        if (audio == null) return false;
190
191        AudioState checkAudioState;
192
193        if ((_stateAddressing == NamedBeanAddressing.Direct)) {
194            checkAudioState = _audioState;
195        } else {
196            checkAudioState = AudioState.valueOf(getNewState());
197        }
198
199        int currentState = audio.getState();
200
201        if (_checkOnlyOnChange && !_hasChangedState) {
202            return false;
203        }
204
205        _hasChangedState = false;
206
207        if (_is_IsNot == Is_IsNot_Enum.Is) {
208            return currentState == checkAudioState.getID();
209        } else {
210            return currentState != checkAudioState.getID();
211        }
212    }
213
214    @Override
215    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
216        throw new UnsupportedOperationException("Not supported.");
217    }
218
219    @Override
220    public int getChildCount() {
221        return 0;
222    }
223
224    @Override
225    public String getShortDescription(Locale locale) {
226        return Bundle.getMessage(locale, "Audio_Short");
227    }
228
229    @Override
230    public String getLongDescription(Locale locale) {
231        String namedBean = _selectNamedBean.getDescription(locale);
232        String state;
233
234        switch (_stateAddressing) {
235            case Direct:
236                state = Bundle.getMessage(locale, "AddressByDirect", _audioState._text);
237                break;
238
239            case Reference:
240                state = Bundle.getMessage(locale, "AddressByReference", _stateReference);
241                break;
242
243            case LocalVariable:
244                state = Bundle.getMessage(locale, "AddressByLocalVariable", _stateLocalVariable);
245                break;
246
247            case Formula:
248                state = Bundle.getMessage(locale, "AddressByFormula", _stateFormula);
249                break;
250
251            default:
252                throw new IllegalArgumentException("invalid _stateAddressing state: " + _stateAddressing.name());
253        }
254
255
256        if (_checkOnlyOnChange) {
257            return Bundle.getMessage(locale, "Audio_Long4", namedBean, _is_IsNot.toString(), state, Bundle.getMessage(locale, "Audio_CheckOnlyOnChange"));
258        } else {
259            return Bundle.getMessage(locale, "Audio_Long3", namedBean, _is_IsNot.toString(), state);
260        }
261    }
262
263    /** {@inheritDoc} */
264    @Override
265    public void setup() {
266        // Do nothing
267    }
268
269    /** {@inheritDoc} */
270    @Override
271    public void registerListenersForThisClass() {
272        if (!_listenersAreRegistered) {
273            _selectNamedBean.addPropertyChangeListener(this);
274            _selectNamedBean.registerListeners();
275            _listenersAreRegistered = true;
276        }
277    }
278
279    /** {@inheritDoc} */
280    @Override
281    public void unregisterListenersForThisClass() {
282        if (_listenersAreRegistered) {
283            _selectNamedBean.removePropertyChangeListener(this);
284            _selectNamedBean.unregisterListeners();
285            _listenersAreRegistered = false;
286        }
287    }
288
289    /** {@inheritDoc} */
290    @Override
291    public void propertyChange(PropertyChangeEvent evt) {
292        if (!Objects.equals(evt.getNewValue(), evt.getOldValue())) {
293            _hasChangedState = true;
294        }
295        getConditionalNG().execute();
296    }
297
298    /** {@inheritDoc} */
299    @Override
300    public void disposeMe() {
301    }
302
303    public enum AudioState {
304        Initial(Audio.STATE_INITIAL, Bundle.getMessage("Audio_StateInitial")),
305        Stopped(Audio.STATE_STOPPED, Bundle.getMessage("Audio_StateStopped")),
306        Playing(Audio.STATE_PLAYING, Bundle.getMessage("Audio_StatePlaying")),
307        Empty(Audio.STATE_EMPTY, Bundle.getMessage("Audio_StateEmpty")),
308        Loaded(Audio.STATE_LOADED, Bundle.getMessage("Audio_StateLoaded")),
309        Positioned(Audio.STATE_POSITIONED, Bundle.getMessage("Audio_StatePositioned")),
310        Moving(Audio.STATE_MOVING, Bundle.getMessage("Audio_StateMoving"));
311
312        private final int _id;
313        private final String _text;
314
315        private AudioState(int id, String text) {
316            this._id = id;
317            this._text = text;
318        }
319
320        public int getID() {
321            return _id;
322        }
323
324        @Override
325        public String toString() {
326            return _text;
327        }
328    }
329
330    /** {@inheritDoc} */
331    @Override
332    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
333        log.debug("getUsageReport :: ExpressionAudio: bean = {}, report = {}", cdl, report);
334        _selectNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Expression);
335    }
336
337    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ExpressionAudio.class);
338
339}