001package jmri.jmrit.logixng.actions;
002
003import java.beans.*;
004import java.util.*;
005
006import javax.annotation.Nonnull;
007
008import jmri.*;
009import jmri.jmrit.logixng.*;
010import jmri.jmrit.logixng.util.LogixNG_SelectNamedBean;
011import jmri.jmrit.logixng.util.parser.ParserException;
012
013/**
014 * This action finds a table row or column.
015 *
016 * @author Daniel Bergqvist Copyright 2022
017 */
018public class ActionFindTableRowOrColumn extends AbstractDigitalAction
019        implements PropertyChangeListener, VetoableChangeListener {
020
021    private final LogixNG_SelectNamedBean<NamedTable> _selectNamedBean =
022            new LogixNG_SelectNamedBean<>(
023                    this, NamedTable.class, InstanceManager.getDefault(NamedTableManager.class), this);
024    private TableRowOrColumn _tableRowOrColumn = TableRowOrColumn.Row;
025    private String _rowOrColumnName = "";
026    private boolean _includeCellsWithoutHeader = false;
027    private String _localVariableNamedBean;
028    private String _localVariableRow;
029
030    public ActionFindTableRowOrColumn(String sys, String user)
031            throws BadUserNameException, BadSystemNameException {
032        super(sys, user);
033        _selectNamedBean.setOnlyDirectAddressingAllowed();
034    }
035
036    @Override
037    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException {
038        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
039        String sysName = systemNames.get(getSystemName());
040        String userName = userNames.get(getSystemName());
041        if (sysName == null) sysName = manager.getAutoSystemName();
042        ActionFindTableRowOrColumn copy = new ActionFindTableRowOrColumn(sysName, userName);
043        copy.setComment(getComment());
044        _selectNamedBean.copy(copy._selectNamedBean);
045        copy.setTableRowOrColumn(_tableRowOrColumn);
046        copy.setRowOrColumnName(_rowOrColumnName);
047        copy.setIncludeCellsWithoutHeader(_includeCellsWithoutHeader);
048
049        copy.setLocalVariableNamedBean(_localVariableNamedBean);
050        copy.setLocalVariableRow(_localVariableRow);
051
052        return manager.registerAction(copy);
053    }
054
055    public LogixNG_SelectNamedBean<NamedTable> getSelectNamedBean() {
056        return _selectNamedBean;
057    }
058
059    /**
060     * Get tableRowOrColumn.
061     * @return tableRowOrColumn
062     */
063    public TableRowOrColumn getTableRowOrColumn() {
064        return _tableRowOrColumn;
065    }
066
067    /**
068     * Set tableRowOrColumn.
069     * @param tableRowOrColumn tableRowOrColumn
070     */
071    public void setTableRowOrColumn(@Nonnull TableRowOrColumn tableRowOrColumn) {
072        _tableRowOrColumn = tableRowOrColumn;
073    }
074
075    /**
076     * Get name of row or column
077     * @return name of row or column
078     */
079    public String getRowOrColumnName() {
080        return _rowOrColumnName;
081    }
082
083    /**
084     * Set name of row or column
085     * @param rowOrColumnName name of row or column
086     */
087    public void setRowOrColumnName(@Nonnull String rowOrColumnName) {
088        if (rowOrColumnName == null) throw new IllegalArgumentException("Row/column name is null");
089        _rowOrColumnName = rowOrColumnName;
090    }
091
092    /**
093     * Set whenever to include cells that doesn't have a header.
094     * Cells without headers can be used to use some cells in the table
095     * as comments.
096     * @return true if include cells that doesn't have a header, false otherwise
097     */
098    public boolean getIncludeCellsWithoutHeader() {
099        return _includeCellsWithoutHeader;
100    }
101
102    /**
103     * Set whenever to include cells that doesn't have a header.
104     * Cells without headers can be used to use some cells in the table
105     * as comments.
106     * @param includeCellsWithoutHeader true if include rows/columns that
107     *                                  doesn't have a header, false otherwise
108     */
109    public void setIncludeCellsWithoutHeader(boolean includeCellsWithoutHeader) {
110        _includeCellsWithoutHeader = includeCellsWithoutHeader;
111    }
112
113    public void setLocalVariableNamedBean(String localVariableNamedBean) {
114        if ((localVariableNamedBean != null) && (!localVariableNamedBean.isEmpty())) {
115            this._localVariableNamedBean = localVariableNamedBean;
116        } else {
117            this._localVariableNamedBean = null;
118        }
119    }
120
121    public String getLocalVariableNamedBean() {
122        return _localVariableNamedBean;
123    }
124
125    public void setLocalVariableRow(String localVariableNewValue) {
126        if ((localVariableNewValue != null) && (!localVariableNewValue.isEmpty())) {
127            this._localVariableRow = localVariableNewValue;
128        } else {
129            this._localVariableRow = null;
130        }
131    }
132
133    public String getLocalVariableRow() {
134        return _localVariableRow;
135    }
136
137    /** {@inheritDoc} */
138    @Override
139    public Category getCategory() {
140        return Category.OTHER;
141    }
142
143    private Map<String, Object> getRow(Object value) throws JmriException {
144        if (_selectNamedBean.getNamedBean() == null) {
145            log.error("No table name is given");
146            return null;    // No row found
147        }
148        if (_rowOrColumnName.isEmpty()) {
149            log.error("rowOrColumnName is empty string");
150            return null;    // No row found
151        }
152
153        NamedTable table = _selectNamedBean.evaluateNamedBean(getConditionalNG());
154
155        if (_tableRowOrColumn == TableRowOrColumn.Row) {
156            int row = table.getRowNumber(_rowOrColumnName);
157            for (int column=1; column <= table.numColumns(); column++) {
158                // If the header is null or empty, treat the row as a comment
159                // unless _includeRowColumnWithoutHeader is true
160                Object header = table.getCell(0, column);
161//                System.out.format("Row header: %s%n", header);
162                if (_includeCellsWithoutHeader
163                        || ((header != null) && (!header.toString().isEmpty()))) {
164                    Object cell = table.getCell(row, column);
165                    if ((cell != null) && (cell.equals(value))) {
166                        Map<String, Object> rowData = new HashMap<>();
167
168                        for (int rowIndex=1; rowIndex <= table.numRows(); rowIndex++) {
169                            Object subHeader = table.getCell(rowIndex, 0);
170                            if ((subHeader != null) && (!subHeader.toString().isEmpty())) {
171                                rowData.put(subHeader.toString(), table.getCell(rowIndex, column));
172                            }
173                        }
174                        return rowData;
175                    }
176                }
177            }
178        } else {
179            int column = table.getColumnNumber(_rowOrColumnName);
180            for (int row=1; row <= table.numRows(); row++) {
181                // If the header is null or empty, treat the row as a comment
182                // unless _includeRowColumnWithoutHeader is true
183                Object header = table.getCell(row, 0);
184//                System.out.format("Column header: %s%n", header);
185                if (_includeCellsWithoutHeader
186                        || ((header != null) && (!header.toString().isEmpty()))) {
187                    Object cell = table.getCell(row, column);
188                    if ((cell != null) && (cell.equals(value))) {
189                        Map<String, Object> columnData = new HashMap<>();
190
191                        for (int colIndex=1; colIndex <= table.numColumns(); colIndex++) {
192                            Object subHeader = table.getCell(0, colIndex);
193                            if ((subHeader != null) && (!subHeader.toString().isEmpty())) {
194                                columnData.put(subHeader.toString(), table.getCell(row, colIndex));
195                            }
196                        }
197                        return columnData;
198                    }
199                }
200            }
201        }
202        return null;    // No row found
203    }
204
205    /** {@inheritDoc} */
206    @Override
207    public void execute() throws JmriException {
208        SymbolTable symbolTable = getConditionalNG().getSymbolTable();
209        Object value;
210        if ((_localVariableNamedBean != null) && (_localVariableRow != null)) {
211            value = symbolTable.getValue(_localVariableNamedBean);
212            symbolTable.setValue(_localVariableRow, getRow(value));
213        }
214    }
215
216    @Override
217    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
218        throw new UnsupportedOperationException("Not supported.");
219    }
220
221    @Override
222    public int getChildCount() {
223        return 0;
224    }
225
226    @Override
227    public String getShortDescription(Locale locale) {
228        return Bundle.getMessage(locale, "ActionFindTableRowOrColumn_Short");
229    }
230
231    @Override
232    public String getLongDescription(Locale locale) {
233        String tableName = _selectNamedBean.getDescription(locale);
234        return Bundle.getMessage(locale, "ActionFindTableRowOrColumn_Long",
235                _tableRowOrColumn.getOpposite().toStringLowerCase(),
236                tableName,
237                _tableRowOrColumn.toStringLowerCase(),
238                _rowOrColumnName,
239                _tableRowOrColumn.getOpposite().toStringLowerCase(),
240                _localVariableNamedBean);
241    }
242
243    /** {@inheritDoc} */
244    @Override
245    public void setup() {
246        // Do nothing
247    }
248
249    /** {@inheritDoc} */
250    @Override
251    public void registerListenersForThisClass() {
252        if (_listenersAreRegistered) return;
253
254        _selectNamedBean.registerListeners();
255        _listenersAreRegistered = true;
256    }
257
258    /** {@inheritDoc} */
259    @Override
260    public void unregisterListenersForThisClass() {
261        if (!_listenersAreRegistered) return;
262
263        _selectNamedBean.unregisterListeners();
264        _listenersAreRegistered = false;
265    }
266
267    /** {@inheritDoc} */
268    @Override
269    public void propertyChange(PropertyChangeEvent evt) {
270//        System.out.format("Table: Property: %s, Bean: %s, Listen: %b%n", evt.getPropertyName(), ((NamedBean)evt.getSource()).getDisplayName(), _listenOnAllProperties);
271        getConditionalNG().execute();
272    }
273
274    /** {@inheritDoc} */
275    @Override
276    public void disposeMe() {
277    }
278
279
280    /** {@inheritDoc} */
281    @Override
282    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
283/*
284        log.debug("getUsageReport :: ActionListenOnBeans: bean = {}, report = {}", cdl, report);
285        for (NamedBeanReference namedBeanReference : _namedBeanReferences.values()) {
286            if (namedBeanReference._handle != null) {
287                if (bean.equals(namedBeanReference._handle.getBean())) {
288                    report.add(new NamedBeanUsageReport("LogixNGAction", cdl, getLongDescription()));
289                }
290            }
291        }
292*/
293    }
294
295    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionFindTableRowOrColumn.class);
296
297}