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