package net.mvla.mvhs.schedulecalendar; import android.content.Context; import android.util.Pair; import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.Moshi; import com.squareup.moshi.Types; import net.mvla.mvhs.Utils; import net.mvla.mvhs.schedulecalendar.bellschedule.BellSchedule; import net.mvla.mvhs.schedulecalendar.cache.DiskMemoryCache; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.List; import okio.Buffer; import rx.Observable; import rx.Single; import rx.functions.Func1; import rx.schedulers.Schedulers; public class ScheduleCalendarRepository { public static final int CACHE_MAX_BYTES = 4_048_576; public static final int CACHE_MAX_SIZE = 5000; private static ScheduleCalendarRepository instance; private final JsonAdapter<CalendarEvents> calendarEventsAdapter; private final JsonAdapter<List<BellSchedule>> bellScheduleListAdapter; private ScheduleCalendarDataSource model; private DiskMemoryCache<List<BellSchedule>> bellSchedulesCache; private DiskMemoryCache<CalendarEvents> calendarEventCache; private Observable<List<Event>> eventListObservable; private boolean cacheUpdated; private ScheduleCalendarRepository(Context context) { model = new ScheduleCalendarDataSource(); Moshi moshi = new Moshi.Builder().build(); calendarEventsAdapter = moshi.adapter(CalendarEvents.class); bellScheduleListAdapter = moshi.adapter(Types.newParameterizedType(List.class, BellSchedule.class)); bellSchedulesCache = new DiskMemoryCache<>(context, CACHE_MAX_BYTES, CACHE_MAX_SIZE, "bellSchedule", new DiskMemoryCache.Mapper<List<BellSchedule>>() { @Override public List<BellSchedule> fromStream(InputStream inputStream) throws IOException { return bellScheduleListAdapter.fromJson(new Buffer().readFrom(inputStream)); } @Override public void toStream(OutputStream outputStream, List<BellSchedule> bellSchedules) throws IOException { Buffer sink = new Buffer(); bellScheduleListAdapter.toJson(sink, bellSchedules); sink.writeTo(outputStream); sink.clear(); } }); calendarEventCache = new DiskMemoryCache<>(context, CACHE_MAX_BYTES, CACHE_MAX_SIZE, "calendarEvents", new DiskMemoryCache.Mapper<CalendarEvents>() { @Override public CalendarEvents fromStream(InputStream inputStream) throws IOException { return calendarEventsAdapter.fromJson(new Buffer().readFrom(inputStream)); } @Override public void toStream(OutputStream outputStream, CalendarEvents item) throws IOException { Buffer sink = new Buffer(); calendarEventsAdapter.toJson(sink, item); sink.writeTo(outputStream); sink.clear(); } }); cacheUpdated = false; } public static ScheduleCalendarRepository getInstance(Context context) { if (instance == null) { instance = new ScheduleCalendarRepository(context.getApplicationContext()); } return instance; } public Observable<List<Event>> getEventListOnDate(Calendar selectedDate) { if (eventListObservable != null) { return eventListObservable; } Observable<List<Event>> observable = calendarEventCache.get(String.valueOf(selectedDate.getTimeInMillis()), calendarEvents -> calendarEvents != null) .flatMap(calendarEvents -> Observable.just(calendarEvents.events)); Single<List<Event>> queryPutCache = model.getCalendarEvents().flatMap(calendarEvents -> { List<Event> events = calendarEvents.events; List<Event> eventsOnSelectedDay = new ArrayList<>(); List<Event> eventsOnDay = new ArrayList<>(); Calendar currDay = null; long lastDay = Long.MAX_VALUE; for (Event event : events) { Calendar eventTime = new GregorianCalendar(); eventTime.setTimeInMillis(event.startTime); if (currDay == null) { currDay = new GregorianCalendar(); currDay.clear(); currDay.set(eventTime.get(Calendar.YEAR), eventTime.get(Calendar.MONTH), eventTime.get(Calendar.DATE)); } if (Utils.sameDay(eventTime, currDay)) { eventsOnDay.add(event); } else { CalendarEvents finishedEvents = new CalendarEvents(); finishedEvents.events = new ArrayList<>(eventsOnDay); if (lastDay != Long.MAX_VALUE) { for (long i = (long) (lastDay + 8.64e+7); i < currDay.getTimeInMillis(); i = (long) (i + 8.64e+7)) { CalendarEvents item = new CalendarEvents(); item.events = new ArrayList<>(); calendarEventCache.put(String.valueOf(i), item); } } calendarEventCache.put(String.valueOf(currDay.getTimeInMillis()), finishedEvents); if (Utils.sameDay(currDay, selectedDate)) { eventsOnSelectedDay = new ArrayList<>(finishedEvents.events); } lastDay = currDay.getTimeInMillis(); eventsOnDay.clear(); eventsOnDay.add(event); currDay.clear(); currDay.set(eventTime.get(Calendar.YEAR), eventTime.get(Calendar.MONTH), eventTime.get(Calendar.DATE)); } } return Single.just(eventsOnSelectedDay); }); if (!cacheUpdated) { observable = observable.mergeWith(queryPutCache.toObservable()); } eventListObservable = observable.subscribeOn(Schedulers.io()) .doOnCompleted(() -> eventListObservable = null) .replay().autoConnect(); return eventListObservable; } public Observable<BellSchedule> getBellSchedule(Calendar selectedDate) { Observable<List<BellSchedule>> bellScheduleObservable = bellSchedulesCache.get("0", bellSchedules -> bellSchedules != null ); Single<List<BellSchedule>> bellSchedulesQuery = model.getBellSchedules() .doOnSuccess(bellSchedules -> { bellSchedulesCache.put("0", bellSchedules); }); if (!cacheUpdated) { bellScheduleObservable = bellScheduleObservable.mergeWith(bellSchedulesQuery.toObservable()); } return Observable.combineLatest( getEventListOnDate(selectedDate), bellScheduleObservable, Pair::new ).subscribeOn(Schedulers.io()) .flatMap(new Func1<Pair<List<Event>, List<BellSchedule>>, Observable<BellSchedule>>() { @Override public Observable<BellSchedule> call(Pair<List<Event>, List<BellSchedule>> eventsBellSchedule) { List<Event> calendarEvents = eventsBellSchedule.first; List<BellSchedule> bellSchedules = eventsBellSchedule.second; String defaultScheduleName = null; switch (selectedDate.get(java.util.Calendar.DAY_OF_WEEK)) { case java.util.Calendar.MONDAY: case java.util.Calendar.FRIDAY: defaultScheduleName = "Sched. A"; break; case java.util.Calendar.TUESDAY: defaultScheduleName = "Tutorial Schedule"; break; case java.util.Calendar.WEDNESDAY: defaultScheduleName = "Sched. B"; break; case java.util.Calendar.THURSDAY: defaultScheduleName = "Sched. C"; break; default: //Weekend break; } BellSchedule defaultSchedule = null; BellSchedule chosen = null; SimpleDateFormat format = new SimpleDateFormat("M/dd/yyyy"); for (BellSchedule schedule : bellSchedules) { String[] scheduleNameParts = schedule.name.split("-"); try { Date date = format.parse(scheduleNameParts[0]); Calendar calendar = Calendar.getInstance(); calendar.setTime(date); Calendar endTime = null; if (scheduleNameParts.length == 2 && !scheduleNameParts[1].isEmpty()) { endTime = Calendar.getInstance(); endTime.setTime(format.parse(scheduleNameParts[1])); } boolean inRange = endTime != null && calendar.getTimeInMillis() <= selectedDate.getTimeInMillis() && endTime.getTimeInMillis() >= selectedDate.getTimeInMillis(); if (Utils.sameDay(calendar, selectedDate) || inRange) { chosen = schedule; break; } } catch (ParseException e) { //Nope. for (Event event : calendarEvents) { String value = event.name; if (schedule.name.startsWith(value.split("\\|")[0])) { chosen = schedule; break; } } if (chosen != null) { break; } if (defaultScheduleName != null && schedule.name.startsWith(defaultScheduleName)) { defaultSchedule = schedule; } } } if (chosen == null) { chosen = defaultSchedule; } if (chosen == null) { chosen = new BellSchedule(); } cacheUpdated = true; return Observable.just(chosen); } }); } public static class CalendarEvents { public long timestamp; public List<Event> events; } public static class Event { public String name; public long startTime; public long endTime; } }