from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import Q
from django.utils.translation import gettext_lazy

from .assignment import Assignment


class NonzeroSmallesMinimalPointsValidationError(ValidationError):
    pass


class InvalidLargestMaximumPointsValidationError(ValidationError):
    pass


class GapsInMapValidationError(ValidationError):
    pass


class DuplicateGradeError(Exception):
    pass


class PointToGradeMapQuerySet(models.QuerySet):
    def prefetch_pointrange_to_grade(self):
        """
        Prefetch all :class:`.PointRangeToGrade` objects
        for each PointToGradeMap objects in the queryset,
        and stores the result in the ``prefetched_pointrangetograde_objects``.

        The PointRangeToGrade objects in ``prefetched_pointrangetograde_objects``
        is ordered by ``minimum_points`` ascending.

        You should use the :meth:`.PointToGradeMap.prefetched_pointrangetogrades`
        property ``prefetched_pointrangetograde_objects``, because that shows a
        good error message when you forget to use this method on the queryset.
        """
        return self.prefetch_related(
                models.Prefetch('pointrangetograde_set',
                                queryset=PointRangeToGrade.objects.order_by('minimum_points'),
                                to_attr='prefetched_pointrangetograde_objects'))


class PointToGradeMap(models.Model):
    """
    Data structure to store the mapping from a point ranges to grades
    when using custom-table.

    The basic idea is described in https://github.com/devilry/devilry-django/issues/511,
    but we decided to add this OneToOne table to represent a mapping table
    to avoid adding complexity to Assignment that is only needed when the
    custom-table ``points_to_grade_mapper`` is used.

    .. attribute:: assignment

        Foreign Key to the assignment.

    .. attribute:: invalid

        This is set to ``True`` when the map has been invalidated because of
        changes to :attr:`devilry.apps.core.models.Assignment.max_points`,
        or when the map has been created, but it is empty.