import itertools
from django.utils.translation import gettext_lazy as _
from rest_framework import validators
from rest_framework.serializers import ValidationError

class IsInValidator:
  def __init__(self, allowed, message = None):
    self.allowed = allowed
    self.message = message or _('value %(value)s is not in the list of allowed values')

  def __call__(self, value):
    allowed = self.allowed() if callable(self.allowed) else self.allowed

    if not value in allowed:
      raise ValidationError(self.message % { 'value': value })

class SetExactlyNValidator:
  def __init__(self, fields, count, error = None):
    self.count  = count
    self.fields = fields
    self.error  = error or 'exactly {0} of {1} must be set'.format(count, fields)

  def __call__(self, data):
    if not len(list(filter(data.get, self.fields))) == self.count:
      raise ValidationError(self.error)

class SetExactlyOneValidator(SetExactlyNValidator):
  def __init__(self, fields):
    super().__init__(fields, 1, 'exactly one of {0} must be set'.format(fields))

class MutuallyDistinctValidator:
  def __init__(self, fields, error = None):
    assert len(fields) > 1, 'you must specify at least 2 fields to be mutually distinct'

    self.fields = fields
    self.error  = error or '{0} and {1} must be mutually distinct'

  def __call__(self, data):
    for field1, field2 in itertools.combinations(self.fields, 2):
      if data.get(field1) == data.get(field2):
        raise ValidationError(self.error.format(field1, field2))

# doesn't impose required constraint on unique_together fields
# when any of the required_together fields isn't specified, the check is skipped
class UniqueTogetherValidator(validators.UniqueTogetherValidator):
  def enforce_required_fields(self, *args, **kwargs):
    return

  def filter_queryset(self, attrs, queryset):
    # If this is an update, then any unprovided field should
    # have it's value set based on the existing instance attribute.
    if self.instance is not None:
      for field_name in self.fields:
        if field_name not in attrs:
          attrs[field_name] = getattr(self.instance, field_name)

    # Determine the filter keyword arguments and filter the queryset.
    try:
      filter_kwargs = {
        field_name: attrs[field_name]
        for field_name in self.fields
      }
    except KeyError:
      # some field wasn't present - skip this check
      return queryset.none()

    return validators.qs_filter(queryset, **filter_kwargs)

def unique_together_for(model, *fieldsets):
  validators = [UniqueTogetherValidator(model.objects.all(), fieldset) for fieldset in fieldsets]
  return validators[0] if len(validators) == 1 else validators
