001package jmri.jmrit.logixng.expressions;
002
003import java.beans.*;
004import java.util.*;
005
006import jmri.*;
007import jmri.Block;
008import jmri.BlockManager;
009import jmri.jmrit.display.layoutEditor.LayoutBlock;
010import jmri.jmrit.display.layoutEditor.LayoutBlockManager;
011import jmri.jmrit.logixng.*;
012import jmri.jmrit.logixng.util.LogixNG_SelectNamedBean;
013import jmri.jmrit.logixng.util.LogixNG_SelectEnum;
014import jmri.jmrit.logixng.util.LogixNG_SelectString;
015import jmri.jmrit.logixng.util.parser.*;
016
017/**
018 * This expression evaluates the state of a Block.
019 * The supported characteristics are:
020 * <ul>
021 *   <li>Is [not] Occupied (based on occupancy sensor state)</li>
022 *   <li>Is [not] Unoccupied (based on occupancy sensor state)</li>
023 *   <li>Is [not] Other (UNKNOWN, INCONSISTENT, UNDETECTED)</li>
024 *   <li>Is [not] Allocated (based on the LayoutBlock useAlternateColor)</li>
025 *   <li>Value [not] equals string</li>
026 * </ul>
027 * @author Daniel Bergqvist Copyright 2021
028 * @author Dave Sand Copyright 2021
029 */
030public class ExpressionBlock extends AbstractDigitalExpression
031        implements PropertyChangeListener {
032
033    private final LogixNG_SelectNamedBean<Block> _selectNamedBean =
034            new LogixNG_SelectNamedBean<>(
035                    this, Block.class, InstanceManager.getDefault(BlockManager.class), this);
036
037    private Is_IsNot_Enum _is_IsNot = Is_IsNot_Enum.Is;
038
039    private final LogixNG_SelectEnum<BlockState> _selectEnum =
040            new LogixNG_SelectEnum<>(this, BlockState.values(), BlockState.Occupied, this);
041
042    private final LogixNG_SelectString _selectBlockValue =
043            new LogixNG_SelectString(this, this);
044
045
046    public ExpressionBlock(String sys, String user)
047            throws BadUserNameException, BadSystemNameException {
048        super(sys, user);
049    }
050
051    @Override
052    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException {
053        DigitalExpressionManager manager = InstanceManager.getDefault(DigitalExpressionManager.class);
054        String sysName = systemNames.get(getSystemName());
055        String userName = userNames.get(getSystemName());
056        if (sysName == null) sysName = manager.getAutoSystemName();
057        ExpressionBlock copy = new ExpressionBlock(sysName, userName);
058        copy.setComment(getComment());
059
060        _selectNamedBean.copy(copy._selectNamedBean);
061        _selectEnum.copy(copy._selectEnum);
062        _selectBlockValue.copy(copy._selectBlockValue);
063
064        copy.set_Is_IsNot(_is_IsNot);
065
066        return manager.registerExpression(copy);
067    }
068
069    public LogixNG_SelectNamedBean<Block> getSelectNamedBean() {
070        return _selectNamedBean;
071    }
072
073    public LogixNG_SelectEnum<BlockState> getSelectEnum() {
074        return _selectEnum;
075    }
076
077    public LogixNG_SelectString getSelectBlockValue() {
078        return _selectBlockValue;
079    }
080
081    public void set_Is_IsNot(Is_IsNot_Enum is_IsNot) {
082        _is_IsNot = is_IsNot;
083    }
084
085    public Is_IsNot_Enum get_Is_IsNot() {
086        return _is_IsNot;
087    }
088
089    /** {@inheritDoc} */
090    @Override
091    public Category getCategory() {
092        return Category.ITEM;
093    }
094
095    /**
096     * A block is considered to be allocated if the related layout block has use extra color enabled.
097     * @param block The block whose allocation state is requested.
098     * @return true if the layout block is using the extra color.
099     */
100    public boolean isBlockAllocated(Block block) {
101        boolean result = false;
102        LayoutBlock layoutBlock = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(block);
103        if (layoutBlock != null) {
104            result = layoutBlock.getUseExtraColor();
105        }
106        return result;
107    }
108
109    /** {@inheritDoc} */
110    @Override
111    public boolean evaluate() throws JmriException {
112        ConditionalNG conditionalNG = getConditionalNG();
113
114        Block block = _selectNamedBean.evaluateNamedBean(conditionalNG);
115
116        if (block == null) return false;
117
118        BlockState checkBlockState = _selectEnum.evaluateEnum(conditionalNG);
119
120        int currentState = block.getState();
121
122        switch (checkBlockState) {
123            case Other:
124                if (currentState != Block.OCCUPIED && currentState != Block.UNOCCUPIED) {
125                    currentState = BlockState.Other.getID();
126                } else {
127                    currentState = 0;
128                }
129                break;
130
131            case Allocated:
132                boolean cuurrentAllocation = isBlockAllocated(block);
133                currentState = cuurrentAllocation ? BlockState.Allocated.getID() : 0;
134                break;
135
136            case ValueMatches:
137                String blockValue = _selectBlockValue.evaluateValue(conditionalNG);
138                currentState = blockValue.equals(block.getValue())
139                        ? BlockState.ValueMatches.getID() : 0;
140                break;
141
142            default:
143                break;
144        }
145
146        if (_is_IsNot == Is_IsNot_Enum.Is) {
147            return currentState == checkBlockState.getID();
148        } else {
149            return currentState != checkBlockState.getID();
150        }
151    }
152
153    @Override
154    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
155        throw new UnsupportedOperationException("Not supported.");
156    }
157
158    @Override
159    public int getChildCount() {
160        return 0;
161    }
162
163    @Override
164    public String getShortDescription(Locale locale) {
165        return Bundle.getMessage(locale, "Block_Short");
166    }
167
168    @Override
169    public String getLongDescription(Locale locale) {
170        String namedBean = _selectNamedBean.getDescription(locale);
171        String state;
172
173        if (_selectEnum.isDirectAddressing()) {
174            BlockState blockState = _selectEnum.getEnum();
175
176            if (blockState == BlockState.ValueMatches) {
177                String bundleKey = "Block_Long_Value";
178                String equalsString = _is_IsNot == Is_IsNot_Enum.Is ? Bundle.getMessage("Block_Equal") : Bundle.getMessage("Block_NotEqual");
179                return Bundle.getMessage(locale, bundleKey, namedBean, equalsString, _selectBlockValue.getDescription(locale));
180            } else if (blockState == BlockState.Other) {
181                state = Bundle.getMessage(locale, "AddressByDirect", blockState._text);
182                return Bundle.getMessage(locale, "Block_Long", namedBean, "", state);
183            } else {
184                state = Bundle.getMessage(locale, "AddressByDirect", blockState._text);
185            }
186        } else {
187            state = _selectEnum.getDescription(locale);
188        }
189
190        return Bundle.getMessage(locale, "Block_Long", namedBean, _is_IsNot.toString(), state);
191    }
192
193    /** {@inheritDoc} */
194    @Override
195    public void setup() {
196        // Do nothing
197    }
198
199    /** {@inheritDoc} */
200    @Override
201    public void registerListenersForThisClass() {
202        if (!_listenersAreRegistered) {
203            _selectNamedBean.addPropertyChangeListener(this);
204            _selectNamedBean.registerListeners();
205            _selectEnum.registerListeners();
206            _selectBlockValue.registerListeners();
207            _listenersAreRegistered = true;
208        }
209    }
210
211    /** {@inheritDoc} */
212    @Override
213    public void unregisterListenersForThisClass() {
214        if (_listenersAreRegistered) {
215            _selectNamedBean.removePropertyChangeListener(this);
216            _selectNamedBean.unregisterListeners();
217            _selectEnum.unregisterListeners();
218            _selectBlockValue.unregisterListeners();
219            _listenersAreRegistered = false;
220        }
221    }
222
223    /** {@inheritDoc} */
224    @Override
225    public void propertyChange(PropertyChangeEvent evt) {
226        getConditionalNG().execute();
227    }
228
229    /** {@inheritDoc} */
230    @Override
231    public void disposeMe() {
232    }
233
234    public enum BlockState {
235        Occupied(2, Bundle.getMessage("Block_StateOccupied")),
236        NotOccupied(4, Bundle.getMessage("Block_StateNotOccupied")),
237        Other(-1, Bundle.getMessage("Block_StateOther")),
238        Allocated(-2, Bundle.getMessage("Block_Allocated")),
239        ValueMatches(-3, Bundle.getMessage("Block_ValueMatches"));
240
241        private final int _id;
242        private final String _text;
243
244        private BlockState(int id, String text) {
245            this._id = id;
246            this._text = text;
247        }
248
249        public int getID() {
250            return _id;
251        }
252
253        @Override
254        public String toString() {
255            return _text;
256        }
257    }
258
259    /** {@inheritDoc} */
260    @Override
261    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
262        log.debug("getUsageReport :: ExpressionBlock: bean = {}, report = {}", cdl, report);
263        _selectNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Expression);
264    }
265
266    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ExpressionBlock.class);
267
268}