from itertools import groupby
from ldt.api.ldt.authentication import (SessionAuthentication, ApiKeyAuthentication)
from ldt.indexation import get_results_list
from ldt.ldt_utils.models import Content, Media, Project, Segment
from ldt.ldt_utils.projectserializer import ProjectJsonSerializer
from ldt.ldt_utils.modelsutils  import ProjectMerger
from ldt.security import unprotect_models, protect_models
import logging

from django.conf import settings
from django.conf.urls import url
from django.contrib.auth.models import Group
from django.shortcuts import get_object_or_404
from guardian.shortcuts import get_objects_for_group
from django.core.paginator import Paginator, InvalidPage
from tastypie import fields
from tastypie.authentication import MultiAuthentication
from tastypie.resources import Bundle, ModelResource, ALL_WITH_RELATIONS, ALL
from tastypie.utils import trailing_slash
from tastypie.exceptions import BadRequest, NotFound


logger = logging.getLogger(__name__)

class MediaResource(ModelResource):

    class Meta:
        allowed_methods = ['get']
        resource_name = 'medias'
        queryset = Media.objects.all()

    def get_resource_uri(self, bundle_or_obj=None, url_name='api_dispatch_list'):
        if not bundle_or_obj:
            return super(MediaResource, self).get_resource_uri(bundle_or_obj,url_name)
        elif isinstance(bundle_or_obj, Bundle):
            return bundle_or_obj.obj.videopath + bundle_or_obj.obj.stream_src
        else:
            return bundle_or_obj.videopath + bundle_or_obj.stream_src

class ContentStatResource(ModelResource):

    nb_annotations = fields.IntegerField(attribute='nb_annotations')
    last_annotated = fields.DateTimeField(attribute='last_annotated')
    annotation_volume_str = fields.CharField(attribute='annotation_volume_str')
    polemics_volume_str = fields.CharField(attribute='polemics_volume_str')
    class Meta:
        resource_name = 'content_stats'
        include_resource_uri = False



