package com.tevinjeffrey.rutgersct.rutgersapi;
import android.support.annotation.NonNull;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.tevinjeffrey.rutgersct.rutgersapi.exceptions.RutgersServerIOException;
import com.tevinjeffrey.rutgersct.rutgersapi.model.Course;
import com.tevinjeffrey.rutgersct.rutgersapi.model.Request;
import com.tevinjeffrey.rutgersct.rutgersapi.model.Subject;
import com.tevinjeffrey.rutgersct.rutgersapi.model.SystemMessage;
import com.tevinjeffrey.rutgersct.rutgersapi.utils.UrlUtils;
import com.tevinjeffrey.rutgersct.utils.BackgroundThread;
import com.tevinjeffrey.rutgersct.utils.RxUtils;
import java.util.List;
import rx.Observable;
import rx.Scheduler;
import rx.Subscriber;
import rx.functions.Action1;
import rx.functions.Func1;
import static com.tevinjeffrey.rutgersct.rutgersapi.model.Course.Section;
public class RetroRutgers {
private static final int SERVER_RETRY_COUNT = 3;
private static final int RETRY_DELAY_MILLIS = 3000;
private final RetroRutgersService mRetroRutgersService;
private final Scheduler mBackgroundThread;
final List<Subject> mSubjectsList;
public RetroRutgers(RetroRutgersService retroRutgersService, @BackgroundThread Scheduler backgroundThread) {
this.mBackgroundThread = backgroundThread;
this.mRetroRutgersService = retroRutgersService;
mSubjectsList = initSubjectList();
}
public Observable<List<Subject>> getSubjects(Request request) {
return mRetroRutgersService.getSubjects(UrlUtils.buildSubjectQuery(request));
}
public Observable<SystemMessage> getSystemMessage() {
return mRetroRutgersService.getSystemMessage();
}
public Observable<Course.Section> getTrackedSections(final List<Request> allTrackedSections) {
return Observable.from(allTrackedSections)
.flatMap(new Func1<Request, Observable<Section>>() {
@Override
public Observable<Section> call(final Request request) {
return getCourses(request)
//Emit the sections within the course that was emitted.
.flatMap(new Func1<List<Course>, Observable<Section>>() {
@Override
public Observable<Section> call(final List<Course> courseList) {
return Observable.from(courseList)
.flatMap(new Func1<Course, Observable<Section>>() {
@Override
public Observable<Section> call(Course course) {
return Observable.from(course.getSections());
}
});
}
})
//Filters out the sections we were not looking for. If the condition
//true the item will be allow through
.filter(new Func1<Section, Boolean>() {
@Override
public Boolean call(Section section) {
return section.getIndex().equals(request.getIndex());
}
})
.subscribeOn(mBackgroundThread);
}
})
// Convert every completed request in into a list and check if all were completed successfully.
// Sometimes the servers bug out and course and section information will not be available.
// Earlier in the the flow, this would simple result in a JsonSyntaxException or an
// RutgersDataException. However, it can make it to this point where no section has been
// found. Instead of emiting an empty observable, I converted it to a list whose size
// will then be compared to the original list of requests. Emit any items, before
// deciding wheter or not to pass an exeception to onError(). Comparing by the size of the
// retrieved list is an indication that all requests weren't made.
.toList()
.flatMap(new Func1<List<Course.Section>, Observable<Course.Section>>() {
@Override
public Observable<Course.Section> call(final List<Course.Section> sectionList) {
return Observable.create(new Observable.OnSubscribe<List<Course.Section>>() {
@Override
public void call(Subscriber<? super List<Course.Section>> subscriber) {
if (!subscriber.isUnsubscribed()) {
subscriber.onNext(sectionList);
if (sectionList.size() < allTrackedSections.size()) {
subscriber.onError(new RutgersServerIOException("Could not retrieve all sections"));
} else {
subscriber.onCompleted();
}
}
}
}).flatMap(new Func1<List<Course.Section>, Observable<Course.Section>>() {
@Override
public Observable<Course.Section> call(List<Course.Section> sectionList) {
return Observable.from(sectionList);
}
});
}
});
}
public Observable<List<Course>> getCourses(final Request request) {
return mRetroRutgersService.getCourses(UrlUtils.buildCourseQuery(request))
.subscribeOn(mBackgroundThread)
.flatMap(new Func1<List<Course>, Observable<Course>>() {
@Override
public Observable<Course> call(final List<Course> courses) {
return configureCourses(courses, request);
}
}).toSortedList()
.flatMap(new Func1<List<Course>, Observable<? extends List<Course>>>() {
@Override
public Observable<? extends List<Course>> call(final List<Course> courseList) {
return Observable.create(new Observable.OnSubscribe<List<Course>>() {
@Override
public void call(Subscriber<? super List<Course>> subscriber) {
if (!subscriber.isUnsubscribed()) {
if (courseList.isEmpty()) {
subscriber.onError(new RutgersServerIOException());
} else {
subscriber.onNext(courseList);
subscriber.onCompleted();
}
}
}
});
}
})
.retryWhen(new RxUtils.RetryWithDelay(SERVER_RETRY_COUNT, RETRY_DELAY_MILLIS));
}
private Observable<Course> configureCourses(List<Course> courses, final Request request) {
return Observable.from(courses)
.flatMap(filterUnprintedSections())
.filter(filterEmptySections())
.map(setSubjectInCourse())
.map(setRequestAndStubCourseInSection(request));
}
@NonNull
private Func1<Course, Boolean> filterEmptySections() {
return new Func1<Course, Boolean>() {
@Override
public Boolean call(Course course) {
return course.getSectionsTotal() > 0;
}
};
}
private Func1<Course, Observable<Course>> filterUnprintedSections() {
return new Func1<Course, Observable<Course>>() {
@Override
public Observable<Course> call(final Course course) {
return Observable.from(course.getSections())
.filter(new Func1<Course.Section, Boolean>() {
@Override
public Boolean call(Section section) {
return section.isPrinted();
}
}).toList()
.map(new Func1<List<Section>, Course>() {
@Override
public Course call(List<Section> sections) {
course.setSections(sections);
return course;
}
});
}
};
}
private Func1<Course, Course> setRequestAndStubCourseInSection(final Request request) {
return new Func1<Course, Course>() {
@Override
public Course call(final Course course) {
Observable.from(course.getSections())
.forEach(new Action1<Course.Section>() {
@Override
public void call(Section section) {
section.setRequest(request);
section.setCourse(new Course(course));
}
});
return course;
}
};
}
private Func1<Course, Course> setSubjectInCourse() {
return new Func1<Course, Course>() {
@Override
public Course call(Course course) {
course.setEnclosingSubject(getSubjectFromJson(course.getSubject()));
return course;
}
};
}
public Subject getSubjectFromJson(String code) {
List<Subject> subjectsList = mSubjectsList;
Subject temp;
for (int i = 0, size = subjectsList.size(); i < size; i++) {
temp = subjectsList.get(i);
if (temp.getCode().equals(code)) {
return temp;
}
}
return null;
}
private List<Subject> initSubjectList() {
return new Gson().fromJson(RetroRutgersService.SUBJECT_JSON, new TypeToken<List<Subject>>() {
}.getType());
}
}