from django.views.generic import DetailView

from core.views import DetailedModelViewBase
from core.views import ModelListViewBase
from .models import Measurement, MeasurementRound, SingleRoundResult
from rest.serializers import MeasurementsSerializer
from utils.view_utils import default_detail_crumb, list_breadcrumb
from users.access_checks import LoginRequiredMixin


class MeasurementsListView(LoginRequiredMixin, ModelListViewBase):
    model = Measurement

    type = None
    serializer = MeasurementsSerializer
    sample_type = None

    def get(self, request, *args, **kwargs):
        self.sample_type = request.GET.get('type', None)
        return super(ModelListViewBase, self).get(request, *args, **kwargs)

    def get_query(self):
        qs = Measurement.objects.select_related('sample', 'sample__cylinder')
        if self.sample_type == 'reference':
            return qs.filter(sample__cylinder__isnull=False)
        elif self.sample_type == 'odour':
            return qs.filter(sample__cylinder__isnull=True)
        else:
            return qs


# returns a dict with all the relevant round overview information
# to be used in a measurement detail view
def get_round_overview(measurement):
    round_list = list()
    for ro in measurement.rounds.all().order_by('finished_at'):
        round_list.append(dict())
        round_list[-1]['is_included'] = ro.is_included
        round_list[-1]['number'] = ro.number
        round_list[-1]['round_pk'] = ro.pk
        round_list[-1]['results'] = list()
        round_list[-1]['operator_id'] = ro.operator.pk
        round_list[-1]['operator'] = ro.operator.username
        round_list[-1]['started_at'] = ro.started_at
        round_list[-1]['finished_at'] = ro.finished_at

        for single in ro.results.all():
            blank = None
            if single.blank_error_percentage > 0:
                blank = int(round(single.blank_error_percentage * 100))

            sr_dict = {
                "qualified": single.panelist.qualified,
                "panelist": single.panelist,
                "valid": single.valid
            }

            # insert more stuff depending on measurement type
            if blank is not None:
                sr_dict.update({'blank': '{0}%'.format(blank)})

            if single.delta_z is not None:
                sr_dict.update({'dz': single.delta_z})

            if single.ite is not None:
                sr_dict.update({"ite": single.ite})

            round_list[-1]['results'].append(sr_dict)

    return round_list


class MeasurementDetailView(LoginRequiredMixin, DetailedModelViewBase):
    model = Measurement

    template_name = 'measurements/measurement_detail.html'

    def get_context_data(self, **kwargs):
        ctx = super(MeasurementDetailView, self).get_context_data(**kwargs)

        pk = self.kwargs.get(self.pk_url_kwarg)
        get_measurement_dict(pk, ctx, self.object)
        # print(ctx)
        ctx['app_label'] = self.model._meta.app_label
        ctx['model_class'] = self.model.__name__
        return ctx


def get_measurement_dict(pk, ctx, model_instance):
    measure_obj = Measurement.objects.prefetch_related('sample', 'rounds', 'rounds__results').filter(pk=pk)
    obj = measure_obj.values('presentation_time', 'temperature',
                                                   'olfactometer', 'olfactometer__name',
                                                   'olfactometer__inventory_number', 'comments',
                                                   'calibration', 'standard_identifier', 'presentation_time',
                                                   'location', 'calibration__valid_to', 'compliant_result',
                             'sample', 'sample__name', 'analysis_result',
                                                   'sample__contained_in', 'sample__contained_in__name')[0]

    # fill in started at and finished at from calculated properties
    # can't look them up via the database
    obj['started_at'] = model_instance.started_at
    obj['finished_at'] = model_instance.finished_at

    if obj['analysis_result'] is not None:
        obj['result'] = model_instance.sample.total_dilution * obj['analysis_result']
    else:
        obj['result'] = None

    ctx['manually_excluded'] = manually_excluded_panel_member(measure_obj[0])

    ctx['predilution'] = model_instance.sample.total_dilution
    ctx.update(obj)
    ctx['used_method'] = model_instance.get_used_method_display()
    ctx['round_info'] = get_round_overview(model_instance)


# retrieve the shorthands for all manually excluded panel members
def manually_excluded_panel_member(measurement_obj):
    rounds = measurement_obj.rounds.all().values_list('id', flat=True)
    srr_for_measurement = SingleRoundResult.objects.select_related('panelist'). \
        filter(round__in=rounds, manually_excluded=True).distinct('panelist__shorthand'). \
        values_list('panelist__shorthand', flat=True)
    return srr_for_measurement

# copy and paste from ctrl software to have it here as a reference
# hence it's somewhat important to note:
# each (series) step provides a comma separated integer tuple, each step on his
# own is sep. by space: x1,y1 x2,y2 x3,y3 ... for each series step
# - hedonics/intensity: only the first int is relevant, second is ignored and should be 0 or -1
# one should note, that for int/hed -1 and 0 are valid replies.
# - dfc (without int/hed): first int is 1-6 from guess wrong to certain correct (see old winose), second is ignored
# 42 indicates a missed reply step
# for dfc combined with int or hed, the second reply would hold the int/hed answer
# - y/n - first is reply to sample: 1 - detected a diluted sample, 2 - detected null sample, 0 - no reply
# (this is done to be recreate a matrix without knowing the dilution row)
# secondary reply is 0 for no reply, 1 for a reply (always a blank)
# - astm/dfc Hed/Int


