package com.tfltravelalerts.model; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.HashSet; import java.util.Set; import android.util.Log; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.tfltravelalerts.alerts.service.AlertIdGenerator; public class LineStatusAlert { private static final String LOG_TAG = "LineStatusAlertUtil"; private final static int LOOK_AHEAD_FOR_ALERT_TIME = 60; // unit: minutes private final static int LOOK_BEHIND_FOR_ALERT_TIME = -30; // unit: minutes private final int mId; private final String mTitle; private final ImmutableSet<Line> mLines; private final ImmutableSet<Day> mDays; private final Time mTime; private final boolean mOnlyNotifyForDisruptions; private LineStatusAlert(int id, String title, Set<Line> lines, Set<Day> days, Time time, boolean onlyNotifyForDisruptions) { mId = id; mTitle = title; mLines = ImmutableSet.copyOf(lines); mDays = ImmutableSet.copyOf(days); mTime = time; mOnlyNotifyForDisruptions = onlyNotifyForDisruptions; } public String getTitle() { return mTitle; } public ImmutableSet<Line> getLines() { return mLines; } public ImmutableSet<Day> getDays() { return mDays; } public Time getTime() { return mTime; } public int getId() { return mId; } public boolean onlyNotifyForDisruptions() { return mOnlyNotifyForDisruptions; } @Override public String toString() { return "#" + getId() + " " + mTitle; } public static Builder builder(int id) { return new Builder(id); } public static Builder builder(LineStatusAlert alert) { return new Builder(alert); } public static class Builder { private int mId = -1; private String mTitle; private Set<Line> mLines = new HashSet<Line>(); private Set<Day> mDays = new HashSet<Day>(); private Time mTime; private boolean mOnlyNotifyForDisruptions; public Builder(LineStatusAlert alert) { if (alert.getId() == -1) { mId = AlertIdGenerator.generateId(); } else { mId = alert.getId(); } mTitle = alert.getTitle(); mLines = Sets.newHashSet(alert.getLines()); mDays = Sets.newHashSet(alert.getDays()); mTime = alert.getTime(); } public Builder(int id) { if (id == -1) { mId = AlertIdGenerator.generateId(); } else { mId = id; } } public Builder() { mId = AlertIdGenerator.generateId(); } public Builder title(String title) { mTitle = title; return this; } public Builder addLine(Line... line) { mLines.addAll(Arrays.asList(line)); return this; } public Builder addLine(Collection<Line> lines) { mLines.addAll(lines); return this; } public Builder clearLines() { mLines.clear(); return this; } public Builder addDays(Collection<Day> days) { mDays.addAll(days); return this; } public Builder addDay(Day... day) { mDays.addAll(Arrays.asList(day)); return this; } public Builder clearDays() { mDays.clear(); return this; } public Builder setTime(Time time) { mTime = time; return this; } public Builder setOnlyNotifyForDisruptions(boolean onlyNotifyForDisruptions) { mOnlyNotifyForDisruptions = onlyNotifyForDisruptions; return this; } public LineStatusAlert build() { return new LineStatusAlert(mId, mTitle, mLines, mDays, mTime, mOnlyNotifyForDisruptions); } } @Override public boolean equals(Object o) { if (o instanceof LineStatusAlert) { LineStatusAlert otherAlert = (LineStatusAlert) o; return getId() == otherAlert.getId(); } return false; } @Override public int hashCode() { return getId(); } public long getNextAlertTime(long fromTime) { Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(fromTime); while (calendar.get(Calendar.SECOND) != 0) { calendar.add(Calendar.SECOND, 1); } while (calendar.get(Calendar.MINUTE) != mTime.getMinute()) { calendar.add(Calendar.MINUTE, 1); } while (calendar.get(Calendar.HOUR_OF_DAY) != mTime.getHour()) { calendar.add(Calendar.HOUR_OF_DAY, 1); } Set<Integer> calendarDays = Sets.newHashSet(); for (Day day : mDays) { calendarDays.add(day.getCalendarDay()); } if (calendarDays.size() > 0) { // if we don't do this check we would go into an infinite loop while (!isNextAlarmDate(calendar, calendarDays, fromTime)) { calendar.add(Calendar.DATE, 1); } } else { Log.e("LineStatusAlert", "getNextAlertTime: there are no days in alert " + toString()); } calendar.add(Calendar.MINUTE, -LOOK_AHEAD_FOR_ALERT_TIME); return calendar.getTimeInMillis(); } /** * Given that we trigger alarms LOOK_AHEAD_FOR_ALERT_TIME before their * specified time, when we are looking for the next alarm date we need to * make sure this time/date is at least 1 hour into future because otherwise * it would have triggered already */ private boolean isNextAlarmDate(Calendar calendar, Set<Integer> calendarDays, long fromTime) { boolean dayEnabled = calendarDays.contains(calendar.get(Calendar.DAY_OF_WEEK)); boolean inTheFuture = calendar.getTimeInMillis() - fromTime > LOOK_AHEAD_FOR_ALERT_TIME * 60 * 1000; return dayEnabled && inTheFuture; } public boolean isActive(DayTime now) { return alertActiveForTime(this, now); } public static boolean alertActiveForTime(LineStatusAlert alert, DayTime queryTime) { Time time = alert.getTime(); if (time == null) { Log.w(LOG_TAG, "alertActiveForTime: alert time is null; returning false"); return false; } DayTime alertTime = new DayTime(null, time); for (Day day : alert.getDays()) { alertTime.setDay(day); int timeToAlert = queryTime.differenceTo(alertTime); if (timeToAlert >= LOOK_BEHIND_FOR_ALERT_TIME && timeToAlert <= LOOK_AHEAD_FOR_ALERT_TIME) { return true; } } return false; } }