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