from django.shortcuts import HttpResponse
from django.views.generic import TemplateView
import operator
from django.utils.functional import Promise
from django.utils.text import force_text
import json
from functools import reduce
from django.db.models.loading import get_model


# from the interwebs: work around the problem that
# verbose names in the model class leads to non serializable
# json data if the names are wrapped in lazy translation functionals
class LazyEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Promise):
            return force_text(obj)
        return obj


# search result view
class SearchResultView(TemplateView):
    template_name = 'search_results.html'

    def get(self, request, *args, **kwargs):
        query = self.request.GET.get('query')
        ctx = super(SearchResultView, self).get_context_data(**kwargs)
        ctx['query'] = query
        results = get_raw_search(query)
        ctx.update(results)
        from django.core.urlresolvers import reverse_lazy
        from django.shortcuts import HttpResponseRedirect

        # if there is exactly one result, redirect directly to the detail view for this item
        if len(results['query_sets']) == 1:
            item = results['query_sets'][0]
            return HttpResponseRedirect(redirect_to=reverse_lazy(item[3], kwargs={"pk": item[2]}))
        else:
            return self.render_to_response(context=ctx)


# search ajax view for auto complete in search text input
def generic_quick_search(request):
    from json import dumps

    query = request.GET.get('query', '')
    query_sets = get_raw_search(query)['query_sets']

    result = dict()
    result['query_result'] = query_sets[:10]  # just present a few things in the auto complete listbox
    return HttpResponse(dumps(result, cls=LazyEncoder), content_type='application/json')


def get_raw_search(query):
    from datetime import datetime

    t0 = datetime.now()

    # manually select things where we want to search in
    # skip stuff like bag materials and such things
    from samples.models import Sample
    from panelists.models import Panelist
    from bags.models import Bag
    from clients.models import Client, ContactPerson, ClientOffice
    from projects.models import Project
    from referencegases.models import ReferenceGas
    from measurements.models import Measurement

    query_sets = list()
    query_sets.append(Panelist.objects.all())
    query_sets.append(Bag.objects.all())
    query_sets.append(Client.objects.all())
    query_sets.append(ContactPerson.objects.all())
    query_sets.append(ClientOffice.objects.all())
    query_sets.append(Project.objects.all())
    query_sets.append(Sample.objects.all())
    query_sets.append(ReferenceGas.objects.all())
    query_sets.append(Measurement.objects.all())

    search_terms = query.lower().split(' ')
    search_terms = [x.strip() for x in search_terms]
    search_terms = [x for x in search_terms if len(x) > 0]

    def map_query_sets(entry):
        textual = str(entry)
        pk = entry.pk
        app_label = entry._meta.app_label
        model_class = entry.__class__.__name__
        detail_url = get_model(app_label, model_class).detail_url
        # all what is needed: \
        # the textual representation, verbose model name (for the blabla in model part)
        # the pk of course and the detail url, the latter is present since we only search within
        # our little model world, where all those models define such a detail url
        return (textual, entry._meta.verbose_name, pk, detail_url)

    query_sets = [list(map(map_query_sets, qs_list)) for qs_list in query_sets]
    query_sets = reduce(operator.add, query_sets)  # fold

    # either all search terms are contained or the whole search term matches
    def predicate(elem):
        return all(part in elem for part in search_terms)  # or full_term in elem

    query_sets = [query_tuple for query_tuple in query_sets if predicate(query_tuple[0].lower())]

    result = dict()
    result['query_sets'] = query_sets
    result['time'] = (datetime.now() - t0).total_seconds()

    return result

