001package jmri.jmrit.logixng.actions;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.time.LocalTime;
006import java.time.format.DateTimeFormatter;
007import java.time.format.DateTimeParseException;
008import java.util.*;
009
010import jmri.*;
011import jmri.jmrit.logixng.*;
012import jmri.jmrit.logixng.util.LogixNG_SelectEnum;
013import jmri.jmrit.logixng.util.LogixNG_SelectInteger;
014import jmri.jmrit.logixng.util.parser.ParserException;
015import jmri.util.ThreadingUtil;
016
017/**
018 * This action provides the ability to set the fast clock time and start and stop the fast clock.
019 *
020 * @author Daniel Bergqvist Copyright 2021
021 * @author Dave Sand Copyright 2021
022 */
023public class ActionClock extends AbstractDigitalAction
024        implements PropertyChangeListener {
025
026    private final LogixNG_SelectEnum<ClockState> _selectEnum =
027            new LogixNG_SelectEnum<>(this, ClockState.values(), ClockState.SetClock, this);
028    private final LogixNG_SelectInteger _selectValue =
029            new LogixNG_SelectInteger(this, this, new TimeFormatterParserValidator());
030
031
032    public ActionClock(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        ActionClock copy = new ActionClock(sysName, userName);
044        copy.setComment(getComment());
045        _selectEnum.copy(copy._selectEnum);
046        _selectValue.copy(copy._selectValue);
047        return manager.registerAction(copy);
048    }
049
050    public LogixNG_SelectEnum<ClockState> getSelectEnum() {
051        return _selectEnum;
052    }
053
054    public LogixNG_SelectInteger getSelectTime() {
055        return _selectValue;
056    }
057
058    /**
059     * Convert minutes since midnight to hh:mm.
060     * @param minutes The number of minutes from 0 to 1439.
061     * @return time formatted as hh:mm.
062     */
063    public static String formatTime(int minutes) {
064        String hhmm = "00:00";
065        if (minutes >= 0 && minutes < 1440) {
066            hhmm = String.format("%02d:%02d",
067                    minutes / 60,
068                    minutes % 60);
069        }
070        return hhmm;
071    }
072
073    /** {@inheritDoc} */
074    @Override
075    public Category getCategory() {
076        return Category.ITEM;
077    }
078
079    /** {@inheritDoc} */
080    @Override
081    public void execute() throws JmriException {
082
083        ClockState theState = _selectEnum.evaluateEnum(getConditionalNG());
084        int theValue = _selectValue.evaluateValue(getConditionalNG());
085
086        jmri.Timebase timebase = InstanceManager.getDefault(jmri.Timebase.class);
087
088        ThreadingUtil.runOnLayoutWithJmriException(() -> {
089            switch(theState) {
090                case SetClock:
091                    Calendar cal = Calendar.getInstance();
092                    cal.setTime(timebase.getTime());
093                    cal.set(Calendar.HOUR_OF_DAY, theValue / 60);
094                    cal.set(Calendar.MINUTE, theValue % 60);
095                    cal.set(Calendar.SECOND, 0);
096                    timebase.userSetTime(cal.getTime());
097                    break;
098
099                case StartClock:
100                    timebase.setRun(true);
101                    break;
102
103                case StopClock:
104                    timebase.setRun(false);
105                    break;
106
107                default:
108                    throw new IllegalArgumentException("Invalid clock state: " + theState.name());
109            }
110        });
111    }
112
113    @Override
114    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
115        throw new UnsupportedOperationException("Not supported.");
116    }
117
118    @Override
119    public int getChildCount() {
120        return 0;
121    }
122
123    @Override
124    public String getShortDescription(Locale locale) {
125        return Bundle.getMessage(locale, "ActionClock_Short");
126    }
127
128    @Override
129    public String getLongDescription(Locale locale) {
130        String value;
131        if (_selectValue.isDirectAddressing()) {
132            value = formatTime(_selectValue.getValue());
133        } else {
134            value = _selectValue.getDescription(locale);
135        }
136        if (_selectEnum.isDirectAddressing()) {
137            if (_selectEnum.getEnum() == ClockState.SetClock) {
138                return Bundle.getMessage(locale, "ActionClock_LongTime", _selectEnum.getDescription(locale), value);
139            }
140            return Bundle.getMessage(locale, "ActionClock_Long", _selectEnum.getDescription(locale), value);
141        } else {
142            return Bundle.getMessage(locale, "ActionClock_LongTimeIndirect", _selectEnum.getDescription(locale), value);
143        }
144    }
145
146    /** {@inheritDoc} */
147    @Override
148    public void setup() {
149        // Do nothing
150    }
151
152    /** {@inheritDoc} */
153    @Override
154    public void registerListenersForThisClass() {
155        _selectEnum.registerListeners();
156        _selectValue.registerListeners();
157    }
158
159    /** {@inheritDoc} */
160    @Override
161    public void unregisterListenersForThisClass() {
162        _selectEnum.unregisterListeners();
163        _selectValue.unregisterListeners();
164    }
165
166    /** {@inheritDoc} */
167    @Override
168    public void propertyChange(PropertyChangeEvent evt) {
169        getConditionalNG().execute();
170    }
171
172    /** {@inheritDoc} */
173    @Override
174    public void disposeMe() {
175    }
176
177
178    public enum ClockState {
179        SetClock(Bundle.getMessage("ActionClock_SetClock")),
180        StartClock(Bundle.getMessage("ActionClock_StartClock")),
181        StopClock(Bundle.getMessage("ActionClock_StopClock"));
182
183        private final String _text;
184
185        private ClockState(String text) {
186            this._text = text;
187        }
188
189        @Override
190        public String toString() {
191            return _text;
192        }
193
194    }
195
196
197    private static class TimeFormatterParserValidator
198            implements LogixNG_SelectInteger.FormatterParserValidator {
199
200        @Override
201        public int getInitialValue() {
202            return 0;
203        }
204
205        @Override
206        public String format(int value) {
207            return ActionClock.formatTime(value);
208        }
209
210        @Override
211        public int parse(String str) {
212            int minutes;
213
214            try {
215                minutes = Integer.parseInt(str);
216                if (minutes < 0 || minutes > 1439) {
217                    return 0;
218                }
219                return minutes;
220            } catch (NumberFormatException e) {
221                // Do nothing
222            }
223
224            LocalTime newHHMM;
225            try {
226                newHHMM = LocalTime.parse(str.trim(), DateTimeFormatter.ofPattern("H:mm"));
227                minutes = newHHMM.getHour() * 60 + newHHMM.getMinute();
228                if (minutes < 0 || minutes > 1439) {
229                    return 0;
230                }
231                return minutes;
232            } catch (DateTimeParseException ex) {
233                return 0;
234            }
235        }
236
237        @Override
238        public String validate(String str) {
239            int minutes;
240
241            try {
242                minutes = Integer.parseInt(str);
243                if (minutes < 0 || minutes > 1439) {
244                    return Bundle.getMessage("ActionClock_RangeError");
245                }
246                return null;
247            } catch (NumberFormatException e) {
248                // Do nothing
249            }
250
251            LocalTime newHHMM;
252            try {
253                newHHMM = LocalTime.parse(str.trim(), DateTimeFormatter.ofPattern("H:mm"));
254                minutes = newHHMM.getHour() * 60 + newHHMM.getMinute();
255                if (minutes < 0 || minutes > 1439) {
256                    return Bundle.getMessage("ActionClock_RangeError");
257                }
258            } catch (DateTimeParseException ex) {
259                return Bundle.getMessage("ActionClock_ParseError", ex.getParsedString());
260            }
261            return null;
262        }
263
264    }
265
266//    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionPower.class);
267
268}