# -*- encoding: utf-8 -*-

# TODO: Rankings archiv Flag erstellen, womit sie nicht mehr neuberechnet
# werden dürfen.

from __future__ import division

from datetime import datetime, time

from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import ugettext as _

from events.models import Event
from . import DAN_RANKS_DICT, LOGGER, set_dirty
from . import managers

kyu_dan_rankings = set()
ladder_rankings = set()
ladder_seasons = set()


class EventRanking(models.Model):
    """
    Event Rankings funktionieren genauso wie Season Rankings.
    Sie beschränken sich aber auf einen Event und werden nur dann angestossen,
    wenn der Event als Turnier markiert wurde.
    """
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
    event = models.ForeignKey(Event, on_delete=models.CASCADE)
    placement = models.PositiveIntegerField(blank=True, null=True)
    avg_placement = models.FloatField(default=4)
    avg_score = models.FloatField(default=0)
    hanchan_count = models.PositiveIntegerField(default=0)
    good_hanchans = models.PositiveIntegerField(default=0)
    won_hanchans = models.PositiveIntegerField(default=0)

    class Meta(object):
        ordering = ('placement', 'avg_placement', '-avg_score',)

    def get_absolute_url(self):
        return reverse('event-ranking', args=[self.event_id])

    def recalculate(self):
        """
        Berechnet die durschnittliche Platzierung und Punkte, u.v.m. neu.

        Diese Daten werden benötigt um die Platzierung zu erstellen. Sie
        können zwar sehr leicht errechnet werden, es macht trotzdem Sinn
        sie zwischen zu speichern.
        """
        LOGGER.info(
            u'Recalculate EventRanking for Player %s in %s',
            self.user, self.event.name
        )
        sum_placement = 0.0
        sum_score = 0.0
        event_hanchans = Hanchan.objects.confirmed(
            user=self.user_id,
            event=self.event_id
        )
        self.hanchan_count = event_hanchans.count()
        self.won_hanchans = 0
        self.good_hanchans = 0

        if self.hanchan_count <= 0:
            self.delete()
        else:
            for hanchan in event_hanchans:
                hanchan.get_playerdata(user=self.user)
                sum_placement += hanchan.placement
                sum_score += hanchan.game_score
                self.won_hanchans += 1 if hanchan.placement == 1 else 0
                self.good_hanchans += 1 if hanchan.placement == 2 else 0
            self.avg_placement = sum_placement / self.hanchan_count
            self.avg_score = sum_score / self.hanchan_count
            self.save(force_update=True)


