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.parser.*;
013import jmri.jmrit.logixng.util.parser.ExpressionNode;
014import jmri.jmrit.logixng.util.LogixNG_SelectTable;
015import jmri.util.ThreadingUtil;
016import jmri.util.TypeConversionUtil;
017
018/**
019 * This action sets the current report of a Reporter.
020 *
021 * @author Daniel Bergqvist Copyright 2024
022 */
023public class ActionSetReporter extends AbstractDigitalAction
024        implements PropertyChangeListener {
025
026    private final LogixNG_SelectNamedBean<Reporter> _selectNamedBean =
027            new LogixNG_SelectNamedBean<>(
028                    this, Reporter.class, InstanceManager.getDefault(ReporterManager.class), this);
029    private final LogixNG_SelectNamedBean<Memory> _selectOtherMemoryNamedBean =
030            new LogixNG_SelectNamedBean<>(
031                    this, Memory.class, InstanceManager.getDefault(MemoryManager.class), this);
032    private ReporterOperation _reporterOperation = ReporterOperation.SetToString;
033    private String _otherConstantValue = "";
034    private String _otherLocalVariable = "";
035    private String _otherFormula = "";
036    private ExpressionNode _otherExpressionNode;
037    private boolean _provideAnIdTag = false;
038
039    private final LogixNG_SelectTable _selectTable =
040            new LogixNG_SelectTable(this, () -> {return _reporterOperation == ReporterOperation.CopyTableCellToReporter;});
041
042
043    public ActionSetReporter(String sys, String user)
044            throws BadUserNameException, BadSystemNameException {
045        super(sys, user);
046    }
047
048    @Override
049    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException {
050        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
051        String sysName = systemNames.get(getSystemName());
052        String userName = userNames.get(getSystemName());
053        if (sysName == null) sysName = manager.getAutoSystemName();
054        ActionSetReporter copy = new ActionSetReporter(sysName, userName);
055        copy.setComment(getComment());
056        _selectNamedBean.copy(copy._selectNamedBean);
057        _selectOtherMemoryNamedBean.copy(copy._selectOtherMemoryNamedBean);
058        copy.setMemoryOperation(_reporterOperation);
059        copy.setOtherConstantValue(_otherConstantValue);
060        copy.setOtherLocalVariable(_otherLocalVariable);
061        copy.setOtherFormula(_otherFormula);
062        copy.setProvideAnIdTag(_provideAnIdTag);
063        _selectTable.copy(copy._selectTable);
064        return manager.registerAction(copy);
065    }
066
067    public LogixNG_SelectNamedBean<Reporter> getSelectNamedBean() {
068        return _selectNamedBean;
069    }
070
071    public LogixNG_SelectNamedBean<Memory> getSelectOtherMemoryNamedBean() {
072        return _selectOtherMemoryNamedBean;
073    }
074
075    public void setMemoryOperation(ReporterOperation state) throws ParserException {
076        _reporterOperation = state;
077        parseOtherFormula();
078    }
079
080    public ReporterOperation getReporterOperation() {
081        return _reporterOperation;
082    }
083
084    // Constant tab
085    public void setOtherConstantValue(String constantValue) {
086        _otherConstantValue = constantValue;
087    }
088
089    public String getConstantValue() {
090        return _otherConstantValue;
091    }
092
093    public LogixNG_SelectTable getSelectTable() {
094        return _selectTable;
095    }
096
097    public void setProvideAnIdTag(boolean createAnIdTag) {
098        this._provideAnIdTag = createAnIdTag;
099    }
100
101    public boolean isProvideAnIdTag() {
102        return _provideAnIdTag;
103    }
104
105    // Variable tab
106    public void setOtherLocalVariable(@Nonnull String localVariable) {
107        assertListenersAreNotRegistered(log, "setOtherLocalVariable");
108        _otherLocalVariable = localVariable;
109    }
110
111    public String getOtherLocalVariable() {
112        return _otherLocalVariable;
113    }
114
115    // Formula tab
116    public void setOtherFormula(String formula) throws ParserException {
117        _otherFormula = formula;
118        parseOtherFormula();
119    }
120
121    public String getOtherFormula() {
122        return _otherFormula;
123    }
124
125    private void parseOtherFormula() throws ParserException {
126        if (_reporterOperation == ReporterOperation.CalculateFormula) {
127            Map<String, Variable> variables = new HashMap<>();
128            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
129            _otherExpressionNode = parser.parseExpression(_otherFormula);
130        } else {
131            _otherExpressionNode = null;
132        }
133    }
134
135    /** {@inheritDoc} */
136    @Override
137    public Category getCategory() {
138        return Category.ITEM;
139    }
140
141    /** {@inheritDoc} */
142    @Override
143    public void execute() throws JmriException {
144
145        final ConditionalNG conditionalNG = getConditionalNG();
146
147        Reporter reporter = _selectNamedBean.evaluateNamedBean(conditionalNG);
148
149        if (reporter == null) {
150//            log.warn("memory is null");
151            return;
152        }
153
154        AtomicReference<JmriException> ref = new AtomicReference<>();
155
156        ThreadingUtil.runOnLayoutWithJmriException(() -> {
157
158            Object report;
159
160            switch (_reporterOperation) {
161                case SetToNull:
162                    report = null;
163                    break;
164
165                case SetToString:
166                    report = _otherConstantValue;
167                    break;
168
169                case CopyTableCellToReporter:
170                    report = _selectTable.evaluateTableData(conditionalNG);
171                    break;
172
173                case CopyVariableToReporter:
174                    report = conditionalNG.getSymbolTable()
175                            .getValue(_otherLocalVariable);
176                    break;
177
178                case CopyMemoryToReporter:
179                    Memory otherMemory = _selectOtherMemoryNamedBean.evaluateNamedBean(conditionalNG);
180                    if (otherMemory != null) {
181                        report = otherMemory.getValue();
182                    } else {
183                        log.warn("setReporter should copy memory to reporter but memory is null");
184                        return;
185                    }
186                    break;
187
188                case CalculateFormula:
189                    if (_otherFormula.isEmpty()) {
190                        report = null;
191                    } else {
192                        try {
193                            if (_otherExpressionNode == null) {
194                                return;
195                            }
196                            report = _otherExpressionNode.calculate(
197                                    conditionalNG.getSymbolTable());
198                        } catch (JmriException e) {
199                            ref.set(e);
200                            return;
201                        }
202                    }
203                    break;
204
205                default:
206                    throw new IllegalArgumentException("_reporterOperation has invalid value: {}" + _reporterOperation.name());
207            }
208
209            if (_provideAnIdTag) {
210                if (report == null) {
211                    throw new IllegalArgumentException("report is null. Can't provide an IdTag");
212                }
213                IdTag idTag;
214                if (report instanceof IdTag) {
215                    idTag = (IdTag)report;
216                } else {
217                    String name = TypeConversionUtil.convertToString(report, false);
218                    idTag = InstanceManager.getDefault(IdTagManager.class).provideIdTag(name);
219                    report = idTag;
220                }
221                idTag.setWhereLastSeen(reporter);
222            }
223
224            reporter.setReport(report);
225        });
226
227        if (ref.get() != null) throw ref.get();
228    }
229
230    @Override
231    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
232        throw new UnsupportedOperationException("Not supported.");
233    }
234
235    @Override
236    public int getChildCount() {
237        return 0;
238    }
239
240    @Override
241    public String getShortDescription(Locale locale) {
242        return Bundle.getMessage(locale, "ActionSetReporter_Short");
243    }
244
245    @Override
246    public String getLongDescription(Locale locale) {
247        String namedBean = _selectNamedBean.getDescription(locale);
248
249        String copyToMemoryName = _selectOtherMemoryNamedBean.getDescription(locale);
250
251        switch (_reporterOperation) {
252            case SetToNull:
253                return Bundle.getMessage(locale, "ActionSetReporter_Long_Null", namedBean);
254            case SetToString:
255                return Bundle.getMessage(locale, "ActionSetReporter_Long_Value", namedBean, _otherConstantValue);
256            case CopyVariableToReporter:
257                return Bundle.getMessage(locale, "ActionSetReporter_Long_CopyVariableToReporter", namedBean, _otherLocalVariable);
258            case CopyMemoryToReporter:
259                return Bundle.getMessage(locale, "ActionSetReporter_Long_CopyMemoryToReporter", namedBean, copyToMemoryName);
260            case CopyTableCellToReporter:
261                String tableName = _selectTable.getTableNameDescription(locale);
262                String rowName = _selectTable.getTableRowDescription(locale);
263                String columnName = _selectTable.getTableColumnDescription(locale);
264                return Bundle.getMessage(locale, "ActionSetReporter_Long_CopyTableCellToReporter", namedBean, tableName, rowName, columnName);
265            case CalculateFormula:
266                return Bundle.getMessage(locale, "ActionSetReporter_Long_Formula", namedBean, _otherFormula);
267            default:
268                throw new IllegalArgumentException("_memoryOperation has invalid value: " + _reporterOperation.name());
269        }
270    }
271
272    /** {@inheritDoc} */
273    @Override
274    public void setup() {
275        // Do nothing
276    }
277
278    /** {@inheritDoc} */
279    @Override
280    public void registerListenersForThisClass() {
281        if (!_listenersAreRegistered) {
282            _selectNamedBean.registerListeners();
283            _selectOtherMemoryNamedBean.addPropertyChangeListener("value", this);
284            _listenersAreRegistered = true;
285        }
286    }
287
288    /** {@inheritDoc} */
289    @Override
290    public void unregisterListenersForThisClass() {
291        if (_listenersAreRegistered) {
292            _selectNamedBean.unregisterListeners();
293            _selectOtherMemoryNamedBean.removePropertyChangeListener("value", this);
294            _listenersAreRegistered = false;
295        }
296    }
297
298    /** {@inheritDoc} */
299    @Override
300    public void propertyChange(PropertyChangeEvent evt) {
301        getConditionalNG().execute();
302    }
303
304    /** {@inheritDoc} */
305    @Override
306    public void disposeMe() {
307    }
308
309
310    public enum ReporterOperation {
311        SetToNull(Bundle.getMessage("ActionSetReporter_ReporterOperation_SetToNull")),
312        SetToString(Bundle.getMessage("ActionSetReporter_ReporterOperation_SetToString")),
313        CopyVariableToReporter(Bundle.getMessage("ActionSetReporter_ReporterOperation_CopyVariableToReporter")),
314        CopyMemoryToReporter(Bundle.getMessage("ActionSetReporter_ReporterOperation_CopyMemoryToReporter")),
315        CopyTableCellToReporter(Bundle.getMessage("ActionSetReporter_ReporterOperation_CopyTableCellToReporter")),
316        CalculateFormula(Bundle.getMessage("ActionSetReporter_ReporterOperation_CalculateFormula"));
317
318        private final String _text;
319
320        private ReporterOperation(String text) {
321            this._text = text;
322        }
323
324        @Override
325        public String toString() {
326            return _text;
327        }
328
329    }
330
331    /** {@inheritDoc} */
332    @Override
333    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
334        _selectNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Action);
335        _selectOtherMemoryNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Action);
336    }
337
338    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionSetReporter.class);
339
340}