001package jmri.jmrit.logixng.actions;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.util.*;
006
007import jmri.*;
008import jmri.jmrit.logixng.*;
009import jmri.jmrit.logixng.util.LogixNG_SelectEnum;
010import jmri.jmrit.logixng.util.LogixNG_SelectDouble;
011import jmri.jmrit.logixng.util.parser.ParserException;
012import static jmri.jmrit.simpleclock.SimpleTimebase.MAXIMUM_RATE;
013import static jmri.jmrit.simpleclock.SimpleTimebase.MINIMUM_RATE;
014import jmri.util.ThreadingUtil;
015
016/**
017 * This action provides the ability to set the fast clock speed.
018 *
019 * @author Daniel Bergqvist Copyright 2021
020 * @author Dave Sand Copyright 2021
021 * @author Daniel Bergqvist Copyright 2022
022 */
023public class ActionClockRate extends AbstractDigitalAction
024        implements PropertyChangeListener {
025
026    private final LogixNG_SelectEnum<ClockState> _selectEnum =
027            new LogixNG_SelectEnum<>(this, ClockState.values(), ClockState.SetClockRate, this);
028    private final LogixNG_SelectDouble _selectSpeed =
029            new LogixNG_SelectDouble(this, 3, this, new DefaultFormatterParserValidator());
030
031
032    public ActionClockRate(String sys, String user)
033            throws BadUserNameException, BadSystemNameException {
034        super(sys, user);
035    }
036
037    @Override
038    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException {
039        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
040        String sysName = systemNames.get(getSystemName());
041        String userName = userNames.get(getSystemName());
042        if (sysName == null) sysName = manager.getAutoSystemName();
043        ActionClockRate copy = new ActionClockRate(sysName, userName);
044        copy.setComment(getComment());
045        _selectEnum.copy(copy._selectEnum);
046        _selectSpeed.copy(copy._selectSpeed);
047        return manager.registerAction(copy);
048    }
049
050    public LogixNG_SelectEnum<ClockState> getSelectEnum() {
051        return _selectEnum;
052    }
053
054    public LogixNG_SelectDouble getSelectSpeed() {
055        return _selectSpeed;
056    }
057
058    /**
059     * Convert speed to an I18N decimal string.
060     * @param locale The Locale to use for the String conversion.
061     * @param speed The speed
062     * @return speed formatted as %1.3f
063     */
064    public static String formatSpeed(Locale locale, double speed) {
065        return String.format(locale,"%1.3f", speed);
066    }
067
068    /** {@inheritDoc} */
069    @Override
070    public Category getCategory() {
071        return Category.ITEM;
072    }
073
074    /** {@inheritDoc} */
075    @Override
076    public void execute() throws JmriException {
077
078        ClockState theState = _selectEnum.evaluateEnum(getConditionalNG());
079        double theValue = _selectSpeed.evaluateValue(getConditionalNG());
080
081        jmri.Timebase timebase = InstanceManager.getDefault(jmri.Timebase.class);
082
083        ThreadingUtil.runOnLayoutWithJmriException(() -> {
084            switch(theState) {
085                case SetClockRate:
086                    try {
087                        timebase.userSetRate(theValue);
088                    } catch (TimebaseRateException e) {
089                        // Do nothing. This error is already logged as an error
090                    }
091                    break;
092
093                case IncreaseClockRate:
094                    try {
095                        timebase.userSetRate(timebase.userGetRate() + theValue);
096                    } catch (TimebaseRateException e) {
097                        // Do nothing. This error is already logged as an error
098                    }
099                    break;
100
101                case DecreaseClockRate:
102                    try {
103                        timebase.userSetRate(timebase.userGetRate() - theValue);
104                    } catch (TimebaseRateException e) {
105                        // Do nothing. This error is already logged as an error
106                    }
107                    break;
108
109                default:
110                    throw new IllegalArgumentException("Invalid clock state: " + theState.name());
111            }
112        });
113    }
114
115    @Override
116    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
117        throw new UnsupportedOperationException("Not supported.");
118    }
119
120    @Override
121    public int getChildCount() {
122        return 0;
123    }
124
125    @Override
126    public String getShortDescription(Locale locale) {
127        return Bundle.getMessage(locale, "ActionClockRate_Short");
128    }
129
130    @Override
131    public String getLongDescription(Locale locale) {
132        String value;
133        if (_selectSpeed.isDirectAddressing()) {
134            value = formatSpeed( locale, _selectSpeed.getValue());
135        } else {
136            value = _selectSpeed.getDescription(locale);
137        }
138        if (_selectEnum.isDirectAddressing()) {
139            if (_selectEnum.getEnum() == ClockState.SetClockRate) {
140                return Bundle.getMessage(locale, "ActionClockRate_LongTo", _selectEnum.getDescription(locale), value);
141            }
142            return Bundle.getMessage(locale, "ActionClockRate_LongWith", _selectEnum.getDescription(locale), value);
143        } else {
144            return Bundle.getMessage(locale, "ActionClockRate_LongTo", _selectEnum.getDescription(locale), value);
145        }
146    }
147
148    /** {@inheritDoc} */
149    @Override
150    public void setup() {
151        // Do nothing
152    }
153
154    /** {@inheritDoc} */
155    @Override
156    public void registerListenersForThisClass() {
157        _selectEnum.registerListeners();
158        _selectSpeed.registerListeners();
159    }
160
161    /** {@inheritDoc} */
162    @Override
163    public void unregisterListenersForThisClass() {
164        _selectEnum.unregisterListeners();
165        _selectSpeed.unregisterListeners();
166    }
167
168    /** {@inheritDoc} */
169    @Override
170    public void propertyChange(PropertyChangeEvent evt) {
171        getConditionalNG().execute();
172    }
173
174    /** {@inheritDoc} */
175    @Override
176    public void disposeMe() {
177    }
178
179
180    public enum ClockState {
181        SetClockRate(Bundle.getMessage("ActionClockRate_SetClockRate")),
182        IncreaseClockRate(Bundle.getMessage("ActionClockRate_IncreaseClockRate")),
183        DecreaseClockRate(Bundle.getMessage("ActionClockRate_DecreaseClockRate"));
184
185        private final String _text;
186
187        private ClockState(String text) {
188            this._text = text;
189        }
190
191        @Override
192        public String toString() {
193            return _text;
194        }
195
196    }
197
198
199    private static class DefaultFormatterParserValidator
200            extends LogixNG_SelectDouble.DefaultFormatterParserValidator {
201
202        @Override
203        public double getInitialValue() {
204            return 1.0;
205        }
206
207        @Override
208        public double parse(String str) {
209            try {
210                double value = Double.parseDouble(str);
211                if (value < MINIMUM_RATE || value > MAXIMUM_RATE) {
212                    return MINIMUM_RATE;
213                }
214                return value;
215            } catch (NumberFormatException ex) {
216                return MINIMUM_RATE;
217            }
218        }
219
220        @Override
221        public String validate(String str) {
222            try {
223                double value = Double.parseDouble(str);
224                if (value < MINIMUM_RATE || value > MAXIMUM_RATE) {
225                    return Bundle.getMessage("ActionClockRate_RangeError",
226                            MINIMUM_RATE, MAXIMUM_RATE);
227                }
228                return null;
229            } catch (NumberFormatException ex) {
230                return Bundle.getMessage("ActionClockRate_ParseError", str);
231            }
232        }
233
234    }
235
236//    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionPower.class);
237
238}