"""
Serializer for video outline
"""
from edxval.api import ValInternalError, get_video_info_for_course_and_profiles
from rest_framework.reverse import reverse

from courseware.access import has_access
from courseware.courses import get_course_by_id
from courseware.model_data import FieldDataCache
from courseware.module_render import get_module_for_descriptor
from util.module_utils import get_dynamic_descriptor_children
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.mongo.base import BLOCK_TYPES_WITH_CHILDREN
from xmodule.video_module.transcripts_utils import is_val_transcript_feature_enabled_for_course


class BlockOutline(object):
    """
    Serializes course videos, pulling data from VAL and the video modules.
    """
    def __init__(self, course_id, start_block, block_types, request, video_profiles):
        """Create a BlockOutline using `start_block` as a starting point."""
        self.start_block = start_block
        self.block_types = block_types
        self.course_id = course_id
        self.request = request  # needed for making full URLS
        self.local_cache = {}
        try:
            self.local_cache['course_videos'] = get_video_info_for_course_and_profiles(
                unicode(course_id), video_profiles
            )
        except ValInternalError:  # pragma: nocover
            self.local_cache['course_videos'] = {}

    def __iter__(self):
        def parent_or_requested_block_type(usage_key):
            """
            Returns whether the usage_key's block_type is one of self.block_types or a parent type.
            """
            return (
                usage_key.block_type in self.block_types or
                usage_key.block_type in BLOCK_TYPES_WITH_CHILDREN
            )

        def create_module(descriptor):
            """
            Factory method for creating and binding a module for the given descriptor.
            """
            field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
                self.course_id, self.request.user, descriptor, depth=0,
            )
            course = get_course_by_id(self.course_id)
            return get_module_for_descriptor(
                self.request.user, self.request, descriptor, field_data_cache, self.course_id, course=course
            )

        with modulestore().bulk_operations(self.course_id):
            child_to_parent = {}
            stack = [self.start_block]
            while stack:
                curr_block = stack.pop()

                if curr_block.hide_from_toc:
                    # For now, if the 'hide_from_toc' setting is set on the block, do not traverse down
                    # the hierarchy.  The reason being is that these blocks may not have human-readable names
                    # to display on the mobile clients.
                    # Eventually, we'll need to figure out how we want these blocks to be displayed on the
                    # mobile clients.  As they are still accessible in the browser, just not navigatable
                    # from the table-of-contents.
                    continue

                if curr_block.location.block_type in self.block_types:
                    if not has_access(self.request.user, 'load', curr_block, course_key=self.course_id):
                        continue

                    summary_fn = self.block_types[curr_block.category]
                    block_path = list(path(curr_block, child_to_parent, self.start_block))
                    unit_url, section_url = find_urls(self.course_id, curr_block, child_to_parent, self.request)

                    yield {
                        "path": block_path,
                        "named_path": [b["name"] for b in block_path],
                        "unit_url": unit_url,
                        "section_url": section_url,
                        "summary": summary_fn(self.course_id, curr_block, self.request, self.local_cache)
                    }

                if curr_block.has_children:
                    children = get_dynamic_descriptor_children(
                        curr_block,
                        self.request.user.id,
                        create_module,
                        usage_key_filter=parent_or_requested_block_type
                    )
                    for block in reversed(children):
                        stack.append(block)
                        child_to_parent[block] = curr_block


def path(block, child_to_parent, start_block):
    """path for block"""
    block_path = []
    while block in child_to_parent:
        block = child_to_parent[block]
        if block is not start_block:
            block_path.append({
                # to be consistent with other edx-platform clients, return the defaulted display name
                'name': block.display_name_with_default_escaped,
                'category': block.category,
                'id': unicode(block.location)
            })
    return reversed(block_path)


def find_urls(course_id, block, child_to_parent, request):
    """
    Find the section and unit urls for a block.

    Returns:
        unit_url, section_url:
            unit_url (str): The url of a unit
            section_url (str): The url of a section

    """
    block_path = []
    while block in child_to_parent:
        block = child_to_parent[block]
        block_path.append(block)

    block_list = list(reversed(block_path))
    block_count = len(block_list)

    chapter_id = block_list[1].location.block_id if block_count > 1 else None
    section = block_list[2] if block_count > 2 else None
    position = None

    if block_count > 3:
        position = 1
        for block in section.children:
            if block.name == block_list[3].url_name:
                break
            position += 1

    kwargs = {'course_id': unicode(course_id)}
    if chapter_id is None:
        course_url = reverse("courseware", kwargs=kwargs, request=request)
        return course_url, course_url

    kwargs['chapter'] = chapter_id
    if section is None:
        chapter_url = reverse("courseware_chapter", kwargs=kwargs, request=request)
        return chapter_url, chapter_url

    kwargs['section'] = section.url_name
    section_url = reverse("courseware_section", kwargs=kwargs, request=request)
    if position is None:
        return section_url, section_url

    kwargs['position'] = position
    unit_url = reverse("courseware_position", kwargs=kwargs, request=request)
    return unit_url, section_url


def video_summary(video_profiles, course_id, video_descriptor, request, local_cache):
    """
    returns summary dict for the given video module
    """
    always_available_data = {
        "name": video_descriptor.display_name,
        "category": video_descriptor.category,
        "id": unicode(video_descriptor.scope_ids.usage_id),
        "only_on_web": video_descriptor.only_on_web,
    }

    if video_descriptor.only_on_web:
        ret = {
            "video_url": None,
            "video_thumbnail_url": None,
            "duration": 0,
            "size": 0,
            "transcripts": {},
            "language": None,
        }
        ret.update(always_available_data)
        return ret

    # Get encoded videos
    video_data = local_cache['course_videos'].get(video_descriptor.edx_video_id, {})

    # Get highest priority video to populate backwards compatible field
    default_encoded_video = {}

    if video_data:
        for profile in video_profiles:
            default_encoded_video = video_data['profiles'].get(profile, {})
            if default_encoded_video:
                break

    if default_encoded_video:
        video_url = default_encoded_video['url']
    # Then fall back to VideoDescriptor fields for video URLs
    elif video_descriptor.html5_sources:
        video_url = video_descriptor.html5_sources[0]
    else:
        video_url = video_descriptor.source

    # Get duration/size, else default
    duration = video_data.get('duration', None)
    size = default_encoded_video.get('file_size', 0)

    # Transcripts...
    feature_enabled = is_val_transcript_feature_enabled_for_course(course_id)
    transcripts_info = video_descriptor.get_transcripts_info(include_val_transcripts=feature_enabled)
    transcript_langs = video_descriptor.available_translations(
        transcripts=transcripts_info,
        include_val_transcripts=feature_enabled
    )

    transcripts = {
        lang: reverse(
            'video-transcripts-detail',
            kwargs={
                'course_id': unicode(course_id),
                'block_id': video_descriptor.scope_ids.usage_id.block_id,
                'lang': lang
            },
            request=request,
        )
        for lang in transcript_langs
    }

    ret = {
        "video_url": video_url,
        "video_thumbnail_url": None,
        "duration": duration,
        "size": size,
        "transcripts": transcripts,
        "language": video_descriptor.get_default_transcript_language(transcripts_info),
        "encoded_videos": video_data.get('profiles')
    }
    ret.update(always_available_data)
    return ret
