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 a decimal string.
060     * @param speed The speed
061     * @return speed formatted as %1.3f
062     */
063    public static String formatSpeed(double speed) {
064        return String.format("%1.3f", speed);
065    }
066
067    /** {@inheritDoc} */
068    @Override
069    public Category getCategory() {
070        return Category.ITEM;
071    }
072
073    /** {@inheritDoc} */
074    @Override
075    public void execute() throws JmriException {
076
077        ClockState theState = _selectEnum.evaluateEnum(getConditionalNG());
078        double theValue = _selectSpeed.evaluateValue(getConditionalNG());
079
080        jmri.Timebase timebase = InstanceManager.getDefault(jmri.Timebase.class);
081
082        ThreadingUtil.runOnLayoutWithJmriException(() -> {
083            switch(theState) {
084                case SetClockRate:
085                    try {
086                        timebase.userSetRate(theValue);
087                    } catch (TimebaseRateException e) {
088                        // Do nothing. This error is already logged as an error
089                    }
090                    break;
091
092                case IncreaseClockRate:
093                    try {
094                        timebase.userSetRate(timebase.userGetRate() + theValue);
095                    } catch (TimebaseRateException e) {
096                        // Do nothing. This error is already logged as an error
097                    }
098                    break;
099
100                case DecreaseClockRate:
101                    try {
102                        timebase.userSetRate(timebase.userGetRate() - theValue);
103                    } catch (TimebaseRateException e) {
104                        // Do nothing. This error is already logged as an error
105                    }
106                    break;
107
108                default:
109                    throw new IllegalArgumentException("Invalid clock state: " + theState.name());
110            }
111        });
112    }
113
114    @Override
115    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
116        throw new UnsupportedOperationException("Not supported.");
117    }
118
119    @Override
120    public int getChildCount() {
121        return 0;
122    }
123
124    @Override
125    public String getShortDescription(Locale locale) {
126        return Bundle.getMessage(locale, "ActionClockRate_Short");
127    }
128
129    @Override
130    public String getLongDescription(Locale locale) {
131        String value;
132        if (_selectSpeed.isDirectAddressing()) {
133            value = formatSpeed(_selectSpeed.getValue());
134        } else {
135            value = _selectSpeed.getDescription(locale);
136        }
137        if (_selectEnum.isDirectAddressing()) {
138            if (_selectEnum.getEnum() == ClockState.SetClockRate) {
139                return Bundle.getMessage(locale, "ActionClockRate_LongTo", _selectEnum.getDescription(locale), value);
140            }
141            return Bundle.getMessage(locale, "ActionClockRate_LongWith", _selectEnum.getDescription(locale), value);
142        } else {
143            return Bundle.getMessage(locale, "ActionClockRate_LongTo", _selectEnum.getDescription(locale), value);
144        }
145    }
146
147    /** {@inheritDoc} */
148    @Override
149    public void setup() {
150        // Do nothing
151    }
152
153    /** {@inheritDoc} */
154    @Override
155    public void registerListenersForThisClass() {
156        _selectEnum.registerListeners();
157        _selectSpeed.registerListeners();
158    }
159
160    /** {@inheritDoc} */
161    @Override
162    public void unregisterListenersForThisClass() {
163        _selectEnum.unregisterListeners();
164        _selectSpeed.unregisterListeners();
165    }
166
167    /** {@inheritDoc} */
168    @Override
169    public void propertyChange(PropertyChangeEvent evt) {
170        getConditionalNG().execute();
171    }
172
173    /** {@inheritDoc} */
174    @Override
175    public void disposeMe() {
176    }
177
178
179    public enum ClockState {
180        SetClockRate(Bundle.getMessage("ActionClockRate_SetClockRate")),
181        IncreaseClockRate(Bundle.getMessage("ActionClockRate_IncreaseClockRate")),
182        DecreaseClockRate(Bundle.getMessage("ActionClockRate_DecreaseClockRate"));
183
184        private final String _text;
185
186        private ClockState(String text) {
187            this._text = text;
188        }
189
190        @Override
191        public String toString() {
192            return _text;
193        }
194
195    }
196
197
198    private static class DefaultFormatterParserValidator
199            extends LogixNG_SelectDouble.DefaultFormatterParserValidator {
200
201        @Override
202        public double getInitialValue() {
203            return 1.0;
204        }
205
206        @Override
207        public double parse(String str) {
208            try {
209                double value = Double.parseDouble(str);
210                if (value < MINIMUM_RATE || value > MAXIMUM_RATE) {
211                    return MINIMUM_RATE;
212                }
213                return value;
214            } catch (NumberFormatException ex) {
215                return MINIMUM_RATE;
216            }
217        }
218
219        @Override
220        public String validate(String str) {
221            try {
222                double value = Double.parseDouble(str);
223                if (value < MINIMUM_RATE || value > MAXIMUM_RATE) {
224                    return Bundle.getMessage("ActionClockRate_RangeError",
225                            MINIMUM_RATE, MAXIMUM_RATE);
226                }
227                return null;
228            } catch (NumberFormatException ex) {
229                return Bundle.getMessage("ActionClockRate_ParseError", str);
230            }
231        }
232
233    }
234
235//    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionPower.class);
236
237}