001package jmri.jmrit.logix;
002
003import jmri.InstanceManager;
004import jmri.JmriException;
005import jmri.NamedBean;
006import jmri.NamedBeanHandle;
007import jmri.NamedBeanHandleManager;
008import jmri.Sensor;
009import jmri.SpeedStepMode;
010
011import java.text.NumberFormat;
012
013import org.slf4j.Logger;
014import org.slf4j.LoggerFactory;
015
016/**
017 * Oct 2020 - change formats to allow I18N of parameters
018 * @author  Pete Cressman Copyright (C) 2009, 2020
019 */
020public class ThrottleSetting {
021
022    static final int CMD_SPEED = 1;
023    static final int CMD_SPEEDSTEP = 2;
024    static final int CMD_FORWARD = 3;
025    static final int CMD_FTN = 4;
026    static final int CMD_LATCH = 5;
027    static final int CMD_NOOP = 6;
028    static final int CMD_SET_SENSOR = 7;
029    static final int CMD_WAIT_SENSOR = 8;
030    static final int CMD_RUN_WARRANT = 9;
031
032    public enum Command {
033        SPEED(CMD_SPEED, true, "speed"),
034        FORWARD(CMD_FORWARD, true, "forward"),
035        FKEY(CMD_FTN, true, "setFunction"),
036        LATCHF(CMD_LATCH, true, "setKeyMomentary"),
037        SET_SENSOR(CMD_SET_SENSOR, false, "SetSensor"),
038        WAIT_SENSOR(CMD_WAIT_SENSOR, false, "WaitSensor"),
039        RUN_WARRANT(CMD_RUN_WARRANT, false, "runWarrant"),
040        NOOP(CMD_NOOP, true, "NoOp"),
041        SPEEDSTEP(CMD_SPEEDSTEP, true, "speedstep");
042
043        int _command;
044        boolean _hasBlock; // when bean is an OBlock.
045        String _bundleKey; // key to get command display name
046
047        Command(int cmd, boolean hasBlock, String bundleName) {
048            _command = cmd;
049            _hasBlock = hasBlock;
050            _bundleKey = bundleName;
051        }
052
053        public int getIntId() {
054            return _command;
055        }
056
057        boolean hasBlockName() {
058            return _hasBlock;
059        }
060
061        @Override
062        public String toString() {
063            return Bundle.getMessage(_bundleKey);
064        }
065    }
066
067    static final int VALUE_FLOAT = 1;
068    static final int VALUE_NOOP = 2;
069    static final int VALUE_INT = 3;
070    static final int VALUE_STEP = 4;
071    static final int VALUE_TRUE = 10;
072    static final int VALUE_FALSE = 11;
073    static final int VALUE_ON = 20;
074    static final int VALUE_OFF = 21;
075    static final int VALUE_ACTIVE = 30;
076    static final int VALUE_INACTIVE = 31;
077
078    static final int IS_TRUE_FALSE = 1;
079    static final int IS_ON_OFF = 2;
080    static final int IS_SENSOR_STATE = 3;
081
082    public enum ValueType {
083        VAL_FLOAT(VALUE_FLOAT, "NumData"),
084        VAL_NOOP(VALUE_NOOP, "Mark"),
085        VAL_INT(VALUE_INT, "NumData"),
086        VAL_STEP(VALUE_STEP, "speedstep"),
087        VAL_TRUE(VALUE_TRUE, "StateTrue"),
088        VAL_FALSE(VALUE_FALSE, "StateFalse"),
089        VAL_ON(VALUE_ON, "StateOn"),
090        VAL_OFF(VALUE_OFF, "StateOff"),
091        VAL_ACTIVE(VALUE_ACTIVE, "SensorStateActive"),
092        VAL_INACTIVE(VALUE_INACTIVE, "SensorStateInactive");
093
094        int _valueId;  // state id
095        String _bundleKey; // key to get state display name
096
097        ValueType(int id, String bundleName) {
098            _valueId = id;
099            _bundleKey = bundleName;
100        }
101 
102        public int getIntId() {
103            return _valueId;
104        }
105
106        @Override
107        public String toString() {
108            return Bundle.getMessage(_bundleKey);
109        }
110    }
111
112    public static class CommandValue {
113        ValueType _type;
114        SpeedStepMode  _stepMode;
115        float   _floatValue;
116        NumberFormat formatter = NumberFormat.getNumberInstance(); 
117        NumberFormat intFormatter = NumberFormat.getIntegerInstance(); 
118
119        public CommandValue(ValueType t, SpeedStepMode s, float f) {
120            _type = t;
121            _stepMode = s;
122            _floatValue = f;
123        }
124
125        @Override
126        protected CommandValue clone() {
127            return new CommandValue(_type, _stepMode, _floatValue);
128        }
129
130        public ValueType getType() {
131            return _type;
132        }
133
134        public SpeedStepMode getMode() {
135            return _stepMode;
136        }
137
138        void setFloat(float f) {
139            _floatValue = f;
140        }
141
142        public float getFloat() {
143            return _floatValue;
144        }
145
146        public String showValue() {
147            if (_type == ValueType.VAL_FLOAT) {
148                return formatter.format(_floatValue);                               
149            } else if (_type == ValueType.VAL_INT) {
150                return intFormatter.format(_floatValue);
151            } else if (_type == ValueType.VAL_STEP) {
152                return _stepMode.name;
153            } else {
154                return _type.toString();
155            }
156        }
157
158        @Override
159        public String toString() {
160            return "CommandValue type "+_type.getIntId();
161        }
162    }
163
164    public static Command getCommandTypeFromInt(int typeInt) {
165        for (Command type : Command.values()) {
166            if (type.getIntId() == typeInt) {
167                return type;
168            }
169        }
170        throw new IllegalArgumentException(typeInt + " Command type ID is unknown");
171    }
172
173    public static ValueType getValueTypeFromInt(int typeInt) {
174        for (ValueType type : ValueType.values()) {
175            if (type.getIntId() == typeInt) {
176                return type;
177            }
178        }
179        throw new IllegalArgumentException(typeInt + " ValueType ID is unknown");
180    }
181
182    //====================== THrottleSteeing Class ====================
183    private long    _time;
184    private Command _command;
185    private int     _keyNum; // function key number
186    private CommandValue _value;
187    // _namedHandle may be of 3 different types
188    private NamedBeanHandle<? extends NamedBean> _namedHandle = null;
189    private float _trackSpeed; // track speed of the train (millimeters per second)
190
191    public ThrottleSetting() {
192        _keyNum = -1;
193    }
194
195    public ThrottleSetting(long time, Command command, int key, ValueType vType, SpeedStepMode ss, float f, String beanName) {
196        _time = time;
197        _command = command;
198        _keyNum = key;
199        setValue(vType, ss, f);
200        setNamedBean(command, beanName);
201        _trackSpeed = 0.0f;
202    }
203
204    public ThrottleSetting(long time, Command command, int key, ValueType vType, SpeedStepMode ss, float f, String beanName, float trkSpd) {
205        _time = time;
206        _command = command;
207        _keyNum = key;
208        setValue(vType, ss, f);
209        setNamedBean(command, beanName);
210        _trackSpeed = trkSpd;
211    }
212
213    // pre 4.21.3
214    public ThrottleSetting(long time, String cmdStr, String value, String beanName) {
215        _time = time;
216        setCommand(cmdStr);
217        setValue(value);    // must follow setCommand() 
218        setNamedBean(_command, beanName);
219        _trackSpeed = 0.0f;
220    }
221
222    // pre 4.21.3
223    public ThrottleSetting(long time, String cmdStr, String value, String beanName, float trkSpd) {
224        _time = time;
225        setCommand(cmdStr);
226        setValue(value);
227        setNamedBean(_command, beanName);
228        _trackSpeed = trkSpd;
229    }
230
231    public ThrottleSetting(long time, Command command, int key, String value, String beanName, float trkSpd) {
232        _time = time;
233        _command = command;
234        _keyNum = key;
235        setValue(value);    // must follow setCommand() 
236        _namedHandle = null;
237        _trackSpeed = trkSpd;
238    }
239
240    public ThrottleSetting(ThrottleSetting ts) {
241        _time = ts.getTime();
242        _command = ts.getCommand();
243        _keyNum = ts.getKeyNum();
244        setValue(ts.getValue());
245        _namedHandle = ts.getNamedBeanHandle();
246        _trackSpeed = ts.getTrackSpeed();
247    }
248
249    /**
250     * Convert old format. (former Strings for Command enums)
251     * @param cmdStr old style description string
252     * @return enum Command
253     * @throws JmriException in case of a non-integer Function or Fn lock/latch value
254     */
255    private Command getCommandFromString(String cmdStr) throws JmriException {
256        Command command;
257        String cmd = cmdStr.trim().toUpperCase();
258        if ("SPEED".equals(cmd) || Bundle.getMessage("speed").toUpperCase().equals(cmd)) {
259            command = Command.SPEED;
260            _keyNum = -1;
261        } else if ("SPEEDSTEP".equals(cmd) || Bundle.getMessage("speedstep").toUpperCase().equals(cmd)) {
262            command = Command.SPEEDSTEP;
263            _keyNum = -1;
264        } else if ("FORWARD".equals(cmd) || Bundle.getMessage("forward").toUpperCase().equals(cmd)) {
265            command = Command.FORWARD;
266            _keyNum = -1;
267        } else if (cmd.startsWith("F") || Bundle.getMessage("setFunction").toUpperCase().equals(cmd)) {
268            command = Command.FKEY;
269            try {
270                _keyNum = Integer.parseInt(cmd.substring(1));
271            } catch (NumberFormatException nfe) {
272                throw new JmriException(Bundle.getMessage("badFunctionNum"), nfe);
273            }
274        } else if (cmd.startsWith("LOCKF") || Bundle.getMessage("setKeyMomentary").toUpperCase().equals(cmd)) {
275            command = Command.LATCHF;
276            try {
277                _keyNum = Integer.parseInt(cmd.substring(5));
278            } catch (NumberFormatException nfe) {
279                throw new JmriException(Bundle.getMessage("badLockFNum"), nfe);
280            }
281        } else if ("NOOP".equals(cmd) || Bundle.getMessage("NoOp").toUpperCase().equals(cmd)) {
282            command = Command.NOOP;
283            _keyNum = -1;
284        } else if ("SENSOR".equals(cmd) || "SET SENSOR".equals(cmd) || "SET".equals(cmd) 
285                || Bundle.getMessage("SetSensor").toUpperCase().equals(cmd)) {
286            command = Command.SET_SENSOR;
287            _keyNum = -1;
288        } else if ("WAIT SENSOR".equals(cmd) || "WAIT".equals(cmd) 
289                || Bundle.getMessage("WaitSensor").toUpperCase().equals(cmd)) {
290            command = Command.WAIT_SENSOR;
291            _keyNum = -1;
292        } else if ("RUN WARRANT".equals(cmd) || Bundle.getMessage("runWarrant").toUpperCase().equals(cmd)) {
293            command = Command.RUN_WARRANT;
294            _keyNum = -1;
295        } else {
296            throw new jmri.JmriException(Bundle.getMessage("badCommand", cmdStr));
297        }
298        return command;
299    }
300
301    static protected CommandValue getValueFromString(Command command, String valueStr) throws JmriException {
302        if (command == null) {
303            throw new jmri.JmriException(Bundle.getMessage("badCommand", "Command missing "+valueStr));
304        }
305        ValueType type;
306        SpeedStepMode mode = SpeedStepMode.UNKNOWN;
307        float speed = 0.0f;
308        String val = valueStr.trim().toUpperCase();
309        if ("ON".equals(val) || Bundle.getMessage("StateOn").toUpperCase().equals(val)) {
310            switch (command) {
311                case FKEY:
312                case LATCHF:
313                type = ValueType.VAL_ON;
314                    break;
315                default:
316                    throw new jmri.JmriException(Bundle.getMessage("badValue", valueStr, command));
317            }
318        } else if ("OFF".equals(val) || Bundle.getMessage("StateOff").toUpperCase().equals(val)) {
319            switch (command) {
320                case FKEY:
321                case LATCHF:
322                type = ValueType.VAL_OFF;
323                    break;
324                default:
325                    throw new jmri.JmriException(Bundle.getMessage("badValue", valueStr, command));
326            }
327        } else  if ("TRUE".equals(val) || Bundle.getMessage("StateTrue").toUpperCase().equals(val)) {
328            switch (command) {
329                case FORWARD:
330                    type = ValueType.VAL_TRUE;
331                    break;
332                case FKEY:
333                case LATCHF:
334                    type = ValueType.VAL_ON;
335                    break;
336                default:
337                    throw new jmri.JmriException(Bundle.getMessage("badValue", valueStr, command));
338            }
339        } else if ("FALSE".equals(val) || Bundle.getMessage("StateFalse").toUpperCase().equals(val)) {
340            switch (command) {
341                case FORWARD:
342                    type = ValueType.VAL_FALSE;
343                    break;
344                case FKEY:
345                case LATCHF:
346                    type = ValueType.VAL_OFF;
347                    break;
348                default:
349                    throw new jmri.JmriException(Bundle.getMessage("badValue", valueStr, command));
350            }
351        } else if ("ACTIVE".equals(val) || Bundle.getMessage("SensorStateActive").toUpperCase().equals(val)) {
352            switch (command) {
353                case SET_SENSOR:
354                case WAIT_SENSOR:
355                    type = ValueType.VAL_ACTIVE;
356                    break;
357                default:
358                    throw new jmri.JmriException(Bundle.getMessage("badValue", valueStr, command));
359            }
360        } else if ("INACTIVE".equals(val) || Bundle.getMessage("SensorStateInactive").toUpperCase().equals(val)) {
361            switch (command) {
362                case SET_SENSOR:
363                case WAIT_SENSOR:
364                    type = ValueType.VAL_INACTIVE;
365                    break;
366                default:
367                    throw new jmri.JmriException(Bundle.getMessage("badValue", valueStr, command));
368            }
369        } else {
370            try {
371                switch (command) {
372                    case SPEED:
373                        speed = Float.parseFloat(valueStr.replace(',', '.'));
374                        type = ValueType.VAL_FLOAT;
375                        break;
376                    case NOOP:
377                        type = ValueType.VAL_NOOP;
378                        break;
379                    case RUN_WARRANT:
380                        speed = Float.parseFloat(valueStr.replace(',', '.'));
381                        type = ValueType.VAL_INT;
382                        break;
383                    case SPEEDSTEP:
384                        mode = SpeedStepMode.getByName(val);
385                        type = ValueType.VAL_STEP;
386                        break;
387                    default:
388                        throw new JmriException(Bundle.getMessage("badValue", valueStr, command));
389                }
390            } catch (IllegalArgumentException | NullPointerException ex) { // NumberFormatException is sublass of iae
391                throw new JmriException(Bundle.getMessage("badValue", valueStr, command), ex);
392            }
393        }
394        return new CommandValue(type, mode, speed);
395    }
396
397    /**
398     * Time is an object so that a "synch to block entry" notation can be used
399     * rather than elapsed time.
400     *
401     * @param time the time in some unit
402     */
403    public void setTime(long time) {
404        _time = time;
405    }
406
407    public long getTime() {
408        return _time;
409    }
410
411    public void setCommand(String cmdStr) {
412        try {
413            _command = getCommandFromString(cmdStr);
414        } catch (JmriException je) {
415            log.error("Cannot set command from string \"{}\" {}", cmdStr, je.toString());
416        }
417    }
418
419    public void setCommand(Command command) {
420        _command = command;
421    }
422
423    public Command getCommand() {
424        return _command;
425    }
426
427    public void setKeyNum(int key) {
428        _keyNum = key;
429    }
430
431    public int getKeyNum() {
432        return _keyNum;
433    }
434
435    public void setValue(String valueStr) {
436        try {
437            _value = getValueFromString(_command, valueStr);
438        } catch (JmriException je) {
439            log.error("Cannot set value for command {}. {}",
440                   (_command!=null?_command.toString():"null"), je.toString());
441        }
442    }
443
444    public void setValue(CommandValue value) {
445        _value = value.clone();
446    }
447
448    public void setValue(ValueType t, SpeedStepMode s, float f) {
449        _value = new CommandValue(t, s, f);
450    }
451
452    public CommandValue getValue() {
453        return _value;
454    }
455
456    public void setTrackSpeed(float s) {
457        _trackSpeed = s;
458    }
459
460    public float getTrackSpeed() {
461        return _trackSpeed;
462    }
463
464    // _namedHandle may be of 3 different types
465    public String setNamedBean(Command cmd, String name) {
466        if (log.isDebugEnabled()) {
467            log.debug("setNamedBean({}, {})", cmd, name);
468        }
469        String msg = WarrantFrame.checkBeanName(cmd, name);
470        if (msg != null) {
471            _namedHandle = null;
472            return msg;
473        }
474        try {
475            if (cmd.equals(Command.SET_SENSOR) || cmd.equals(Command.WAIT_SENSOR)) {
476                Sensor s = InstanceManager.sensorManagerInstance().provideSensor(name);
477                _namedHandle = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(name, s);
478            } else if (cmd.equals(Command.RUN_WARRANT)) {
479                Warrant w = InstanceManager.getDefault(jmri.jmrit.logix.WarrantManager.class).provideWarrant(name);
480                _namedHandle = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(name, w);
481            } else {
482                OBlock b = InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager.class).getOBlock(name);
483                if (b != null) {
484                    _namedHandle = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(name, b);
485                }
486            }
487        } catch (IllegalArgumentException iae) {
488            return Bundle.getMessage("badCommand", cmd+iae.toString());
489        }
490        return null;
491    }
492
493    // _namedHandle may be of 3 different types
494    public <T extends NamedBean> void setNamedBeanHandle(NamedBeanHandle<T> bh) {
495        _namedHandle = bh;
496    }
497
498    // _namedHandle may be of 3 different types
499    public NamedBeanHandle<? extends NamedBean> getNamedBeanHandle() {
500        return _namedHandle;
501    }
502
503    public NamedBean getBean() {
504        if (_namedHandle == null) {
505            return null;
506        }
507        return _namedHandle.getBean();
508    }
509
510    public String getBeanDisplayName() {
511        if (_namedHandle == null) {
512            return null;
513        }
514        return _namedHandle.getBean().getDisplayName();
515    }
516
517    public String getBeanSystemName() {
518        if (_namedHandle == null) {
519            return null;
520        }
521        return _namedHandle.getBean().getSystemName();
522    }
523
524    @Override
525    public String toString() {
526        return "ThrottleSetting: wait " + _time + "ms then " + _command.toString()
527                + " with value " + _value.showValue() + " for bean \"" + getBeanDisplayName()
528                + "\" at trackSpeed " + getTrackSpeed() + "\"";
529    }
530
531    private static final Logger log = LoggerFactory.getLogger(ThrottleSetting.class);
532}