from django.db.models import Max

import random


class Algorithm(object):

    shows = None
    ranks = None
    origranks = None
    double = None

    def __init__(self, shows, members, choices):
        """Initialisation :
            - on aggrège toutes les demandes pour chaque spectacle dans
              show.requests
            - on crée des tables de demandes pour chaque personne, afin de
              pouvoir modifier les rankings"""
        self.max_group = 2*max(choice.priority for choice in choices)
        self.shows = []
        showdict = {}
        for show in shows:
            show.nrequests = 0
            showdict[show] = show
            show.requests = []
            self.shows.append(show)
        self.ranks = {}
        self.origranks = {}
        self.choices = {}
        next_rank = {}
        member_shows = {}
        for member in members:
            self.ranks[member] = {}
            self.choices[member] = {}
            next_rank[member] = 1
            member_shows[member] = {}
        for choice in choices:
            member = choice.participant
            if choice.spectacle in member_shows[member]:
                continue
            else:
                member_shows[member][choice.spectacle] = True
            showdict[choice.spectacle].requests.append(member)
            showdict[choice.spectacle].nrequests += 2 if choice.double else 1
            self.ranks[member][choice.spectacle] = next_rank[member]
            next_rank[member] += 2 if choice.double else 1
            self.choices[member][choice.spectacle] = choice
        for member in members:
            self.origranks[member] = dict(self.ranks[member])

    def IncrementRanks(self, member, currank, increment=1):
        for show in self.ranks[member]:
            if self.ranks[member][show] > currank:
                self.ranks[member][show] -= increment

    def appendResult(self, l, member, show):
        l.append((member,
                  self.ranks[member][show],
                  self.origranks[member][show],
                  self.choices[member][show].double))

    def __call__(self, seed):
        random.seed(seed)
        results = []
        shows = sorted(self.shows, key=lambda x: x.nrequests / x.slots,
                       reverse=True)
        for show in shows:
            # On regroupe tous les gens ayant le même rang
            groups = dict([(i, []) for i in range(1, self.max_group + 1)])
            for member in show.requests:
                if self.ranks[member][show] == 0:
                    raise RuntimeError(member, show.title)
                groups[self.ranks[member][show]].append(member)
            # On passe à l'attribution
            winners = []
            losers = []
            for i in range(1, self.max_group + 1):
                group = list(groups[i])
                random.shuffle(group)
                for member in group:
                    if self.choices[member][show].double:  # double
                        if len(winners) + 1 < show.slots:
                            self.appendResult(winners, member, show)
                            self.appendResult(winners, member, show)
                        elif not self.choices[member][show].autoquit \
                                and len(winners) < show.slots:
                            self.appendResult(winners, member, show)
                            self.appendResult(losers, member, show)
                        else:
                            self.appendResult(losers, member, show)
                            self.appendResult(losers, member, show)
                            self.IncrementRanks(member, i, 2)
                    else:  # simple
                        if len(winners) < show.slots:
                            self.appendResult(winners, member, show)
                        else:
                            self.appendResult(losers, member, show)
                            self.IncrementRanks(member, i)
            results.append((show, winners, losers))
        return results