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