package com.schneeloch.bostonbusmap_library.data;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import android.os.Parcel;
import android.os.Parcelable;
import com.schneeloch.bostonbusmap_library.transit.TransitSystem;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
public class TimeBounds implements Parcelable {
private final ImmutableMap<Integer, TimeSpan> bounds;
private final String routeTitle;
private static final int MONDAY = 0x1;
private static final int TUESDAY = 0x2;
private static final int WEDNESDAY = 0x4;
private static final int THURSDAY = 0x8;
private static final int FRIDAY = 0x10;
private static final int SATURDAY = 0x20;
private static final int SUNDAY = 0x40;
private static final int WEEKDAYS = MONDAY | TUESDAY | WEDNESDAY | THURSDAY | FRIDAY;
private static final int[] boundsOrder = new int[] {
WEEKDAYS, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
};
private TimeBounds(String routeTitle, Builder builder) {
this.bounds = ImmutableMap.copyOf(builder.bounds);
this.routeTitle = routeTitle;
}
public static class Builder
{
private final Map<Integer, TimeSpan> bounds = Maps.newHashMap();
public void add(int weekdaysBits, int start, int end) {
bounds.put(weekdaysBits, new TimeSpan(start, end));
}
public TimeBounds build(String routeTitle) {
return new TimeBounds(routeTitle, this);
}
}
private static class TimeSpan
{
public final int begin;
public final int end;
public TimeSpan(int begin, int end) {
this.begin = begin;
this.end = end;
}
}
private static boolean doesRouteRunOnDayOfWeek(int dayOfWeek, int weekdayBits) {
switch (dayOfWeek) {
case Calendar.MONDAY:
if ((weekdayBits & MONDAY) == 0) {
return false;
}
break;
case Calendar.TUESDAY:
if ((weekdayBits & TUESDAY) == 0) {
return false;
}
break;
case Calendar.WEDNESDAY:
if ((weekdayBits & WEDNESDAY) == 0) {
return false;
}
break;
case Calendar.THURSDAY:
if ((weekdayBits & THURSDAY) == 0) {
return false;
}
break;
case Calendar.FRIDAY:
if ((weekdayBits & FRIDAY) == 0) {
return false;
}
break;
case Calendar.SATURDAY:
if ((weekdayBits & SATURDAY) == 0) {
return false;
}
break;
case Calendar.SUNDAY:
if ((weekdayBits & SUNDAY) == 0) {
return false;
}
break;
default:
throw new RuntimeException("Calendar.DAYOFWEEK returned unexpected value");
}
return true;
}
/**
* Checks if route is running at the given time
* @param calendar Some calendar, probably today's date, in
* @return
*/
public boolean isRouteRunning(Calendar calendar) {
if (bounds.size() == 0) {
// this is a just in case measure
return true;
}
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
int currentMinute = calendar.get(Calendar.MINUTE);
int currentSecond = calendar.get(Calendar.SECOND);
int secondsFromMidnight = currentSecond + 60*currentMinute + 60*60*currentHour;
/**
* end can be greater than 24 hours, if the route ends at 1:30am for instance
*
* check if secondsFromMidnight after start and before end for today's day of week
* also check for day of week = yesterday if end > 24 hours
*/
for (Integer weekdayBitsObj : bounds.keySet()) {
TimeSpan timeSpan = bounds.get(weekdayBitsObj);
int weekdayBits = weekdayBitsObj;
if (doesRouteRunOnDayOfWeek(dayOfWeek, weekdayBits)) {
if (secondsFromMidnight >= timeSpan.begin && secondsFromMidnight < timeSpan.end) {
return true;
}
}
else if (timeSpan.end > 24*60*60) {
int yesterday = calcYesterday(dayOfWeek);
for (Integer yesterdayWeekdayBitsObj : bounds.keySet()) {
int yesterdayWeekdayBits = yesterdayWeekdayBitsObj;
if (doesRouteRunOnDayOfWeek(yesterday, yesterdayWeekdayBits)) {
if (secondsFromMidnight < timeSpan.end - 24*60*60) {
return true;
}
}
}
}
}
return false;
}
/**
* Like calendar.roll(Calendar.DAY_OF_WEEK, false), but without changing calendar object
* @param i
* @return
*/
private int calcYesterday(int today) {
switch (today) {
case Calendar.MONDAY:
return Calendar.SUNDAY;
case Calendar.TUESDAY:
return Calendar.MONDAY;
case Calendar.WEDNESDAY:
return Calendar.TUESDAY;
case Calendar.THURSDAY:
return Calendar.WEDNESDAY;
case Calendar.FRIDAY:
return Calendar.THURSDAY;
case Calendar.SATURDAY:
return Calendar.FRIDAY;
case Calendar.SUNDAY:
return Calendar.SATURDAY;
default:
throw new RuntimeException("Unexpected day of week");
}
}
public static com.schneeloch.bostonbusmap_library.data.TimeBounds.Builder builder() {
return new com.schneeloch.bostonbusmap_library.data.TimeBounds.Builder();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(routeTitle);
dest.writeInt(bounds.size());
for (Integer weekdays : bounds.keySet()) {
TimeSpan span = bounds.get(weekdays);
dest.writeInt(weekdays);
dest.writeInt(span.begin);
dest.writeInt(span.end);
}
}
public static final Parcelable.Creator<TimeBounds> CREATOR = new Creator<TimeBounds>() {
@Override
public TimeBounds createFromParcel(Parcel source) {
Builder builder = new Builder();
String route = source.readString();
int size = source.readInt();
for (int i = 0; i < size; i++) {
int weekdays = source.readInt();
int start = source.readInt();
int end = source.readInt();
builder.add(weekdays, start, end);
}
return builder.build(route);
}
@Override
public TimeBounds[] newArray(int size) {
return new TimeBounds[size];
}
};
private void appendTimeBound(int weekdays, StringBuilder ret) {
TimeSpan span = bounds.get(weekdays);
List<String> dayStrings = Lists.newArrayList();
if ((weekdays & WEEKDAYS) == WEEKDAYS) {
dayStrings.add("Weekdays");
}
else {
if ((weekdays & MONDAY) != 0) {
dayStrings.add("Monday");
}
if ((weekdays & TUESDAY) != 0) {
dayStrings.add("Tuesday");
}
if ((weekdays & WEDNESDAY) != 0) {
dayStrings.add("Wednesday");
}
if ((weekdays & THURSDAY) != 0) {
dayStrings.add("Thursday");
}
if ((weekdays & FRIDAY) != 0) {
dayStrings.add("Friday");
}
}
if ((weekdays & SATURDAY) != 0) {
dayStrings.add("Saturday");
}
if ((weekdays & SUNDAY) != 0) {
dayStrings.add("Sunday");
}
ret.append(Joiner.on(", ").join(dayStrings)).append(" - ");
ret.append(makeTimeString(span.begin)).append(" until ").append(makeTimeString(span.end)).append("<br />");
}
public String makeSnippet() {
StringBuilder ret = new StringBuilder("Schedule for route ").append(routeTitle).append(": <br/>");
for (int weekdays : boundsOrder) {
if (bounds.containsKey(weekdays)) {
appendTimeBound(weekdays, ret);
}
}
return ret.toString();
}
private String makeTimeString(int secondsFromMidnight) {
int seconds = secondsFromMidnight % 60;
int totalMinutes = secondsFromMidnight / 60;
int minutes = totalMinutes % 60;
int totalHours = totalMinutes / 60;
int hours = totalHours % 24;
DateFormat format = TransitSystem.getDefaultTimeFormat();
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, hours);
calendar.set(Calendar.MINUTE, minutes);
calendar.set(Calendar.SECOND, seconds);
String ret = format.format(calendar.getTime());
return ret;
}
public String getRouteTitle() {
return routeTitle;
}
}