package org.edx.mobile.services;
import android.util.LruCache;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.edx.mobile.interfaces.SectionItemInterface;
import org.edx.mobile.logger.Logger;
import org.edx.mobile.model.Filter;
import org.edx.mobile.model.api.IPathNode;
import org.edx.mobile.model.api.LectureModel;
import org.edx.mobile.model.api.SectionEntry;
import org.edx.mobile.model.api.SummaryModel;
import org.edx.mobile.model.api.VideoResponseModel;
import org.edx.mobile.model.course.BlockModel;
import org.edx.mobile.model.course.BlockType;
import org.edx.mobile.model.course.CourseComponent;
import org.edx.mobile.model.course.CourseStructureV1Model;
import org.edx.mobile.model.course.DiscussionBlockModel;
import org.edx.mobile.model.course.DiscussionData;
import org.edx.mobile.model.course.HasDownloadEntry;
import org.edx.mobile.model.course.HtmlBlockModel;
import org.edx.mobile.model.course.IBlock;
import org.edx.mobile.model.course.VideoBlockModel;
import org.edx.mobile.model.course.VideoData;
import org.edx.mobile.model.course.VideoInfo;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A central place for course data model transformation
*/
@Singleton
public class CourseManager {
protected final Logger logger = new Logger(getClass().getName());
private LruCache<String, CourseComponent> cachedComponent;
@Inject
ServiceManager serviceManager;
public CourseManager(){
cachedComponent = new LruCache<>(1);
}
public CourseComponent getCourseByCourseId(String courseId){
CourseComponent component = cachedComponent.get(courseId);
if ( component != null )
return component;
try {
component = serviceManager.getCourseStructureFromCache(courseId);
cachedComponent.put(courseId,component);
} catch (Exception e) {
logger.error(e);
}
return component;
}
public CourseComponent getComponentById(String courseId, final String componentId){
CourseComponent courseComponent = getCourseByCourseId(courseId);
if ( courseComponent == null )
return null;
return courseComponent.find(new Filter<CourseComponent>() {
@Override
public boolean apply(CourseComponent courseComponent) {
return componentId.equals(courseComponent.getId());
}
});
}
/**
* Mapping from raw data structure from getCourseStructure() API
* @param courseStructureV1Model
* @return
*/
public static IBlock normalizeCourseStructure(CourseStructureV1Model courseStructureV1Model, String courseId){
BlockModel topBlock = courseStructureV1Model.getBlockById(courseStructureV1Model.root);
CourseComponent course = new CourseComponent(topBlock, null);
course.setCourseId(courseId);
for (BlockModel m : courseStructureV1Model.getDescendants(topBlock)) {
normalizeCourseStructure(courseStructureV1Model,m,course);
}
return course;
}
private static void normalizeCourseStructure(CourseStructureV1Model courseStructureV1Model,
BlockModel block,
CourseComponent parent) {
if (block.isContainer()) {
CourseComponent child = new CourseComponent(block, parent);
for (BlockModel m : courseStructureV1Model.getDescendants(block)) {
normalizeCourseStructure(courseStructureV1Model, m, child);
}
} else {
if (BlockType.VIDEO == block.type && block.data instanceof VideoData) {
new VideoBlockModel(block, parent);
} else if (BlockType.DISCUSSION == block.type && block.data instanceof DiscussionData) {
new DiscussionBlockModel(block, parent);
} else { //everything else.. we fallback to html component
new HtmlBlockModel(block, parent);
}
}
}
/**
* we map the new course outline data to old data model.
* TODO : Ideally we should update all the code to match the new data model.
* @param courseComponent
* @return
*/
public static List<SectionItemInterface> mappingAllVideoResponseModelFrom(CourseComponent courseComponent, Filter<VideoResponseModel> filter){
List<SectionItemInterface> items = new ArrayList<>();
for(HasDownloadEntry item : courseComponent.getVideos()){
VideoResponseModel model = mappingVideoResponseModelFrom((VideoBlockModel)item);
if ( filter == null )
items.add( model );
else {
if (filter.apply(model)){
items.add( model );
}
}
}
return items;
}
/**
* from new VideoBlockModel to legacy VideoRsponseModel
* @param videoBlockModel
* @return
*/
public static VideoResponseModel mappingVideoResponseModelFrom(VideoBlockModel videoBlockModel){
VideoResponseModel model = new VideoResponseModel();
model.setCourseId(videoBlockModel.getCourseId());
SummaryModel summaryModel = mappingSummaryModelFrom(videoBlockModel);
model.setSummary(summaryModel);
model.videoBlockModel = videoBlockModel;
model.setSectionUrl(videoBlockModel.getParent().getBlockUrl());
model.setUnitUrl(videoBlockModel.getBlockUrl());
return model;
}
private static SummaryModel mappingSummaryModelFrom(VideoBlockModel videoBlockModel){
SummaryModel model = new SummaryModel();
model.setType(videoBlockModel.getType());
model.setDisplayName(videoBlockModel.getDisplayName());
model.setDuration((int)videoBlockModel.getData().duration);
model.setOnlyOnWeb(videoBlockModel.getData().onlyOnWeb);
model.setId(videoBlockModel.getId());
final VideoInfo videoInfo = videoBlockModel.getData().encodedVideos.getPreferredVideoInfo();
if (null != videoInfo) {
model.setVideoUrl(videoInfo.url);
model.setSize(videoInfo.fileSize);
}
model.setTranscripts(videoBlockModel.getData().transcripts);
//FIXME = is this field missing?
// private EncodingsModel encodings;
return model;
}
/**
* from new CourseComponent to legacy data structure.
* @param courseComponent
* @return
*/
public static Map<String, SectionEntry> mappingCourseHierarchyFrom(CourseComponent courseComponent){
Map<String, SectionEntry> map = new HashMap<>();
for(IBlock block : courseComponent.getChildren()){
CourseComponent chapter = (CourseComponent)block;
SectionEntry entry = new SectionEntry();
entry.chapter = chapter.getDisplayName();
entry.isChapter = true;
entry.section_url = chapter.getBlockUrl();
map.put(entry.chapter, entry);
for( IBlock subBlock : chapter.getChildren() ){
CourseComponent section = (CourseComponent)subBlock;
entry.sections.put(section.getDisplayName(),
(ArrayList)CourseManager.mappingAllVideoResponseModelFrom(section, null));
}
}
return map;
}
/**
* we handle both name and id for backward compatibility. legacy code use name, it is not a good idea as name is not
* grantee to be unique.
*/
public static LectureModel getLecture(CourseComponent courseComponent, String chapterName, String chapterId, String lectureName, String lectureId)
throws Exception {
//TODO - we may use a generic filter to fetch the data?
for(IBlock chapter : courseComponent.getChildren()){
if ( chapter.getId().equals(chapterId) ){
for(IBlock lecture : chapter.getChildren() ){
//TODO - check to see if need to compare id or not
if ( lecture.getId().equals(lectureId) ){
LectureModel lm = new LectureModel();
lm.name = lecture.getDisplayName();
lm.videos = (ArrayList) mappingAllVideoResponseModelFrom((CourseComponent)lecture, null );
return lm;
}
}
}
}
//if we can not find object by id, try to get by name.
for(IBlock chapter : courseComponent.getChildren()){
if ( chapter.getDisplayName().equals(chapterName) ){
for(IBlock lecture : chapter.getChildren() ){
//TODO - check to see if need to compare id or not
if ( lecture.getDisplayName().equals(lectureName) ){
LectureModel lm = new LectureModel();
lm.name = lecture.getDisplayName();
lm.videos = (ArrayList) mappingAllVideoResponseModelFrom((CourseComponent)lecture, null );
return lm;
}
}
}
}
return null;
}
public static VideoResponseModel getVideoById(CourseComponent courseComponent, String videoId)
throws Exception {
for(HasDownloadEntry item : courseComponent.getVideos()) {
VideoBlockModel model = (VideoBlockModel)item;
if (model.getId().equals(videoId))
return mappingVideoResponseModelFrom((VideoBlockModel) item);
}
return null;
}
/**
*
* @param courseComponent
* @param subsectionId
* @return
*/
public static VideoResponseModel getSubsectionById(CourseComponent courseComponent, String subsectionId){
////TODO - we may use a generic filter to fetch the data?
Map<String, SectionEntry> map = mappingCourseHierarchyFrom(courseComponent);
for (Map.Entry<String, SectionEntry> chapterentry : map.entrySet()) {
// iterate lectures
for (Map.Entry<String, ArrayList<VideoResponseModel>> entry :
chapterentry.getValue().sections.entrySet()) {
// iterate videos
for (VideoResponseModel v : entry.getValue()) {
// identify the subsection (module) if id matches
IPathNode node = v.getSection();
if (node != null && subsectionId.equals(node.getId())) {
return v;
}
}
}
}
return null;
}
}