001package jmri.jmrit.logixng.actions;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.util.HashMap;
006import java.util.List;
007import java.util.Locale;
008import java.util.Map;
009
010import javax.annotation.Nonnull;
011
012import jmri.*;
013import jmri.jmrit.logixng.*;
014import jmri.jmrit.logixng.util.LogixNG_SelectEnum;
015import jmri.jmrit.logixng.util.LogixNG_SelectNamedBean;
016import jmri.jmrit.logixng.util.ReferenceUtil;
017import jmri.jmrit.logixng.util.parser.ExpressionNode;
018import jmri.jmrit.logixng.util.parser.ParserException;
019import jmri.jmrit.logixng.util.parser.RecursiveDescentParser;
020import jmri.jmrit.logixng.util.parser.Variable;
021import jmri.util.ThreadingUtil;
022import jmri.util.TypeConversionUtil;
023
024/**
025 * This action sets the state of a light.
026 *
027 * @author Daniel Bergqvist Copyright 2018
028 */
029public class ActionLight extends AbstractDigitalAction
030        implements PropertyChangeListener {
031
032    private final LogixNG_SelectNamedBean<Light> _selectNamedBean =
033            new LogixNG_SelectNamedBean<>(
034                    this, Light.class, InstanceManager.getDefault(LightManager.class), this);
035
036    private final LogixNG_SelectEnum<LightState> _selectEnum =
037            new LogixNG_SelectEnum<>(this, LightState.values(), LightState.On, this);
038
039    private NamedBeanAddressing _dataAddressing = NamedBeanAddressing.Direct;
040    private String _dataReference = "";
041    private String _dataLocalVariable = "";
042    private String _dataFormula = "";
043    private ExpressionNode _dataExpressionNode;
044
045    private int _lightValue = 0;
046
047
048    public ActionLight(String sys, String user)
049            throws BadUserNameException, BadSystemNameException {
050        super(sys, user);
051    }
052
053    @Override
054    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException {
055        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
056        String sysName = systemNames.get(getSystemName());
057        String userName = userNames.get(getSystemName());
058        if (sysName == null) sysName = manager.getAutoSystemName();
059        ActionLight copy = new ActionLight(sysName, userName);
060        copy.setComment(getComment());
061        _selectNamedBean.copy(copy._selectNamedBean);
062        _selectEnum.copy(copy._selectEnum);
063
064        copy.setDataAddressing(_dataAddressing);
065        copy.setDataReference(_dataReference);
066        copy.setDataLocalVariable(_dataLocalVariable);
067        copy.setDataFormula(_dataFormula);
068
069        copy.setLightValue(_lightValue);
070
071        return manager.registerAction(copy);
072    }
073
074    public LogixNG_SelectNamedBean<Light> getSelectNamedBean() {
075        return _selectNamedBean;
076    }
077
078    public LogixNG_SelectEnum<LightState> getSelectEnum() {
079        return _selectEnum;
080    }
081
082    public void setDataAddressing(NamedBeanAddressing addressing) throws ParserException {
083        _dataAddressing = addressing;
084        parseDataFormula();
085    }
086
087    public NamedBeanAddressing getDataAddressing() {
088        return _dataAddressing;
089    }
090
091    public void setDataReference(@Nonnull String reference) {
092        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
093            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
094        }
095        _dataReference = reference;
096    }
097
098    public String getDataReference() {
099        return _dataReference;
100    }
101
102    public void setDataLocalVariable(@Nonnull String localVariable) {
103        _dataLocalVariable = localVariable;
104    }
105
106    public String getDataLocalVariable() {
107        return _dataLocalVariable;
108    }
109
110    public void setDataFormula(@Nonnull String formula) throws ParserException {
111        _dataFormula = formula;
112        parseDataFormula();
113    }
114
115    public String getDataFormula() {
116        return _dataFormula;
117    }
118
119    private void parseDataFormula() throws ParserException {
120        if (_dataAddressing == NamedBeanAddressing.Formula) {
121            Map<String, Variable> variables = new HashMap<>();
122
123            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
124            _dataExpressionNode = parser.parseExpression(_dataFormula);
125        } else {
126            _dataExpressionNode = null;
127        }
128    }
129
130
131    public void setLightValue(int value) {
132        _lightValue = value;
133    }
134
135    public int getLightValue() {
136        return _lightValue;
137    }
138
139    /** {@inheritDoc} */
140    @Override
141    public Category getCategory() {
142        return Category.ITEM;
143    }
144
145    private int getNewData(SymbolTable symbolTable) throws JmriException {
146        String newValue = "";
147
148        switch (_dataAddressing) {
149            case Direct:
150                return _lightValue;
151
152            case Reference:
153                newValue = ReferenceUtil.getReference(symbolTable, _dataReference);
154                break;
155
156            case LocalVariable:
157                newValue = TypeConversionUtil
158                        .convertToString(symbolTable.getValue(_dataLocalVariable), false);
159                break;
160
161            case Formula:
162                newValue = _dataExpressionNode != null
163                        ? TypeConversionUtil.convertToString(
164                                _dataExpressionNode.calculate(symbolTable), false)
165                        : "";
166                break;
167
168            default:
169                throw new IllegalArgumentException("invalid _addressing state: " + _dataAddressing.name());
170        }
171        try {
172            int newInt = Integer.parseInt(newValue);
173            if (newInt < 0) newInt = 0;
174            if (newInt > 100) newInt = 100;
175            return newInt;
176        } catch (NumberFormatException ex) {
177            return 0;
178        }
179    }
180
181    /** {@inheritDoc} */
182    @Override
183    public void execute() throws JmriException {
184        Light light = _selectNamedBean.evaluateNamedBean(getConditionalNG());
185
186        if (light == null) return;
187
188        LightState state = _selectEnum.evaluateEnum(getConditionalNG());
189
190        SymbolTable symbolTable = getConditionalNG().getSymbolTable();
191
192        ThreadingUtil.runOnLayoutWithJmriException(() -> {
193            if (state == LightState.Toggle) {
194                if (light.getKnownState() == Turnout.CLOSED) {
195                    light.setCommandedState(Turnout.THROWN);
196                } else {
197                    light.setCommandedState(Turnout.CLOSED);
198                }
199
200            } else if (state == LightState.Intensity) {
201                if (light instanceof VariableLight) {
202                    ((VariableLight)light).setTargetIntensity(getNewData(symbolTable) / 100.0);
203                } else {
204                    light.setCommandedState(getNewData(symbolTable) > 50 ? Light.ON : Light.OFF);
205                }
206            } else if (state == LightState.Interval) {
207                if (light instanceof VariableLight) {
208                    ((VariableLight)light).setTransitionTime(getNewData(symbolTable));
209                }
210            } else {
211                light.setCommandedState(state.getID());
212            }
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, "Light_Short");
229    }
230
231    @Override
232    public String getLongDescription(Locale locale) {
233        String namedBean = _selectNamedBean.getDescription(locale);
234        String state = _selectEnum.getDescription(locale);
235
236        if (_selectEnum.getAddressing() == NamedBeanAddressing.Direct) {
237            if (_selectEnum.getEnum() == LightState.Intensity || _selectEnum.getEnum() == LightState.Interval) {
238                String bundleKey = "Light_Long_Value";
239                switch (_dataAddressing) {
240                    case Direct:
241                        String type = _selectEnum.getEnum() == LightState.Intensity ?
242                                 Bundle.getMessage("Light_Intensity_Value") :
243                                 Bundle.getMessage("Light_Interval_Value");
244                        return Bundle.getMessage(locale, bundleKey, namedBean, type, _lightValue);
245                    case Reference:
246                        return Bundle.getMessage(locale, bundleKey, namedBean, "", Bundle.getMessage("AddressByReference", _dataReference));
247                    case LocalVariable:
248                        return Bundle.getMessage(locale, bundleKey, namedBean, "", Bundle.getMessage("AddressByLocalVariable", _dataLocalVariable));
249                    case Formula:
250                        return Bundle.getMessage(locale, bundleKey, namedBean, "", Bundle.getMessage("AddressByFormula", _dataFormula));
251                    default:
252                        throw new IllegalArgumentException("invalid _dataAddressing state: " + _dataAddressing.name());
253                }
254            }
255        }
256
257        return Bundle.getMessage(locale, "Light_Long", namedBean, state);
258    }
259
260    /** {@inheritDoc} */
261    @Override
262    public void setup() {
263        // Do nothing
264    }
265
266    /** {@inheritDoc} */
267    @Override
268    public void registerListenersForThisClass() {
269        _selectNamedBean.registerListeners();
270        _selectEnum.registerListeners();
271    }
272
273    /** {@inheritDoc} */
274    @Override
275    public void unregisterListenersForThisClass() {
276        _selectNamedBean.unregisterListeners();
277        _selectEnum.unregisterListeners();
278    }
279
280    /** {@inheritDoc} */
281    @Override
282    public void disposeMe() {
283    }
284
285
286    // This constant is only used internally in LightState but must be outside
287    // the enum.
288    private static final int TOGGLE_ID = -1;
289    private static final int INTENSITY_ID = -2;
290    private static final int INTERVAL_ID = -3;
291
292
293    public enum LightState {
294        Off(Light.OFF, Bundle.getMessage("StateOff")),
295        On(Light.ON, Bundle.getMessage("StateOn")),
296        Toggle(TOGGLE_ID, Bundle.getMessage("LightToggleStatus")),
297        Intensity(INTENSITY_ID, Bundle.getMessage("LightIntensity")),
298        Interval(INTERVAL_ID, Bundle.getMessage("LightInterval")),
299        Unknown(Light.UNKNOWN, Bundle.getMessage("BeanStateUnknown")),
300        Inconsistent(Light.INCONSISTENT, Bundle.getMessage("BeanStateInconsistent"));
301
302        private final int _id;
303        private final String _text;
304
305        private LightState(int id, String text) {
306            this._id = id;
307            this._text = text;
308        }
309
310        static public LightState get(int id) {
311            switch (id) {
312                case Light.UNKNOWN:
313                    return Unknown;
314
315                case Light.INCONSISTENT:
316                    return Inconsistent;
317
318                case Light.OFF:
319                    return Off;
320
321                case Light.ON:
322                    return On;
323
324                case TOGGLE_ID:
325                    return Toggle;
326
327                default:
328                    throw new IllegalArgumentException("invalid light state");
329            }
330        }
331
332        public int getID() {
333            return _id;
334        }
335
336        @Override
337        public String toString() {
338            return _text;
339        }
340
341    }
342
343    /** {@inheritDoc} */
344    @Override
345    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
346        _selectNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Action);
347    }
348
349    /** {@inheritDoc} */
350    @Override
351    public void propertyChange(PropertyChangeEvent evt) {
352        getConditionalNG().execute();
353    }
354
355//    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionLight.class);
356
357}