class Hanchan(models.Model):
    """
    Ein komplette Runde Mahjong, die aus genau 4 Spielern bestehen muss.
    Es werden aber noch andere Tests durchgeführt, ob sie gültig ist.
    Außerdem gehört jede Hanchan zu einer Veranstaltung.
    """
    event = models.ForeignKey(Event, on_delete=models.CASCADE)
    start = models.DateTimeField(
        _('Start'),
        help_text=_('This is crucial to get the right Hanchans that scores')
    )

    player1 = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.PROTECT,
        related_name='user_hanchan+',
        verbose_name=_('Player 1'))
    player1_input_score = models.IntegerField(_('Score'))
    player1_game_score = models.PositiveIntegerField(
        _('Score'), default=0, editable=False)
    player1_placement = models.PositiveSmallIntegerField(
        default=0, editable=False)
    player1_kyu_points = models.SmallIntegerField(
        blank=True, null=True, editable=False)
    player1_dan_points = models.SmallIntegerField(
        blank=True, null=True, editable=False)
    player1_bonus_points = models.SmallIntegerField(
        blank=True, null=True, editable=False)
    player1_comment = models.CharField(
        _('Comment'), blank=True, max_length=255, editable=False)

    player2 = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.PROTECT,
        related_name='user_hanchan+',
        verbose_name=_('Player 2'))
    player2_input_score = models.IntegerField(_('Score'))
    player2_game_score = models.PositiveIntegerField(
        _('Score'), default=0, editable=False)
    player2_placement = models.PositiveSmallIntegerField(
        default=0, editable=False)
    player2_kyu_points = models.SmallIntegerField(
        blank=True, null=True, editable=False)
    player2_dan_points = models.SmallIntegerField(
        blank=True, null=True, editable=False)
    player2_bonus_points = models.SmallIntegerField(
        blank=True, null=True, editable=False)
    player2_comment = models.CharField(
        _('Comment'), blank=True, max_length=255, editable=False)

    player3 = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.PROTECT,
        related_name='user_hanchan+',
        verbose_name=_('Player 3'))
    player3_input_score = models.IntegerField(_('Score'))
    player3_game_score = models.PositiveIntegerField(
        _('Score'), default=0, editable=False)
    player3_placement = models.PositiveSmallIntegerField(
        default=0, editable=False)
    player3_kyu_points = models.SmallIntegerField(
        blank=True, null=True, editable=False)
    player3_dan_points = models.SmallIntegerField(
        blank=True, null=True, editable=False)
    player3_bonus_points = models.SmallIntegerField(
        blank=True, null=True, editable=False)
    player3_comment = models.CharField(
        _('Comment'), blank=True, max_length=255, editable=False)

    player4 = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.PROTECT,
        related_name='user_hanchan+',
        verbose_name=_('Player 4'))
    player4_input_score = models.IntegerField(_('Score'))
    player4_game_score = models.PositiveIntegerField(
        _('Score'), default=0, editable=False)
    player4_placement = models.PositiveSmallIntegerField(
        default=0, editable=False)
    player4_kyu_points = models.SmallIntegerField(
        blank=True, null=True, editable=False)
    player4_dan_points = models.SmallIntegerField(
        blank=True, null=True, editable=False)
    player4_bonus_points = models.SmallIntegerField(
        blank=True, null=True, editable=False)
    player4_comment = models.CharField(
        _('Comment'), blank=True, max_length=255, editable=False)

    comment = models.TextField(_('Comment'), blank=True)
    confirmed = models.BooleanField(
        _('Has been Confirmed'), default=True,
        help_text=_('Only valid and confirmed Hanchans will be counted in the '
                    'rating.')
    )
    player_names = models.CharField(max_length=255, editable=False)
    season = models.PositiveSmallIntegerField(
        _('Season'), editable=False, db_index=True)
    objects = managers.HanchanManager()

    class Meta(object):
        ordering = ('-start',)
        verbose_name = _(u'Hanchan')
        verbose_name_plural = _(u'Hanchans')

    def __str__(self):
        return _("Hanchan from {0:%Y-%m-%d} at {0:%H:%M} with {1}").format(
            self.start, self.player_names
        )

    def clean(self):
        """
        Check if 4 different Players are attending the Game,
        if the Game between the opening hours and the Hanchan has a total of
        100.000 Points.
        """
        errors = {}
        score_sum = 0
        player_set = set()

        try:
            self.clean_fields()
        except ValidationError as exception:
            exception.update_error_dict(errors)
            return

        for i in range(1, 5):
            player = getattr(self, 'player%d' % i)
            input_score = getattr(self, 'player%d_input_score' % i)
            score_sum += input_score
            game_score = input_score if input_score > 0 else 0
            if player in player_set:
                errors['player%d' % i] = _(
                    "%s can't attend the same game multiple times") % player
            else:
                player_set.add(player)
                setattr(self, 'player%d_game_score' % i, game_score)

        # Check if the game was played during the event
        if self.start > timezone.now():
            errors['start'] = _(
                "Games in the future may not be added, Dr. Brown")
        elif not (self.event.start <= self.start <= self.event.end):
            errors['start'] = _("Only games during the event are allowed")
        elif score_sum < 100000:
            errors['player4_input_score'] = _(
                'Gamescore is lower then 100.000 Pt.')
        elif score_sum > 100000:
            errors['player4_input_score'] = _('Gamescore is over 100.000 Pt.')

        if errors:
            raise ValidationError(errors)

    def compute_player_placements(self):
        u"""
        Bestimmt die Platzierung eines der Spieler einer Hanchan und speichert
        diese beim jeweiligen Spieler ab.
        """
        LOGGER.debug("Berechne die Platzierungen neu...")
        player_names = []
        other_player_placement = 0
        other_player_game_score = 0
        placement = 1
        player_nr = 1
        for player in self.player_list:
            if player['game_score'] == other_player_game_score:
                player['placement'] = other_player_placement
            else:
                player['placement'] = placement
            setattr(self, "player%d" % player_nr, player['user'])
            setattr(self, "player%d_input_score" %
                    player_nr, player['input_score'])
            setattr(self, "player%d_game_score" %
                    player_nr, player['game_score'])
            setattr(self, "player%d_placement" %
                    player_nr, player['placement'])
            setattr(self, "player%d_kyu_points" %
                    player_nr, player['kyu_points'])
            setattr(self, "player%d_dan_points" %
                    player_nr, player['dan_points'])
            setattr(self, "player%d_bonus_points" %
                    player_nr, player['bonus_points'])
            setattr(self, "player%d_comment" % player_nr, player['comment'])
            other_player_placement = player['placement']
            other_player_game_score = player['game_score']
            player_names.append(player['user'].username)
            player_nr += 1
            placement += 1
        self.player_names = ', '.join(player_names)

    def get_absolute_url(self):
        return "{url:s}#{id:d}".format(
            url=reverse('event-hanchan-list', kwargs={'event': self.event_id}),
            id=self.pk
        )

    def get_playerdata(self, user):
        """small workaround to access score, placement and points of a
        specific user prominent in the user templates"""
        for player in ('player1', 'player2', 'player3', 'player4'):
            if getattr(self, player) == user:
                self.user = user
                self.input_score = getattr(self, '%s_input_score' % player)
                self.game_score = getattr(self, '%s_game_score' % player)
                self.placement = getattr(self, '%s_placement' % player)
                self.kyu_points = getattr(self, '%s_kyu_points' % player)
                self.dan_points = getattr(self, '%s_dan_points' % player)
                self.bonus_points = getattr(self, '%s_bonus_points' % player)
                self.player_comment = getattr(self, '%s_comment' % player)

    def update_playerdata(self, user, **kwargs):
        """i small workaround to access score, placement of a specific user
        prominent from a in the user templates"""
        for player in ('player1', 'player2', 'player3', 'player4'):
            if getattr(self, player) == user:
                setattr(self, '%s_input_score' % player, self.input_score)
                setattr(self, '%s_game_score' % player, self.game_score)
                setattr(self, '%s_placement' % player, self.placement)
                setattr(self, '%s_kyu_points' % player, self.kyu_points)
                setattr(self, '%s_dan_points' % player, self.dan_points)
                setattr(self, '%s_bonus_points' % player, self.bonus_points)
                setattr(self, '%s_comment' % player, self.player_comment)

    @property
    def player_list(self):
        player_list = []
        for i in range(1, 5):
            player_list.append({
                'user': getattr(self, 'player%d' % i),
                'input_score': getattr(self, 'player%d_input_score' % i),
                'game_score': getattr(self, 'player%d_game_score' % i),
                'placement': getattr(self, 'player%d_placement' % i),
                'kyu_points': getattr(self, 'player%d_kyu_points' % i),
                'dan_points': getattr(self, 'player%d_dan_points' % i),
                'bonus_points': getattr(self, 'player%d_bonus_points' % i),
                'comment': getattr(self, 'player%d_comment' % i),
            })
        # sort player by Score:
        return sorted(player_list, key=lambda player: player['input_score'],
                      reverse=True)

    def save(self, recalculate=True, **kwargs):
        self.season = self.event.mahjong_season or self.start.year
        self.full_clean()
        if recalculate:
            self.compute_player_placements()
            update_ranking(sender=Hanchan, instance=self)
        return models.Model.save(self, **kwargs)