def map_yes_no(prim, sec):
    # in contrast to the ctrl sw, we don't make a difference when a blank is detected
    if prim > 0:
        prim = '\N{CHECK MARK}'  # this stuff actually works :)
    else:
        prim = None
    if sec > 0:
        sec = '\N{BALLOT X}'
    else:
        sec = None

    return prim, sec,


def map_reply_tuple(reply_tuple, method):
    prim = reply_tuple[0]
    sec = reply_tuple[1]
    from .models import YES_NO_METHOD, FORCED_CHOICE_METHOD, INTENSITY_METHOD, HEDONICS_METHOD, TFC_METHOD
    from .models import DFCHED_METHOD, DFCINT_METHOD

    # TODO: this code needs some serious improvement

    if method == YES_NO_METHOD:
        return map_yes_no(prim, sec)
    elif method == FORCED_CHOICE_METHOD or method == TFC_METHOD \
            or method == DFCHED_METHOD or method == DFCINT_METHOD:
        # mapping missed reply here, same behaviour for combined methods too
        if prim == 42:
            prim = '\u2014'  # long hyphen
        if method == DFCHED_METHOD:
            if sec is not None and sec >= 0:
                sec -= 4
            else:
                sec = '\u2014'
            return prim, sec
        elif method == DFCINT_METHOD:
            if sec is not None and sec >= 0:
                return prim, sec  # int needs to processing
            else:
                return prim, '\u2014'
        else:
            return prim, None
    elif method == INTENSITY_METHOD:
        return prim, None
    elif method == HEDONICS_METHOD:
        return prim - 4, None
    else:
        return prim, sec,


def get_matrix_context(ctx, model_instance):
    # first, pull dilution series into context
    context_series = (model_instance.series.split())
    panel = list()
    panel_replies = list()

    for single in model_instance.results.all():
        panel.append(single.panelist.shorthand)
        r_vec = [list(map(int, x.split(','))) for x in single.replies.split()]
        panel_replies.append(r_vec)

    reply_data = list()

    # collect replies per step
    for step_idx in range(len(context_series)):
        reply_data.append(list())
        reply_data[-1].append(context_series[step_idx])
        # for each panelist
        for p_idx in range(len(panel_replies)):
            # still replied to that one?
            if step_idx < len(panel_replies[p_idx]):
                # print context_series[step_idx]
                spiced_up = map_reply_tuple(panel_replies[p_idx][step_idx],
                                            model_instance.measurement.used_method)
                reply_data[-1].append(spiced_up)
            else:
                reply_data[-1].append(None)

    # filter out unused dilution steps
    reply_data = [x for x in reply_data if any(x[1:])]
    ctx['series'] = context_series[:len(reply_data)]
    ctx['panel'] = panel
    ctx['method'] = model_instance.measurement.used_method
    ctx['reply_data'] = reply_data
    ctx['round'] = model_instance


class ThresholdRoundDetailView(LoginRequiredMixin, DetailView):
    template_name = 'measurements/round_detail.html'
    model = MeasurementRound

    # override breadcrumbs to get the hierarchy right
    def bread(self):
        base = [list_breadcrumb(Measurement.overview_url, Measurement)]
        base.append(default_detail_crumb(Measurement.detail_url, self.object.measurement))
        base.append(default_detail_crumb(MeasurementRound.detail_url, self.object))
        return base

    def get_context_data(self, **kwargs):
        ctx = super(ThresholdRoundDetailView, self).get_context_data(**kwargs)

        get_matrix_context(ctx, self.object)

        return ctx


class SampleReportGeneratorView(DetailView):
    model = Measurement
    template_name = 'measurements/sample_report_web.html'

    def get_context_data(self, **kwargs):
        ctx = super(SampleReportGeneratorView, self).get_context_data(**kwargs)

        if self.object.analysis_result:
            ctx['result_with_predil'] = self.object.analysis_result * self.object.sample.total_dilution

        from .models import HEDONICS_METHOD, INTENSITY_METHOD, YES_NO_METHOD

        ctx['has_blank_column'] = self.object.used_method == YES_NO_METHOD
        ctx['round_info'] = get_round_overview(ctx['object'])
        ctx['is_reference'] = self.object.sample.cylinder is not None

        include_matrices = self.request.GET.get('matrices', None)
        ctx['include_matrices'] = include_matrices

        ctx['manually_excluded'] = manually_excluded_panel_member(self.object)

        # the round table makes only sense for non quality methods
        ctx['show_overview_table'] = (self.object.used_method != HEDONICS_METHOD and
                                      self.object.used_method != INTENSITY_METHOD)

        if include_matrices:
            matrices = list()
            for r in self.object.rounds.all().order_by('number'):
                rctx = dict()
                get_matrix_context(rctx, r)
                matrices.append(rctx)
            ctx['matrices'] = matrices

        return ctx


