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