from django.db import models
from panelists.models import Panelist
from measurements.models import SingleRoundResult
from math import log10, sqrt
import logging

winose_log = logging.getLogger('wnsq')


class HistoryTable(models.Model):
    class Meta:
        abstract = True

    # ppb = (ppm/ite)*1000
    # no need to store the ppm value explicitly, can look it up via rsr
    ppm = models.FloatField(
        null=False,
        editable=False
    )

    # convenience to make sorting easier, gets the round start date
    timestamp = models.DateTimeField(
        null=False,
        editable=False
    )

    # standard deviation across the log10 values of the ppb value
    sigma = models.FloatField(
        null=True,  # empty for the first 9 values
        editable=False
    )

    # average value across the average of the log10 values of the ppb value
    y_ite = models.FloatField(
        null=True,  # empty for the first 9 values
        editable=False
    )

    def __str__(self):
        return 'timestamp={0} ppb={1} sigma={2}, y_ite={3}' \
            .format(self.timestamp.date(), self.ppm, self.sigma, self.y_ite)


# panelist reference gas history
# first sketch, duplicates a bit of data...

class PanelHistory(HistoryTable):
    # the panelist this entry belongs to
    panelist = models.ForeignKey(to=Panelist, null=False)
    # the single result as part of a measurement where the ite was produced
    single_round_result = models.ForeignKey(
        to=SingleRoundResult,
        null=False,
        editable=False,
        unique=True,  # each single result is only once in this table
    )

    # for convenience, stored the actual ite augmented by a possible predilution
    ite = models.FloatField(blank=False,
                            null=False)

    from referencegases.models import REF_GAS_TYPES

    substance_key = models.CharField(
        null=False,
        editable=False,
        choices=REF_GAS_TYPES,
        max_length=1,
    )

    # add a new single round result, that is an ite in a reference gas measurement
    # the caller needs to take care that the ite is:
    # 1. present (and so is the ppm value)
    # 2. the measurement itself was done under standard compliance
    @staticmethod
    def add_new_srr(srr, ppm_value, predilution, substance_key):

        # some sanity checks
        if PanelHistory.objects.filter(single_round_result_id=srr.pk).exists():
            winose_log.debug('attempt to insert panel history object twice, pk={0}'.format(srr.pk))
            return

        # grab all existing entries for this panelist, sort by date
        previous_history = PanelHistory.objects.filter(panelist=srr.panelist, substance_key=substance_key). \
            order_by('-timestamp').values_list('ppm', flat=True)

        new_entry = PanelHistory()
        new_entry.single_round_result = srr
        new_entry.panelist = srr.panelist
        # grab round object for this srr and use the finish date-time as timestamp
        new_entry.timestamp = srr.round.finished_at

        # note, since this is stored, the predilution might not change later on
        new_entry.ite = srr.ite * predilution
        new_entry.ppm = (ppm_value / new_entry.ite)
        new_entry.substance_key = substance_key

        # now the interesting bit, grab at least 10, at most 20 values
        # calculate new average and standard deviation
        from statistics import stdev, mean

        previous_history = list(previous_history)
        # prepend the new ite of course
        previous_history.insert(0, new_entry.ppm)
        # need at least 10 values, the 10th is the first one to receive a std-dev/mean then
        if len(previous_history) >= 10:
            previous_history = previous_history[:20]  # at most 20, at least 10
            log_umol = [log10(v) for v in previous_history]
            sigma = stdev(log_umol)
            y_ite = mean(log_umol)
            new_entry.sigma = sigma
            new_entry.y_ite = y_ite

        new_entry.save()


from measurements.models import Measurement

LABHIST_STUDENT_20 = 2.093
LABHIST_AW_20 = sqrt(1.0 / 40.0)
LABHIST_MU = -1.3979  # accepted reference value (roughly log10(40 ppb))
SQR2 = sqrt(2.0)


# permanently stored lab history table
class LabHistory(HistoryTable):
    # in order to lookup certain things, we need the measurement
    team_ite = models.ForeignKey(to=Measurement,
                                 null=False)

    # for performance reasons, we store those values too, else things
    # might get really slow when the reference measurements start to
    # pile up
    r_value = models.FloatField(null=True)

    a_value = models.FloatField(null=True)

    dw_value = models.FloatField(null=True)

    @staticmethod
    def add_new_lab_value(measurement, ppm):

        if LabHistory.objects.filter(team_ite_id=measurement.pk).exists():
            winose_log.debug('attempt to add measurement object twice to history, pk={0}'.format(measurement.pk))
            return
        elif measurement.sample.cylinder.substance != 'B':
            winose_log.debug('invalid substance for lab history entry')
            return
        elif measurement.standard_identifier <= 0 or measurement.result is None:
            winose_log.debug('attempt to add a non compliant measurement/no result')
            return

        # almost the same thing as with the panel history
        # grab existing values, correctly sorted
        previous_history = LabHistory.objects.all().order_by('-timestamp').values_list("ppm", flat=True)

        new_entry = LabHistory()
        new_entry.ppm = (ppm / measurement.result)
        new_entry.timestamp = measurement.finished_at
        new_entry.team_ite = measurement

        from statistics import stdev, mean

        previous_history = list(previous_history)
        previous_history.insert(0, new_entry.ppm)  # prepend new entry

        # the 20th value is the first one to receive sigma/mean
        # TODO: rethink this, are we really using the correct values (amount of)
        if len(previous_history) >= 19:
            previous_history = previous_history[:20]
            log_ppb = [log10(v) for v in previous_history]
            y_ite = mean(log_ppb)
            sigma = stdev(log_ppb, y_ite)

            new_entry.sigma = sigma
            new_entry.y_ite = y_ite

            new_entry.r_value = LABHIST_STUDENT_20 * SQR2 * sigma
            new_entry.dw_value = y_ite - LABHIST_MU
            from math import fabs

            new_entry.a_value = fabs(new_entry.dw_value) + (LABHIST_AW_20 * new_entry.r_value)

        new_entry.save()

