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