001package jmri.jmrit.logixng.expressions;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.beans.VetoableChangeListener;
006import java.util.*;
007
008import javax.annotation.Nonnull;
009
010import jmri.*;
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 * Evaluates the state of a SignalHead.
021 *
022 * @author Daniel Bergqvist Copyright 2020
023 */
024public class ExpressionSignalHead extends AbstractDigitalExpression
025        implements PropertyChangeListener, VetoableChangeListener {
026
027    private final LogixNG_SelectNamedBean<SignalHead> _selectNamedBean =
028            new LogixNG_SelectNamedBean<>(
029                    this, SignalHead.class, InstanceManager.getDefault(SignalHeadManager.class), this);
030
031    private NamedBeanAddressing _queryAddressing = NamedBeanAddressing.Direct;
032    private QueryType _queryType = QueryType.Appearance;
033    private String _queryReference = "";
034    private String _queryLocalVariable = "";
035    private String _queryFormula = "";
036    private ExpressionNode _queryExpressionNode;
037
038    private NamedBeanAddressing _appearanceAddressing = NamedBeanAddressing.Direct;
039    private int _signalHeadAppearance = SignalHead.DARK;
040    private String _appearanceReference = "";
041    private String _appearanceLocalVariable = "";
042    private String _appearanceFormula = "";
043    private ExpressionNode _appearanceExpressionNode;
044
045    private final LogixNG_SelectNamedBean<SignalHead> _selectExampleNamedBean =
046            new LogixNG_SelectNamedBean<>(
047                    this, SignalHead.class, InstanceManager.getDefault(SignalHeadManager.class), this);
048
049
050    public ExpressionSignalHead(String sys, String user)
051            throws BadUserNameException, BadSystemNameException {
052        super(sys, user);
053    }
054
055    @Override
056    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException {
057        DigitalExpressionManager manager = InstanceManager.getDefault(DigitalExpressionManager.class);
058        String sysName = systemNames.get(getSystemName());
059        String userName = userNames.get(getSystemName());
060        if (sysName == null) sysName = manager.getAutoSystemName();
061        ExpressionSignalHead copy = new ExpressionSignalHead(sysName, userName);
062        copy.setComment(getComment());
063        _selectNamedBean.copy(copy._selectNamedBean);
064        copy.setAppearance(_signalHeadAppearance);
065        copy.setQueryAddressing(_queryAddressing);
066        copy.setQueryType(_queryType);
067        copy.setQueryFormula(_queryFormula);
068        copy.setQueryLocalVariable(_queryLocalVariable);
069        copy.setQueryReference(_queryReference);
070        copy.setAppearanceAddressing(_appearanceAddressing);
071        copy.setAppearanceFormula(_appearanceFormula);
072        copy.setAppearanceLocalVariable(_appearanceLocalVariable);
073        copy.setAppearanceReference(_appearanceReference);
074        _selectExampleNamedBean.copy(copy._selectExampleNamedBean);
075        return manager.registerExpression(copy).deepCopyChildren(this, systemNames, userNames);
076    }
077
078    public LogixNG_SelectNamedBean<SignalHead> getSelectNamedBean() {
079        return _selectNamedBean;
080    }
081
082    public LogixNG_SelectNamedBean<SignalHead> getSelectExampleNamedBean() {
083        return _selectExampleNamedBean;
084    }
085
086    public void setQueryAddressing(NamedBeanAddressing addressing) throws ParserException {
087        _queryAddressing = addressing;
088        parseQueryFormula();
089    }
090
091    public NamedBeanAddressing getQueryAddressing() {
092        return _queryAddressing;
093    }
094
095    public void setQueryType(QueryType queryType) {
096        _queryType = queryType;
097    }
098
099    public QueryType getQueryType() {
100        return _queryType;
101    }
102
103    public void setQueryReference(@Nonnull String reference) {
104        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
105            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
106        }
107        _queryReference = reference;
108    }
109
110    public String getQueryReference() {
111        return _queryReference;
112    }
113
114    public void setQueryLocalVariable(@Nonnull String localVariable) {
115        _queryLocalVariable = localVariable;
116    }
117
118    public String getQueryLocalVariable() {
119        return _queryLocalVariable;
120    }
121
122    public void setQueryFormula(@Nonnull String formula) throws ParserException {
123        _queryFormula = formula;
124        parseQueryFormula();
125    }
126
127    public String getQueryFormula() {
128        return _queryFormula;
129    }
130
131    private void parseQueryFormula() throws ParserException {
132        if (_queryAddressing == NamedBeanAddressing.Formula) {
133            Map<String, Variable> variables = new HashMap<>();
134
135            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
136            _queryExpressionNode = parser.parseExpression(_queryFormula);
137        } else {
138            _queryExpressionNode = null;
139        }
140    }
141
142    public void setAppearanceAddressing(NamedBeanAddressing addressing) throws ParserException {
143        _appearanceAddressing = addressing;
144        parseAppearanceFormula();
145    }
146
147    public NamedBeanAddressing getAppearanceAddressing() {
148        return _appearanceAddressing;
149    }
150
151    public void setAppearance(int appearance) {
152        _signalHeadAppearance = appearance;
153    }
154
155    public int getAppearance() {
156        return _signalHeadAppearance;
157    }
158
159    public void setAppearanceReference(@Nonnull String reference) {
160        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
161            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
162        }
163        _appearanceReference = reference;
164    }
165
166    public String getAppearanceReference() {
167        return _appearanceReference;
168    }
169
170    public void setAppearanceLocalVariable(@Nonnull String localVariable) {
171        _appearanceLocalVariable = localVariable;
172    }
173
174    public String getAppearanceLocalVariable() {
175        return _appearanceLocalVariable;
176    }
177
178    public void setAppearanceFormula(@Nonnull String formula) throws ParserException {
179        _appearanceFormula = formula;
180        parseAppearanceFormula();
181    }
182
183    public String getAppearanceFormula() {
184        return _appearanceFormula;
185    }
186
187    private void parseAppearanceFormula() throws ParserException {
188        if (_appearanceAddressing == NamedBeanAddressing.Formula) {
189            Map<String, Variable> variables = new HashMap<>();
190
191            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
192            _appearanceExpressionNode = parser.parseExpression(_appearanceFormula);
193        } else {
194            _appearanceExpressionNode = null;
195        }
196    }
197
198    /** {@inheritDoc} */
199    @Override
200    public Category getCategory() {
201        return Category.ITEM;
202    }
203
204    private int getAppearanceFromName(String name, SignalHead signalHead) {
205        if (signalHead == null) throw new UnsupportedOperationException("_signalHeadHandle is null");
206
207        String[] keys = signalHead.getValidStateKeys();
208        for (int i=0; i < keys.length; i++) {
209            if (name.equals(keys[i])) return signalHead.getValidStates()[i];
210        }
211
212        throw new IllegalArgumentException("Appearance "+name+" is not valid for signal head "+signalHead.getSystemName());
213    }
214
215    private int getNewAppearance(ConditionalNG conditionalNG, SignalHead signalHead)
216            throws JmriException {
217
218        switch (_appearanceAddressing) {
219            case Direct:
220                return _signalHeadAppearance;
221
222            case Reference:
223                return getAppearanceFromName(ReferenceUtil.getReference(
224                        conditionalNG.getSymbolTable(), _appearanceReference), signalHead);
225
226            case LocalVariable:
227                SymbolTable symbolTable = conditionalNG.getSymbolTable();
228                return getAppearanceFromName(TypeConversionUtil
229                        .convertToString(symbolTable.getValue(_appearanceLocalVariable), false), signalHead);
230
231            case Formula:
232                return _appearanceExpressionNode != null
233                        ? getAppearanceFromName(TypeConversionUtil.convertToString(
234                                _appearanceExpressionNode.calculate(
235                                        conditionalNG.getSymbolTable()), false), signalHead)
236                        : -1;
237
238            default:
239                throw new IllegalArgumentException("invalid _aspectAddressing state: " + _appearanceAddressing.name());
240        }
241    }
242
243    private QueryType getQuery(ConditionalNG conditionalNG) throws JmriException {
244
245        String oper = "";
246        try {
247            switch (_queryAddressing) {
248                case Direct:
249                    return _queryType;
250
251                case Reference:
252                    oper = ReferenceUtil.getReference(
253                            conditionalNG.getSymbolTable(), _queryReference);
254                    return QueryType.valueOf(oper);
255
256                case LocalVariable:
257                    SymbolTable symbolTable =
258                            conditionalNG.getSymbolTable();
259                    oper = TypeConversionUtil
260                            .convertToString(symbolTable.getValue(_queryLocalVariable), false);
261                    return QueryType.valueOf(oper);
262
263                case Formula:
264                    if (_appearanceExpressionNode != null) {
265                        oper = TypeConversionUtil.convertToString(
266                                _queryExpressionNode.calculate(
267                                        conditionalNG.getSymbolTable()), false);
268                        return QueryType.valueOf(oper);
269                    } else {
270                        return null;
271                    }
272                default:
273                    throw new IllegalArgumentException("invalid _addressing state: " + _queryAddressing.name());
274            }
275        } catch (IllegalArgumentException e) {
276            throw new JmriException("Unknown query: "+oper, e);
277        }
278    }
279
280    /** {@inheritDoc} */
281    @Override
282    public boolean evaluate() throws JmriException {
283        final ConditionalNG conditionalNG = getConditionalNG();
284
285        SignalHead signalHead = _selectNamedBean.evaluateNamedBean(conditionalNG);
286
287        if (signalHead == null) return false;
288
289        QueryType query = getQuery(conditionalNG);
290
291        boolean result = false;
292        int queryAppearance;
293
294        switch (query) {
295            case Appearance:
296                queryAppearance = getNewAppearance(conditionalNG, signalHead);
297                if (queryAppearance != -1) {
298                    result = signalHead.getAppearance() == queryAppearance;
299                }
300                break;
301            case NotAppearance:
302                queryAppearance = getNewAppearance(conditionalNG, signalHead);
303                if (queryAppearance != -1) {
304                    result = ! (signalHead.getAppearance() == queryAppearance);
305                }
306                break;
307            case Lit:
308                result = signalHead.getLit();
309                break;
310            case NotLit:
311                result = ! signalHead.getLit();
312                break;
313            case Held:
314                result = signalHead.getHeld();
315                break;
316            case NotHeld:
317                result = ! signalHead.getHeld();
318                break;
319            default:
320                throw new RuntimeException("Unknown enum: "+_queryType.name());
321        }
322
323        return result;
324    }
325
326    @Override
327    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
328        throw new UnsupportedOperationException("Not supported.");
329    }
330
331    @Override
332    public int getChildCount() {
333        return 0;
334    }
335
336    @Override
337    public String getShortDescription(Locale locale) {
338        return Bundle.getMessage(locale, "SignalHead_Short");
339    }
340
341    @Override
342    public String getLongDescription(Locale locale) {
343        String namedBean = _selectNamedBean.getDescription(locale);
344        String query;
345        String appearance;
346
347        switch (_queryAddressing) {
348            case Direct:
349                query = Bundle.getMessage(locale, "AddressByDirect", _queryType._text);
350                break;
351
352            case Reference:
353                query = Bundle.getMessage(locale, "AddressByReference", _queryReference);
354                break;
355
356            case LocalVariable:
357                query = Bundle.getMessage(locale, "AddressByLocalVariable", _queryLocalVariable);
358                break;
359
360            case Formula:
361                query = Bundle.getMessage(locale, "AddressByFormula", _queryFormula);
362                break;
363
364            default:
365                throw new IllegalArgumentException("invalid _queryAddressing state: " + _queryAddressing.name());
366        }
367
368        switch (_appearanceAddressing) {
369            case Direct:
370                SignalHead signalHead = null;
371                if (_selectNamedBean.getAddressing() == NamedBeanAddressing.Direct) {
372                    if (_selectNamedBean.getNamedBean() != null) {
373                        signalHead = _selectNamedBean.getNamedBeanIfDirectAddressing();
374                    }
375                } else {
376                    if (_selectExampleNamedBean.getNamedBean() != null) {
377                        signalHead = _selectExampleNamedBean.getNamedBeanIfDirectAddressing();
378                    }
379                }
380                String a = "";
381                if (signalHead != null) {
382                    a = signalHead.getAppearanceName(_signalHeadAppearance);
383                }
384                appearance = Bundle.getMessage(locale, "AddressByDirect", a);
385                break;
386
387            case Reference:
388                appearance = Bundle.getMessage(locale, "AddressByReference", _appearanceReference);
389                break;
390
391            case LocalVariable:
392                appearance = Bundle.getMessage(locale, "AddressByLocalVariable", _appearanceLocalVariable);
393                break;
394
395            case Formula:
396                appearance = Bundle.getMessage(locale, "AddressByFormula", _appearanceFormula);
397                break;
398
399            default:
400                throw new IllegalArgumentException("invalid _stateAddressing state: " + _appearanceAddressing.name());
401        }
402
403        if (_queryAddressing == NamedBeanAddressing.Direct) {
404            if (_queryType == QueryType.Appearance) {
405                return Bundle.getMessage(locale, "SignalHead_LongAppearance", namedBean, appearance);
406            } else if (_queryType == QueryType.NotAppearance) {
407                return Bundle.getMessage(locale, "SignalHead_LongNotAppearance", namedBean, appearance);
408            } else {
409                return Bundle.getMessage(locale, "SignalHead_Long", namedBean, query);
410            }
411        } else {
412            return Bundle.getMessage(locale, "SignalHead_LongUnknownOper", namedBean, query, appearance);
413        }
414    }
415
416    /** {@inheritDoc} */
417    @Override
418    public void setup() {
419        // Do nothing
420    }
421
422    /** {@inheritDoc} */
423    @Override
424    public void registerListenersForThisClass() {
425        SignalHead signalHead = _selectNamedBean.getNamedBeanIfDirectAddressing();
426
427       if (!_listenersAreRegistered && (signalHead != null)) {
428            switch (_queryType) {
429                case Appearance:
430                case NotAppearance:
431                    signalHead.addPropertyChangeListener("Appearance", this);
432                    break;
433
434                case Lit:
435                case NotLit:
436                    signalHead.addPropertyChangeListener("Lit", this);
437                    break;
438
439                case Held:
440                case NotHeld:
441                    signalHead.addPropertyChangeListener("Held", this);
442                    break;
443
444                default:
445                    throw new RuntimeException("Unknown enum: "+_queryType.name());
446            }
447            _selectNamedBean.registerListeners();
448            _listenersAreRegistered = true;
449        }
450    }
451
452    /** {@inheritDoc} */
453    @Override
454    public void unregisterListenersForThisClass() {
455        SignalHead signalHead = _selectNamedBean.getNamedBeanIfDirectAddressing();
456
457       if (_listenersAreRegistered && (signalHead != null)) {
458            switch (_queryType) {
459                case Appearance:
460                case NotAppearance:
461                    signalHead.removePropertyChangeListener("Appearance", this);
462                    break;
463
464                case Lit:
465                case NotLit:
466                    signalHead.removePropertyChangeListener("Lit", this);
467                    break;
468
469                case Held:
470                case NotHeld:
471                    signalHead.removePropertyChangeListener("Held", this);
472                    break;
473
474                default:
475                    throw new RuntimeException("Unknown enum: "+_queryType.name());
476            }
477            _selectNamedBean.unregisterListeners();
478            _listenersAreRegistered = false;
479        }
480    }
481
482    /** {@inheritDoc} */
483    @Override
484    public void propertyChange(PropertyChangeEvent evt) {
485        getConditionalNG().execute();
486    }
487
488    /** {@inheritDoc} */
489    @Override
490    public void disposeMe() {
491    }
492
493
494
495    public enum QueryType {
496        Appearance(Bundle.getMessage("SignalHeadQueryType_Appearance")),
497        NotAppearance(Bundle.getMessage("SignalHeadQueryType_NotAppearance")),
498        Lit(Bundle.getMessage("SignalHeadQueryType_Lit")),
499        NotLit(Bundle.getMessage("SignalHeadQueryType_NotLit")),
500        Held(Bundle.getMessage("SignalHeadQueryType_Held")),
501        NotHeld(Bundle.getMessage("SignalHeadQueryType_NotHeld"));
502
503        private final String _text;
504
505        private QueryType(String text) {
506            this._text = text;
507        }
508
509        @Override
510        public String toString() {
511            return _text;
512        }
513
514    }
515
516    /** {@inheritDoc} */
517    @Override
518    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
519        log.debug("getUsageReport :: ExpressionSignalHead: bean = {}, report = {}", cdl, report);
520        _selectNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Action);
521        _selectExampleNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Action);
522    }
523
524    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ExpressionSignalHead.class);
525
526}