class KyuDanRanking(models.Model):
    u"""
    Die Einstufung des Spielers im Kyu bzw. Dan System.
    Im Gegensatz zum Ladder Ranking ist das nicht Saison gebunden.
    Deswegen läuft es getrennt.
    """
    user = models.OneToOneField(settings.AUTH_USER_MODEL,
                                on_delete=models.CASCADE)
    dan = models.PositiveSmallIntegerField(blank=True, null=True)
    dan_points = models.PositiveIntegerField(default=0)
    max_dan_points = models.PositiveIntegerField(default=0)
    kyu = models.PositiveSmallIntegerField(default=10, blank=True, null=True)
    kyu_points = models.PositiveIntegerField(default=0)
    won_hanchans = models.PositiveIntegerField(default=0)
    good_hanchans = models.PositiveIntegerField(default=0)
    hanchan_count = models.PositiveIntegerField(default=0)
    legacy_date = models.DateField(blank=True, null=True)
    legacy_dan = models.PositiveSmallIntegerField(blank=True, null=True)
    legacy_dan_points = models.PositiveIntegerField(blank=True, null=True)
    legacy_kyu = models.PositiveSmallIntegerField(blank=True, null=True)
    legacy_kyu_points = models.PositiveIntegerField(blank=True, null=True)
    legacy_hanchan_count = models.PositiveIntegerField(blank=True, null=True)
    legacy_good_hanchans = models.PositiveIntegerField(blank=True, null=True)
    legacy_won_hanchans = models.PositiveIntegerField(blank=True, null=True)
    wins_in_a_row = models.PositiveIntegerField(default=0)
    last_hanchan_date = models.DateTimeField(blank=True, null=True)
    objects = managers.KyuDanRankingManager()

    class Meta(object):
        ordering = ('-dan_points', 'dan', '-kyu_points')
        verbose_name = _(u'Kyū/Dan Ranking')
        verbose_name_plural = _(u'Kyū/Dan Rankings')

    @property
    def rank(self):
        if self.dan is not None:
            return "{0:d}. Dan".format(self.dan)
        else:
            return "{0:d}. Kyū".format(self.kyu or 10)

    @property
    def points(self):
        return self.dan_points if self.dan is not None else self.kyu_points

    def __str__(self):
        if self.dan is not None:
            return u"%s - %d. Dan" % (self.user.username, self.dan or 1)
        else:
            return u"%s - %d. Kyū" % (self.user.username, self.kyu or 10)

    def append_3_in_a_row_bonuspoints(self, hanchan):
        u"""
        Wenn der Spieler 3 Siege in Folge hatte, bekommt er so viele Punkte
        das er einen Dan Rang aufsteigt. Dies wird als Kommentar abgespeichert,
        um es besser nachvollziehen zu können.
        """
        if not self.dan or not settings.DAN_3_WINS_IN_A_ROW:
            return
        if hanchan.placement == 1:
            self.wins_in_a_row += 1
        else:
            self.wins_in_a_row = 0
            return
        if self.wins_in_a_row >= 3 and self.dan < 9:
            LOGGER.info(
                'adding bonuspoints for 3 wins in a row for %s', self.user)
            new_dan_rank = self.dan + 1
            new_dan_points = DAN_RANKS_DICT[new_dan_rank] + 1
            bonus_points = new_dan_points - self.dan_points

            LOGGER.debug("Stats for %s:", self.user)
            LOGGER.debug("current dan_points: %d", self.dan_points)
            LOGGER.debug("current dan: %d", self.dan)
            LOGGER.debug(
                "min required points for the next dan: %d", new_dan_points)
            LOGGER.debug("bonus points to add: %d", bonus_points)

            hanchan.dan_points += bonus_points
            hanchan.bonus_points += bonus_points
            hanchan.player_comment += '3 Siege in Folge: +%d Punkte auf den ' \
                                      '%d. Dan. ' % (
                                          bonus_points, new_dan_rank)
            self.dan_points += bonus_points
            self.wins_in_a_row = 0
            self.update_rank()

    def append_tournament_bonuspoints(self, hanchan):
        """
        Prüft ob es die letzte Hanchan in einem Turnier war. Wenn ja werden
        bei Bedarf Bonuspunkte vergeben, falls der Spieler das Turnier
        gewonnen hat.
        :param hanchan: Ein Player Objekt
        """
        bonus_points = 0
        hanchans_this_event = Hanchan.objects.user_hanchans(
            user=self.user, event=hanchan.event
        ).order_by('-start')
        last_hanchan_this_event = hanchans_this_event[0]
        # Das braucht nur am Ende eines Turnieres gemacht werden.
        if hanchan != last_hanchan_this_event: return False
        event_ranking = EventRanking.objects.get(
            user=self.user,
            event=hanchan.event
        )
        if event_ranking.placement == 1:
            bonus_points += settings.TOURNAMENT_WIN_BONUSPOINTS
            hanchan.player_comment += u'+{0:d} Punkte Turnier gewonnen. '.format(
                settings.TOURNAMENT_WIN_BONUSPOINTS)
        if event_ranking.avg_placement == 1:
            bonus_points += settings.TOURNAMENT_FLAWLESS_VICTORY_BONUSPOINTS
            hanchan.player_comment += u'+{0:d} Pkt: alle Spiele des Turnieres gewonnen. '.format(
                settings.TOURNAMENT_FLAWLESS_VICTORY_BONUSPOINTS)

        if bonus_points and self.dan:
            hanchan.dan_points += bonus_points
            self.dan_points += bonus_points
        elif bonus_points:
            hanchan.kyu_points += bonus_points
            self.kyu_points += bonus_points
        hanchan.bonus_points += bonus_points
        return True

    def get_absolute_url(self):
        if self.dan or self.dan_points > 0:
            return reverse('player-dan-score', args=[self.user.username])
        else:
            return reverse('player-kyu-score', args=[self.user.username])

    def calculate(self, since=None, until=None, force_recalc=False):
        """
        Fetches all valid Hanchans from this Player and recalculates his
        Kyu/Dan Ranking.
        """
        valid_hanchans = Hanchan.objects.confirmed(user=self.user)
        valid_hanchans = valid_hanchans.order_by('start')
        if since and self.last_hanchan_date and since < self.last_hanchan_date:
            force_recalc = True
        if until and self.last_hanchan_date and until < self.last_hanchan_date:
            force_recalc = True
        if force_recalc:
            # Setze alles auf die legacy Werte und berechne alles von neuem.
            self.dan = self.legacy_dan
            self.dan_points = self.legacy_dan_points or 0
            self.max_dan_points = self.dan_points
            self.kyu = self.legacy_kyu
            self.kyu_points = self.legacy_kyu_points or 0
            self.hanchan_count = self.legacy_hanchan_count or 0
            self.good_hanchans = self.legacy_good_hanchans or 0
            self.won_hanchans = self.legacy_won_hanchans or 0
            self.last_hanchan_date = None
            self.update_rank()
            since = timezone.make_aware(datetime.combine(
                self.legacy_date,
                time(23, 59, 59))) if self.legacy_date else None
        elif self.last_hanchan_date:
            since = self.last_hanchan_date
        elif self.legacy_date:
            since = timezone.make_aware(
                datetime.combine(self.legacy_date, time(0, 0, 0))
            )
        LOGGER.info(
            "recalculating Kyu/Dan points for %(user)s since %(since)s...",
            {'user': self.user, 'since': str(since)}
        )
        if since:
            valid_hanchans = valid_hanchans.filter(start__gt=since)
        if until:
            valid_hanchans = valid_hanchans.filter(start__lte=until)
        for hanchan in valid_hanchans:
            self.hanchan_count += 1
            hanchan.get_playerdata(self.user)
            if since and hanchan.start < since:
                LOGGER.debug(hanchan, "<", since, "no recalc")
                self.dan_points += hanchan.dan_points or 0
                self.kyu_points += hanchan.kyu_points or 0
                self.update_rank()
            else:
                hanchan.bonus_points = 0
                hanchan.player_comment = ""
                self.update_hanchan_points(hanchan)
                if hanchan.event.mahjong_tournament:
                    self.append_tournament_bonuspoints(hanchan)
                self.update_rank()
                self.append_3_in_a_row_bonuspoints(hanchan)
                hanchan.update_playerdata(self.user)
                hanchan.save(recalculate=False)
            self.won_hanchans += 1 if hanchan.placement == 1 else 0
            self.good_hanchans += 1 if hanchan.placement == 2 else 0
            self.last_hanchan_date = hanchan.start
        self.save(force_update=True)

    def update_hanchan_points(self, hanchan):
        """
        Berechne die Kyu bzw. Dan Punkte für eine Hanchan neu.
        :type hanchan: Hanchan
        :param hanchan: Das Player Objekt das neuberechnet werden soll.
        """
        hanchan.kyu_points = None
        hanchan.dan_points = None
        if hanchan.event.mahjong_tournament and settings.TOURNAMENT_POINT_SYSTEM:
            """Für Turniere gelten andere Regeln zur Punktevergabe:
            1. Platz 4 Punkte
            2. Platz 3 Punkte
            3. Platz 2 Punkte
            4. Platz 1 Punkt"""
            tourney_points = 4 - hanchan.placement
            if self.dan:
                hanchan.dan_points = tourney_points
            else:
                hanchan.kyu_points = tourney_points
        elif self.dan:
            if hanchan.game_score >= 60000:
                hanchan.dan_points = 3
            elif hanchan.game_score == 0:
                hanchan.dan_points = -3
            elif hanchan.placement == 1:
                hanchan.dan_points = 2
            elif hanchan.placement == 2:
                hanchan.dan_points = 1
            elif hanchan.placement == 3:
                hanchan.dan_points = -1
            elif hanchan.placement == 4:
                hanchan.dan_points = -2
        # otherwise player must be in the kyu ranking
        elif hanchan.game_score >= 60000:
            hanchan.kyu_points = 3
        elif hanchan.game_score >= 30000:
            hanchan.kyu_points = 1
        elif hanchan.input_score < 10000:
            hanchan.kyu_points = -1
        else:
            hanchan.kyu_points = 0

        # Add the hanchans points to the players points
        if self.dan:
            # Only substract so much points that player has 0 Points:
            if self.dan_points + hanchan.dan_points < 0:
                hanchan.player_comment = 'Spieler unterschreitet 0 Punkte.' \
                    '(Original {} Punkte)'.format(hanchan.dan_points)
                hanchan.dan_points -= (self.dan_points + hanchan.dan_points)
            self.dan_points += hanchan.dan_points
        else:
            # Only substract so much points that player has 0 Points:
            if self.kyu_points + hanchan.kyu_points < 0:
                hanchan.player_comment = 'Spieler unterschreitet 0 Punkte.' \
                    '(Original {} Punkte)'.format(hanchan.kyu_points)
                hanchan.kyu_points -= (self.kyu_points + hanchan.kyu_points)
            self.kyu_points += hanchan.kyu_points

    def update_rank(self):
        # Update Dan ranking:
        if self.dan or self.dan_points > 0:
            if settings.DAN_ALLOW_DROP_DOWN:
                self.dan = max((dan for min_points, dan in settings.DAN_RANKS
                                if self.dan_points > min_points))
            else:
                self.max_dan_points = max(self.max_dan_points, self.dan_points)
                self.dan = max((dan for min_points, dan in settings.DAN_RANKS
                                if self.max_dan_points > min_points))

        # jump from Kyu to Dan
        elif self.kyu_points > 50:
            self.dan = 1
            self.dan_points = 0
            self.kyu = None
            self.kyu_points = 0
            self.wins_in_a_row = 0
        # update Kyu ranking_
        else:
            self.kyu = min((kyu for min_points, kyu in settings.KYU_RANKS
                            if self.kyu_points > min_points))