"""
Content Resource definition.
The following urls are defined:
    - <api_base>/contents/ : get a list of content
        Method: GET
        Returns: in json returns an object:
            {
                meta: {
                    limit: cf. limit parameter
                    offset: cf. offset parameter
                    next: next page url or '''null'''
                    previous: previous page url ir '''null'''
                    total_count: nb of total results
                },
                objects: [
                    <list of content objects>
                ]

            }
        Parameters:
        - format: compulsory, onlt "json" is defined
        - limit: Max number of results (20 by default)
        - offset: nb of object to skip (0 by default)
        - tags: the tags to filter for. This must be the exact value(s).
            tags=tag1,tag2,tag3 will do an "OR", i.e. here will find the content tagged with either tag1, tag2 or tag3
            tags=tag1&tags=tag2 vill do an "AND", i.e. will find the contents tagged with tag1 and tag2.
        - title: filter by title cf http://django-tastypie.readthedocs.io/en/latest/resources.html#basic-filtering for the syntax
        - order_by: order the results. Possible values are [-]title, [-]creation_date, [-content_creation_date].
            c.f. : http://django-tastypie.readthedocs.io/en/latest/resources.html#ordering for details
    - <api_base>/content/<iri_id>/ : get the detail of a content
        Method: GET
        Parameters:
        - format: compulsory, only "json" is defined
"""
class ContentResource(ModelResource):

    front_project = fields.ForeignKey('ldt.api.ldt.resources.ProjectResource','front_project', null=True, full=False)
    media_url = fields.ForeignKey('ldt.api.ldt.resources.content.MediaResource','media_obj', null=True, full=False)
    stats = fields.OneToOneField('ldt.api.ldt.resources.content.ContentStatResource', 'stat_annotation', full=True)
    tag_list = fields.ListField(readonly=True)

    class Meta:
        allowed_methods = ['get']
        resource_name = 'contents'
        authentication = MultiAuthentication(ApiKeyAuthentication(), SessionAuthentication())
        queryset = Content.objects.prefetch_related('tags').select_related('front_project','media_obj', 'stat_annotation').all()
        filtering = {
            'tags' : ALL_WITH_RELATIONS,
            'title' : ALL,
        }
        ordering = ['title', 'creation_date', 'content_creation_date']

    def get_object_list(self, request):
        return Content.safe_objects.select_related('front_project', 'media_obj', 'stat_annotation').prefetch_related('tags').all()

    def prepend_urls(self):
        return [
            url(r"^(?P<resource_name>%s)/recommended/$" % self._meta.resource_name, self.wrap_view('get_recommended'), name="api_contents_recommended"),
            url(r"^(?P<resource_name>%s)/all/(?P<iri_id>[\w\d_.-]+)/$" % self._meta.resource_name, self.wrap_view('get_all_projects'), name="api_content_all_projects"),
            url(r"^(?P<resource_name>%s)/search%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('get_search'), name="api_content_get_search"),
            url(r"^(?P<resource_name>%s)/(?P<iri_id>[\w\d_.-]+)/$" % self._meta.resource_name, self.wrap_view('dispatch_detail'), name="api_dispatch_detail"),
        ]

    def get_resource_uri(self, bundle_or_obj=None, url_name='api_dispatch_list'):
        if bundle_or_obj is None:
            return super(ContentResource, self).get_resource_uri(bundle_or_obj, url_name)
        kwargs = {
            'resource_name': self._meta.resource_name,
            'api_name': self._meta.api_name
        }
        if isinstance(bundle_or_obj, Bundle):
            kwargs['iri_id'] = bundle_or_obj.obj.iri_id
        else:
            kwargs['iri_id'] = bundle_or_obj.iri_id
        return self._build_reverse_url("api_dispatch_detail", kwargs=kwargs)

    def get_recommended(self, request, **kwargs):
        self.method_check(request, allowed=['get'])

        keywords = request.GET.get('keywords','')
        keywords_search = " OR ".join(keywords.split(','))
        field = request.GET.get('field','all')

        result_list = get_results_list(Segment, field, keywords_search)
        score_dict = dict([(k,sum([e.score for e in i])) for k,i in groupby(result_list, lambda e: e.iri_id)])

        res = [self.full_dehydrate(self.build_bundle(obj=c, request=request)) for c in Content.safe_objects.filter(iri_id__in = score_dict.keys())]

        def add_score(b,s):
            b.data['score'] = s
            return b

        object_list = {
            'objects': sorted([add_score(b, score_dict.get(b.data['iri_id'],0)) for b in res], key=lambda b: b.data['score'], reverse=True),
        }

        self.log_throttled_access(request)

        return  self.create_response(request, object_list)

    def get_all_projects(self, request, api_name, resource_name, iri_id=None):
        self.method_check(request, allowed=['get'])
        content = get_object_or_404(Content, iri_id=iri_id)

        # Unprotect the time to build the project
        unprotect_models()

        # add filter
        group_id = request.GET.get("group")
        if group_id is not None :
            group = get_object_or_404(Group, id=group_id)
            projects = get_objects_for_group(group, "view_project", Project.objects.filter(contents__in=[content], state=2))
        else:
            projects = Project.objects.filter(contents__in=[content], state=2)

        pm = ProjectMerger(content, projects)
        proj = pm.get_merged_project(False)
        ps = ProjectJsonSerializer(proj)
        data = ps.serialize_to_cinelab()
        self.log_throttled_access(request)
        # Delete project because it is useless to keep it in database
        # TODO: remove this, this is ugly. The project object should not be created
        proj.delete()

        protect_models()

        return self.create_response(request, data)

    def build_filters(self, filters=None):
        if filters is None:
            filters = {}

        orm_filters = super(ContentResource, self).build_filters(filters)

        if 'tags' in filters and filters.getlist('tags'):
            tags_filter_list = orm_filters.setdefault('tags', [])
            for tags_filter in filters.getlist('tags'):
                tags_filter_list.append({'tags__name__in': tags_filter.split(",")})

        logger.debug("build_filters filters %r, orm_filters %r", filters['tags'], orm_filters)

        return orm_filters

    def apply_filters(self, request, applicable_filters):
        """
        An ORM-specific implementation of ``apply_filters``.
        The default simply applies the ``applicable_filters`` as ``**kwargs``,
        but should make it possible to do more advanced things.
        """
        logger.debug("apply_filters applicable_filters %r", applicable_filters)

        tags_filter_list = applicable_filters.pop('tags', [])
        qs = self.get_object_list(request).filter(**applicable_filters)

        if tags_filter_list and len(tags_filter_list) == 1:
            qs = qs.filter(**tags_filter_list[0])
        elif tags_filter_list and len(tags_filter_list) > 1:
            for tag_filter in tags_filter_list:
                qs = qs.filter(id__in=self.get_object_list(request).filter(**tag_filter))

        return qs

    def dehydrate_tag_list(self, bundle):
        return [t.name for t in bundle.obj.tags.all()]


    def get_search(self, request, **kwargs):
        self.method_check(request, allowed=['get'])

        params = request.GET.copy()
        # Do the query.
        search = request.GET.get('q', '')
        if search=='':
            raise BadRequest('The request needs a search query "q=" parameter.')
        field = "all"
        if u'author:' in search.lower() :
            sub = search[7:]
            sub = sub.upper()
            if sub[0] != u'"':
                sub = u'"' + sub
            if sub[-1] != u'"':
                sub = sub + u'"'
            search = u'author:' + sub

        results = get_results_list(Content, field, search, False)
        # get_results_list returns a SearchQuerySet, we load_all() to get all real Content objects
        all_contents = results.load_all()
        limit = int(request.GET.get("limit") or getattr(settings, 'API_LIMIT_PER_PAGE', 20))
        paginator = Paginator(all_contents, limit)

        try:
            page = paginator.page(int(request.GET.get('page', 1)))
        except InvalidPage:
            raise NotFound("Sorry, no results on that page.")

        objects = []

        logger.debug("Content Get_search page object list %r for %s", page.object_list, search)
        for search_res in page.object_list:
            # search_res is a SearchResult, search_res.object is the real Segment Object thanks to results.load_all()
            bundle = self.build_bundle(obj=search_res.object, request=request)
            bundle = self.full_dehydrate(bundle)
            objects.append(bundle)

        next_url = None
        if page.has_next():
            params['page'] = page.next_page_number()
            next_url = request.path+"?"+params.urlencode()

        prev_url = None
        if page.has_previous():
            params['page'] = page.previous_page_number()
            prev_url = request.path+"?"+params.urlencode()

        object_list = {
            'meta': {
                'limit': limit,
                'next': next_url,
                'offset': page.start_index()-1,
                'previous': prev_url,
                'total_count': paginator.count
            },
            'objects': objects,
        }

        self.log_throttled_access(request)
        return self.create_response(request, object_list)


