import locale

from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy

from openslides.utils.models import MinMaxIntegerField


class BaseOption(models.Model):
    """
    Base option class for a poll.

    Subclasses have to define a poll field. This must be a ForeignKeyField
    to a subclass of BasePoll. There must also be a vote_class attribute
    which has to be a subclass of BaseVote. Otherwise you have to override the
    get_vote_class method.
    """
    vote_class = None

    class Meta:
        abstract = True

    def get_votes(self):
        return self.get_vote_class().objects.filter(option=self)

    def get_vote_class(self):
        if self.vote_class is None:
            raise NotImplementedError('The option class %s has to have an attribute vote_class.' % self)
        return self.vote_class

    def __getitem__(self, name):
        try:
            return self.get_votes().get(value=name)
        except self.get_vote_class().DoesNotExist:
            raise KeyError


class BaseVote(models.Model):
    """
    Base vote class for an option.

    Subclasses have to define an option field. This must be a ForeignKeyField
    to a subclass of BasePoll.
    """
    weight = models.IntegerField(default=1, null=True)  # Use MinMaxIntegerField
    value = models.CharField(max_length=255, null=True)

    class Meta:
        abstract = True

    def __str__(self):
        return self.print_weight()

    def get_value(self):
        return _(self.value)

    def print_weight(self, raw=False):
        if raw:
            return self.weight
        try:
            percent_base = self.option.poll.get_percent_base()
        except AttributeError:
            # The poll class is no child of CollectVotesCast
            percent_base = 0
        return print_value(self.weight, percent_base)


PERCENT_BASE_CHOICES = (
    {'value': 'WITHOUT_INVALID', 'display_name': ugettext_lazy('Only all valid votes')},
    {'value': 'WITH_INVALID', 'display_name': ugettext_lazy('All votes cast (including invalid votes)')},
    {'value': 'DISABLED', 'display_name': ugettext_lazy('Disabled (no percents)')})


class CollectDefaultVotesMixin(models.Model):
    """
    Mixin for a poll to collect the default vote values for valid votes,
    invalid votes and votes cast.
    """
    votesvalid = MinMaxIntegerField(null=True, blank=True, min_value=-2)
    votesinvalid = MinMaxIntegerField(null=True, blank=True, min_value=-2)
    votescast = MinMaxIntegerField(null=True, blank=True, min_value=-2)

    class Meta:
        abstract = True

    def append_pollform_fields(self, fields):
        fields.append('votesvalid')
        fields.append('votesinvalid')
        fields.append('votescast')
        super(CollectDefaultVotesMixin, self).append_pollform_fields(fields)

    def get_percent_base_choice(self):
        """
        Returns one of the three strings in PERCENT_BASE_CHOICES.
        """
        raise NotImplementedError('You have to provide a get_percent_base_choice() method.')

    def print_votesvalid(self):
        if self.get_percent_base_choice() == 'DISABLED':
            value = print_value(self.votesvalid, None)
        else:
            value = print_value(self.votesvalid, self.get_percent_base())
        return value

    def print_votesinvalid(self):
        if self.get_percent_base_choice() == 'WITH_INVALID':
            value = print_value(self.votesinvalid, self.get_percent_base())
        else:
            value = print_value(self.votesinvalid, None)
        return value

    def print_votescast(self):
        if self.get_percent_base_choice() == 'WITH_INVALID':
            value = print_value(self.votescast, self.get_percent_base())
        else:
            value = print_value(self.votescast, None)
        return value

    def get_percent_base(self):
        if self.get_percent_base_choice() == "WITHOUT_INVALID" and self.votesvalid and self.votesvalid > 0:
            base = 100 / float(self.votesvalid)
        elif self.get_percent_base_choice() == "WITH_INVALID" and self.votescast and self.votescast > 0:
            base = 100 / float(self.votescast)
        else:
            base = None
        return base


class PublishPollMixin(models.Model):
    """
    Mixin for a poll to add a flag whether the poll is published or not.
    """
    published = models.BooleanField(default=False)

    class Meta:
        abstract = True

    def set_published(self, published):
        self.published = published
        self.save()


class BasePoll(models.Model):
    """
    Base poll class.
    """
    vote_values = ['Votes']

    class Meta:
        abstract = True

    def has_votes(self):
        """
        Returns True if there are votes in the poll.
        """
        if self.get_votes().exists():
            return True
        return False

    def set_options(self, options_data=[]):
        """
        Adds new option objects to the poll.

        option_data: A list of arguments for the option.
        """
        for option_data in options_data:
            option = self.get_option_class()(**option_data)
            option.poll = self
            option.save()

    def get_options(self):
        """
        Returns the option objects for the poll.
        """
        return self.get_option_class().objects.filter(poll=self)

    def get_option_class(self):
        """
        Returns the option class for the poll. Default is self.option_class.
        """
        return self.option_class

    def get_vote_values(self):
        """
        Returns the possible values for the poll. Default is as list.
        """
        return self.vote_values

    def get_vote_class(self):
        """
        Returns the related vote class.
        """
        return self.get_option_class().vote_class

    def get_votes(self):
        """
        Return a QuerySet with all vote objects related to this poll.
        """
        return self.get_vote_class().objects.filter(option__poll__id=self.id)

    def set_vote_objects_with_values(self, option, data):
        """
        Creates or updates the vote objects for the poll.
        """
        for value in self.get_vote_values():
            try:
                vote = self.get_votes().filter(option=option).get(value=value)
            except ObjectDoesNotExist:
                vote = self.get_vote_class()(option=option, value=value)
            vote.weight = data[value]
            vote.save()

    def get_vote_objects_with_values(self, option_id):
        """
        Returns the vote values and their weight as a list with two elements.
        """
        values = []
        for value in self.get_vote_values():
            try:
                vote = self.get_votes().filter(option=option_id).get(value=value)
            except ObjectDoesNotExist:
                values.append(self.get_vote_class()(value=value, weight=''))
            else:
                values.append(vote)
        return values

    def get_vote_form(self, **kwargs):
        """
        Returns the form for one option of the poll.
        """
        from openslides.poll.forms import OptionForm
        return OptionForm(extra=self.get_vote_objects_with_values(kwargs['formid']),
                          **kwargs)

    def get_vote_forms(self, **kwargs):
        """
        Returns a list of forms for the poll.
        """
        forms = []
        for option in self.get_options():
            form = self.get_vote_form(formid=option.id, **kwargs)
            form.option = option
            forms.append(form)
        return forms

    def append_pollform_fields(self, fields):
        """
        Appends additional field to a given list of fields. By default it
        appends nothing.
        """
        pass


def print_value(value, percent_base=0):
    """
    Returns a human readable string for the vote value. It is 'majority',
    'undocumented' or the vote value with percent value if so.
    """
    if value == -1:
        verbose_value = _('majority')
    elif value == -2:
        verbose_value = _('undocumented')
    elif value is None:
        verbose_value = _('undocumented')
    else:
        if percent_base:
            locale.setlocale(locale.LC_ALL, '')
            verbose_value = u'%d (%s %%)' % (value, locale.format('%.1f', value * percent_base))
        else:
            verbose_value = u'%s' % value
    return verbose_value