class SeasonRanking(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
    season = models.PositiveSmallIntegerField(_('Season'))
    placement = models.PositiveIntegerField(blank=True, null=True)
    avg_placement = models.FloatField(blank=True, null=True)
    avg_score = models.FloatField(blank=True, null=True)
    hanchan_count = models.PositiveIntegerField(default=0)
    good_hanchans = models.PositiveIntegerField(default=0)
    won_hanchans = models.PositiveIntegerField(default=0)
    objects = managers.SeasonRankingManager()

    class Meta(object):
        ordering = ('placement', 'avg_placement', '-avg_score',)

    def get_absolute_url(self):
        return reverse('player-ladder-score', args=[self.user.username])

    def recalculate(self, until=None):
        season_hanchans = Hanchan.objects.season_hanchans(
            user=self.user, season=self.season, until=until)
        sum_placement = 0
        sum_score = 0
        self.placement = None
        self.hanchan_count = season_hanchans.count()
        self.good_hanchans = 0
        self.won_hanchans = 0

        LOGGER.info(
            u'Recalculate LadderRanking for Player %s in Season %s',
            self.user, self.season)
        for hanchan in season_hanchans:
            sum_placement += hanchan.placement
            sum_score += hanchan.game_score
            self.won_hanchans += 1 if hanchan.placement == 1 else 0
            self.good_hanchans += 1 if hanchan.placement == 2 else 0
        try:
            self.avg_placement = sum_placement / self.hanchan_count
            self.avg_score = sum_score / self.hanchan_count
        except ZeroDivisionError:
            self.avg_placement = None
            self.avg_score = None
        self.save(force_update=True)


def update_ranking(sender, instance, **kwargs):
    for user in (
            instance.player1, instance.player2, instance.player3,
            instance.player4):
        LOGGER.debug(
            "marking %(user)s's kyu/dan for recalculation since %(start)s",
            {'user': user, 'start': str(instance.start)}
        )
        set_dirty(user=user.id, hanchan_date=instance.start)
        LOGGER.debug("marking event %s for recalculation.", instance.event)
        set_dirty(event=instance.event_id, user=user.id)
        if instance.season:
            LOGGER.debug(
                "marking %s's ladder %i season for recalculation.",
                user, instance.season
            )
            set_dirty(user=user.id, season=instance.season)

    LOGGER.debug("marking season %d for recalculation.", instance.season)
    set_dirty(season=instance.season)


models.signals.pre_delete.connect(update_ranking, sender=Hanchan)
