001package jmri.jmrit.logixng.actions;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.util.*;
006
007import javax.annotation.Nonnull;
008
009import jmri.*;
010import jmri.jmrit.logix.OBlock;
011import jmri.jmrit.logix.OBlockManager;
012import jmri.jmrit.logix.Warrant;
013import jmri.jmrit.logixng.*;
014import jmri.jmrit.logixng.util.*;
015import jmri.jmrit.logixng.util.parser.*;
016import jmri.jmrit.logixng.util.parser.ExpressionNode;
017import jmri.jmrit.logixng.util.parser.RecursiveDescentParser;
018import jmri.util.ThreadingUtil;
019import jmri.util.TypeConversionUtil;
020
021/**
022 * This action triggers an OBlock.
023 *
024 * @author Daniel Bergqvist Copyright 2021
025 * @author Dave Sand Copyright 2021
026 */
027public class ActionOBlock extends AbstractDigitalAction
028        implements PropertyChangeListener {
029
030    private final LogixNG_SelectNamedBean<OBlock> _selectNamedBean =
031            new LogixNG_SelectNamedBean<>(
032                    this, OBlock.class, InstanceManager.getDefault(OBlockManager.class), this);
033
034    private final LogixNG_SelectEnum<DirectOperation> _selectEnum =
035            new LogixNG_SelectEnum<>(this, DirectOperation.values(), DirectOperation.Deallocate, this);
036
037    private final LogixNG_SelectNamedBean<Memory> _selectMemoryNamedBean =
038            new LogixNG_SelectNamedBean<>(
039                    this, Memory.class, InstanceManager.getDefault(MemoryManager.class), this);
040
041    private NamedBeanAddressing _dataAddressing = NamedBeanAddressing.Direct;
042    private String _dataReference = "";
043    private String _dataLocalVariable = "";
044    private String _dataFormula = "";
045    private ExpressionNode _dataExpressionNode;
046
047    private String _oblockValue = "";
048
049    public ActionOBlock(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 ParserException {
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        ActionOBlock copy = new ActionOBlock(sysName, userName);
061        copy.setComment(getComment());
062        _selectNamedBean.copy(copy._selectNamedBean);
063        _selectMemoryNamedBean.copy(copy._selectMemoryNamedBean);
064        _selectEnum.copy(copy._selectEnum);
065
066        copy.setDataAddressing(_dataAddressing);
067        copy.setDataReference(_dataReference);
068        copy.setDataLocalVariable(_dataLocalVariable);
069        copy.setDataFormula(_dataFormula);
070        copy.setOBlockValue(_oblockValue);
071
072        return manager.registerAction(copy);
073    }
074
075    public LogixNG_SelectNamedBean<OBlock> getSelectNamedBean() {
076        return _selectNamedBean;
077    }
078
079    public LogixNG_SelectNamedBean<Memory> getSelectMemoryNamedBean() {
080        return _selectMemoryNamedBean;
081    }
082
083    public LogixNG_SelectEnum<DirectOperation> getSelectEnum() {
084        return _selectEnum;
085    }
086
087     public void setDataAddressing(NamedBeanAddressing addressing) throws ParserException {
088        _dataAddressing = addressing;
089        parseDataFormula();
090    }
091
092    public NamedBeanAddressing getDataAddressing() {
093        return _dataAddressing;
094    }
095
096    public void setDataReference(@Nonnull String reference) {
097        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
098            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
099        }
100        _dataReference = reference;
101    }
102
103    public String getDataReference() {
104        return _dataReference;
105    }
106
107    public void setDataLocalVariable(@Nonnull String localVariable) {
108        _dataLocalVariable = localVariable;
109    }
110
111    public String getDataLocalVariable() {
112        return _dataLocalVariable;
113    }
114
115    public void setDataFormula(@Nonnull String formula) throws ParserException {
116        _dataFormula = formula;
117        parseDataFormula();
118    }
119
120    public String getDataFormula() {
121        return _dataFormula;
122    }
123
124    private void parseDataFormula() throws ParserException {
125        if (_dataAddressing == NamedBeanAddressing.Formula) {
126            Map<String, Variable> variables = new HashMap<>();
127
128            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
129            _dataExpressionNode = parser.parseExpression(_dataFormula);
130        } else {
131            _dataExpressionNode = null;
132        }
133    }
134
135    public void setOBlockValue(@Nonnull String value) {
136        _oblockValue = value;
137    }
138
139    public String getOBlockValue() {
140        return _oblockValue;
141    }
142
143    /** {@inheritDoc} */
144    @Override
145    public Category getCategory() {
146        return Category.ITEM;
147    }
148
149    private String getNewData(ConditionalNG conditionalNG) throws JmriException {
150
151        switch (_dataAddressing) {
152            case Direct:
153                return _oblockValue;
154
155            case Reference:
156                return ReferenceUtil.getReference(
157                        conditionalNG.getSymbolTable(), _dataReference);
158
159            case LocalVariable:
160                SymbolTable symbolTable = conditionalNG.getSymbolTable();
161                return TypeConversionUtil
162                        .convertToString(symbolTable.getValue(_dataLocalVariable), false);
163
164            case Formula:
165                return _dataExpressionNode != null
166                        ? TypeConversionUtil.convertToString(
167                                _dataExpressionNode.calculate(
168                                        conditionalNG.getSymbolTable()), false)
169                        : null;
170
171            default:
172                throw new IllegalArgumentException("invalid _addressing state: " + _dataAddressing.name());
173        }
174    }
175
176
177    /** {@inheritDoc} */
178    @Override
179    public void execute() throws JmriException {
180        final ConditionalNG conditionalNG = getConditionalNG();
181
182        OBlock oblock = _selectNamedBean.evaluateNamedBean(conditionalNG);
183
184        if (oblock == null) return;
185
186        DirectOperation oper = _selectEnum.evaluateEnum(conditionalNG);
187
188        // Variables used in lambda must be effectively final
189        DirectOperation theOper = oper;
190
191        ThreadingUtil.runOnLayoutWithJmriException(() -> {
192            switch (theOper) {
193                case Deallocate:
194                    oblock.deAllocate(null);
195                    break;
196                case SetValue:
197                    oblock.setValue(getNewData(conditionalNG));
198                    break;
199                case SetError:
200                    oblock.setError(true);
201                    break;
202                case ClearError:
203                    oblock.setError(false);
204                    break;
205                case SetOutOfService:
206                    oblock.setOutOfService(true);
207                    break;
208                case ClearOutOfService:
209                    oblock.setOutOfService(false);
210                    break;
211                case GetBlockWarrant:
212                    Memory memory = _selectMemoryNamedBean.evaluateNamedBean(conditionalNG);
213                    if (memory != null) {
214                        Warrant w = oblock.getWarrant();
215                        if (w != null) {
216                            memory.setValue(w.getDisplayName());
217                        } else {
218                            memory.setValue("unallocated");
219                        }
220                    } else {
221                        throw new JmriException("Memory for GetBlockWarrant is null for oblock - " + oblock.getDisplayName());  // NOI18N
222                    }
223                    break;
224                case GetBlockValue:
225                    memory = _selectMemoryNamedBean.evaluateNamedBean(conditionalNG);
226                    if (memory != null) {
227                        Object obj = oblock.getValue();
228                        if (obj instanceof String) {
229                            memory.setValue(obj);
230                        } else {
231                            memory.setValue("");
232                        }
233                    } else {
234                        throw new JmriException("Memory for GetBlockValue is null for oblock - " + oblock.getDisplayName());  // NOI18N
235                    }
236                    break;
237                default:
238                    throw new IllegalArgumentException("invalid oper state: " + theOper.name());
239            }
240        });
241    }
242
243    @Override
244    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
245        throw new UnsupportedOperationException("Not supported.");
246    }
247
248    @Override
249    public int getChildCount() {
250        return 0;
251    }
252
253    @Override
254    public String getShortDescription(Locale locale) {
255        return Bundle.getMessage(locale, "ActionOBlock_Short");
256    }
257
258    @Override
259    public String getLongDescription(Locale locale) {
260        String namedBean = _selectNamedBean.getDescription(locale);
261        String state = _selectEnum.getDescription(locale);
262        String getLocationMemory = _selectMemoryNamedBean.getDescription(locale);
263
264        if (_selectEnum.getAddressing() == NamedBeanAddressing.Direct) {
265            if (_selectEnum.getEnum() != null) {
266                switch (_selectEnum.getEnum()) {
267                    case SetValue:
268                        return getLongDataDescription(locale, "ActionOBlock_Long_Value", namedBean, _oblockValue);
269                    case GetBlockWarrant:
270                        return getLongDataDescription(locale, "ActionOBlock_Long_GetWarrant", namedBean, getLocationMemory);
271                    case GetBlockValue:
272                        return getLongDataDescription(locale, "ActionOBlock_Long_GetTrain", namedBean, getLocationMemory);
273                    default:
274                        // Fall thru and handle it in the end of the method
275                }
276            }
277        }
278
279        return Bundle.getMessage(locale, "ActionOBlock_Long", namedBean, state);
280    }
281
282    private String getLongDataDescription(Locale locale, String bundleKey, String namedBean, String value) {
283        switch (_dataAddressing) {
284            case Direct:
285                return Bundle.getMessage(locale, bundleKey, namedBean, value);
286            case Reference:
287                return Bundle.getMessage(locale, bundleKey, namedBean, Bundle.getMessage("AddressByReference", _dataReference));
288            case LocalVariable:
289                return Bundle.getMessage(locale, bundleKey, namedBean, Bundle.getMessage("AddressByLocalVariable", _dataLocalVariable));
290            case Formula:
291                return Bundle.getMessage(locale, bundleKey, namedBean, Bundle.getMessage("AddressByFormula", _dataFormula));
292            default:
293                throw new IllegalArgumentException("invalid _dataAddressing state: " + _dataAddressing.name());
294        }
295    }
296
297    /** {@inheritDoc} */
298    @Override
299    public void setup() {
300        // Do nothing
301    }
302
303    /** {@inheritDoc} */
304    @Override
305    public void registerListenersForThisClass() {
306        _selectNamedBean.registerListeners();
307        _selectEnum.registerListeners();
308        _selectMemoryNamedBean.addPropertyChangeListener("value", this);
309    }
310
311    /** {@inheritDoc} */
312    @Override
313    public void unregisterListenersForThisClass() {
314        _selectNamedBean.unregisterListeners();
315        _selectEnum.unregisterListeners();
316        _selectMemoryNamedBean.removePropertyChangeListener("value", this);
317    }
318
319    /** {@inheritDoc} */
320    @Override
321    public void disposeMe() {
322    }
323
324    public enum DirectOperation {
325        Deallocate(Bundle.getMessage("ActionOBlock_Deallocate")),
326        SetValue(Bundle.getMessage("ActionOBlock_SetValue")),
327        SetError(Bundle.getMessage("ActionOBlock_SetError")),
328        ClearError(Bundle.getMessage("ActionOBlock_ClearError")),
329        SetOutOfService(Bundle.getMessage("ActionOBlock_SetOutOfService")),
330        ClearOutOfService(Bundle.getMessage("ActionOBlock_ClearOutOfService")),
331        GetBlockWarrant(Bundle.getMessage("ActionOBlock_GetBlockWarrant")),
332        GetBlockValue(Bundle.getMessage("ActionOBlock_GetValue"));
333
334        private final String _text;
335
336        private DirectOperation(String text) {
337            this._text = text;
338        }
339
340        @Override
341        public String toString() {
342            return _text;
343        }
344
345    }
346
347    /** {@inheritDoc} */
348    @Override
349    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
350        _selectNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Action);
351    }
352
353    /** {@inheritDoc} */
354    @Override
355    public void propertyChange(PropertyChangeEvent evt) {
356        getConditionalNG().execute();
357    }
358
359//    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionOBlock.class);
360
361}