001package jmri.jmrit.logixng.expressions;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.time.Instant;
006import java.util.*;
007
008import jmri.*;
009import jmri.jmrit.logixng.*;
010import jmri.util.TimerUtil;
011
012/**
013 * This expression is a clock.
014 *
015 * @author Daniel Bergqvist Copyright 2020
016 * @author Dave Sand Copyright 2021
017 */
018public class ExpressionClock extends AbstractDigitalExpression implements PropertyChangeListener {
019
020    private Is_IsNot_Enum _is_IsNot = Is_IsNot_Enum.Is;
021    private Type _type = Type.FastClock;
022    private Timebase _fastClock;
023    private int _beginTime = 0;
024    private int _endTime = 0;
025
026    TimerTask timerTask = null;
027    private int milisInAMinute = 60000;
028
029
030    public ExpressionClock(String sys, String user) {
031        super(sys, user);
032    }
033
034    @Override
035    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException {
036        DigitalExpressionManager manager = InstanceManager.getDefault(DigitalExpressionManager.class);
037        String sysName = systemNames.get(getSystemName());
038        String userName = userNames.get(getSystemName());
039        if (sysName == null) sysName = manager.getAutoSystemName();
040        ExpressionClock copy = new ExpressionClock(sysName, userName);
041        copy.setComment(getComment());
042        copy.set_Is_IsNot(_is_IsNot);
043        copy.setType(_type);
044        copy.setRange(_beginTime, _endTime);
045        return manager.registerExpression(copy).deepCopyChildren(this, systemNames, userNames);
046    }
047
048    /** {@inheritDoc} */
049    @Override
050    public Category getCategory() {
051        return Category.ITEM;
052    }
053
054    public void set_Is_IsNot(Is_IsNot_Enum is_IsNot) {
055        _is_IsNot = is_IsNot;
056    }
057
058    public Is_IsNot_Enum get_Is_IsNot() {
059        return _is_IsNot;
060    }
061
062    public void setType(Type type) {
063        assertListenersAreNotRegistered(log, "setType");
064        _type = type;
065
066        if (_type == Type.FastClock) {
067            _fastClock = InstanceManager.getDefault(jmri.Timebase.class);
068        } else {
069            _fastClock = null;
070        }
071    }
072
073    public Type getType() {
074        return _type;
075    }
076
077    public void setRange(int beginTime, int endTime) {
078        assertListenersAreNotRegistered(log, "setRange");
079        _beginTime = beginTime;
080        _endTime = endTime;
081    }
082
083    public int getBeginTime() {
084        return _beginTime;
085    }
086
087    public int getEndTime() {
088        return _endTime;
089    }
090
091    /**
092     * Convert minutes since midnight to hh:mm.
093     * @param minutes The number of minutes from 0 to 1439.
094     * @return time formatted as hh:mm.
095     */
096    public static String formatTime(int minutes) {
097        String hhmm = "00:00";
098        if (minutes >= 0 && minutes < 1440) {
099            hhmm = String.format("%02d:%02d",
100                    minutes / 60,
101                    minutes % 60);
102        }
103        return hhmm;
104    }
105
106    /** {@inheritDoc} */
107    @Override
108    public boolean evaluate() {
109        boolean result;
110
111        Calendar currentTime = null;
112
113        switch (_type) {
114            case SystemClock:
115                currentTime = Calendar.getInstance();
116                break;
117
118            case FastClock:
119                if (_fastClock == null) return false;
120                currentTime = Calendar.getInstance();
121                currentTime.setTime(_fastClock.getTime());
122                break;
123
124            default:
125                throw new UnsupportedOperationException("_type has unknown value: " + _type.name());
126        }
127
128        int currentMinutes = (currentTime.get(Calendar.HOUR_OF_DAY) * 60) + currentTime.get(Calendar.MINUTE);
129        // check if current time is within range specified
130        if (_beginTime <= _endTime) {
131            // range is entirely within one day
132            result = (_beginTime <= currentMinutes) && (currentMinutes <= _endTime);
133        } else {
134            // range includes midnight
135            result = _beginTime <= currentMinutes || currentMinutes <= _endTime;
136        }
137
138        if (_is_IsNot == Is_IsNot_Enum.Is) {
139            return result;
140        } else {
141            return !result;
142        }
143    }
144
145    @Override
146    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
147        throw new UnsupportedOperationException("Not supported.");
148    }
149
150    @Override
151    public int getChildCount() {
152        return 0;
153    }
154
155    @Override
156    public String getShortDescription(Locale locale) {
157        return Bundle.getMessage(locale, "Clock_Short");
158    }
159
160    @Override
161    public String getLongDescription(Locale locale) {
162        switch (_type) {
163            case SystemClock:
164                return Bundle.getMessage(locale, "Clock_Long_SystemClock", _is_IsNot.toString(),
165                        ExpressionClock.formatTime(_beginTime),
166                        ExpressionClock.formatTime(_endTime));
167
168            case FastClock:
169                return Bundle.getMessage(locale, "Clock_Long_FastClock", _is_IsNot.toString(),
170                        ExpressionClock.formatTime(_beginTime),
171                        ExpressionClock.formatTime(_endTime));
172
173            default:
174                throw new RuntimeException("Unknown value of _timerType: "+_type.name());
175        }
176    }
177
178    /** {@inheritDoc} */
179    @Override
180    public void setup() {
181        // Do nothing
182    }
183
184    /** {@inheritDoc}
185     * The SystemClock listener creates a timer on the first call.  Subsequent calls
186     * enabled timer processing.
187     */
188    @Override
189    public void registerListenersForThisClass() {
190        if (!_listenersAreRegistered) {
191            switch (_type) {
192                case SystemClock:
193                    scheduleTimer();
194                    break;
195
196                case FastClock:
197                    _fastClock.addPropertyChangeListener("time", this);
198                    break;
199
200                default:
201                    throw new UnsupportedOperationException("_type has unknown value: " + _type.name());
202            }
203
204            _listenersAreRegistered = true;
205        }
206    }
207
208    /** {@inheritDoc}
209     * The SystemClock timer flag is set false to suspend processing of timer events.  The
210     * timer keeps running for the duration of the JMRI session.
211     */
212    @Override
213    public void unregisterListenersForThisClass() {
214        if (_listenersAreRegistered) {
215            switch (_type) {
216                case SystemClock:
217                    if (timerTask != null) timerTask.cancel();
218                    break;
219
220                case FastClock:
221                    if (_fastClock != null) _fastClock.removePropertyChangeListener("time", this);
222                    break;
223
224                default:
225                    throw new UnsupportedOperationException("_type has unknown value: " + _type.name());
226            }
227
228            _listenersAreRegistered = false;
229        }
230    }
231
232    private void scheduleTimer() {
233        timerTask = new TimerTask() {
234            @Override
235            public void run() {
236                propertyChange(null);
237            }
238        };
239        TimerUtil.schedule(timerTask, System.currentTimeMillis() % milisInAMinute, milisInAMinute);
240    }
241
242    /** {@inheritDoc} */
243    @Override
244    public void propertyChange(PropertyChangeEvent evt) {
245        getConditionalNG().execute();
246    }
247
248    /** {@inheritDoc} */
249    @Override
250    public void disposeMe() {
251        if (timerTask != null) timerTask.cancel();
252    }
253
254    public enum Type {
255        FastClock(Bundle.getMessage("ClockTypeFastClock")),
256        SystemClock(Bundle.getMessage("ClockTypeSystemClock"));
257
258        private final String _text;
259
260        private Type(String text) {
261            this._text = text;
262        }
263
264        @Override
265        public String toString() {
266            return _text;
267        }
268
269    }
270
271    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ExpressionClock.class);
272
273}