# Foobar - a sport competition management software
# Copyright (c) 2026 Felipe Cuqueto Piekarz
# Copyright (c) 2026 Luiz Soneghet Nascimento
# Copyright (c) 2026 Rodolfo Ribeiro Gomes
#
# This file is part of Foobar.
#
# Foobar is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# Foobar is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Foobar.  If not, see <https://www.gnu.org/licenses/>.

from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.core import validators

from django.template.defaultfilters import slugify

from django.contrib.auth.models import User

class Evento(models.Model):
    nome = models.CharField(_('nome'), max_length=200)
    icone = models.CharField(_('índice'), max_length=200, blank=True)
    descricao = models.CharField(_('descrição'), max_length=200, blank=True)
    criado_em = models.DateTimeField(_("data de criação"), auto_now_add=True)
    def __str__(self):
        return self.nome

class Edicao(models.Model):
    evento = models.ForeignKey(Evento, on_delete=models.CASCADE, related_name='edicoes', verbose_name=_('evento'))
    nome = models.CharField(_('nome'), max_length=200)
    icone = models.CharField(_('ícone'), max_length=200, blank=True)
    descricao = models.CharField(_('descrição'), max_length=200, blank=True)
    inscricao_inicio = models.DateTimeField(_("início das inscrições"))
    inscricao_fim = models.DateTimeField(_("fim das inscrições"))
    inicio_data = models.DateField(_("início do evento"))
    fim_data = models.DateField(_("fim do evento"))
    criado_em = models.DateTimeField(_("data de criação"), auto_now_add=True)
    class Meta:
        verbose_name = _("Edição")
        verbose_name_plural = _("Edições")
    def __str__(self):
        return _("{evento} - {nome}").format(evento=self.evento, nome=self.nome)

class Esporte(models.Model):
    nome = models.CharField(_('nome'), max_length=200)
    icone = models.CharField(_('ícone'), max_length=200, blank=True)
    descricao = models.CharField(_('descrição'), max_length=200, blank=True)
    criado_em = models.DateTimeField(_("data de criação"), auto_now_add=True)
    def __str__(self):
        return self.nome

class Torneio(models.Model):
    MASCULINO = 'M'
    FEMININO = 'F'
    MISTO = 'T'
    
    NAIPES = (
        (MASCULINO, _('masculino')), 
        (FEMININO, _('feminino')), 
        (MISTO, _('misto')), 
    )
    
    edicao = models.ForeignKey(Edicao, on_delete=models.CASCADE, related_name='torneios', verbose_name=_('edição'))
    esporte = models.ForeignKey(Esporte, on_delete=models.CASCADE, related_name='torneios', verbose_name=_('esporte'))
    nome = models.CharField(_('nome'), max_length=200)
    naipe = models.CharField(_('naipe'), max_length=1, choices=NAIPES)
    times_minimo = models.PositiveSmallIntegerField(_('quantidade mínima de times'), default=6)
    times_maximo = models.PositiveSmallIntegerField(_('quantidade máxima de times'), default=18)
    slug = models.SlugField(_('slug'), default="", null=True, unique=True)
    descricao = models.CharField(_('descrição'),max_length=200, blank=True)
    criado_em = models.DateTimeField(_("data de criação"), auto_now_add=True)
    atualizado_em = models.DateTimeField(_("data de atualização"), auto_now=True)

    def __str__(self):
        return _("{nome} ({edicao})").format(nome=self.nome, edicao=self.edicao)
    
    def save(self, *args, **kwargs):
        if self.edicao_id:
            self.edicao = Edicao.objects.get(pk=self.edicao_id)
        if not self.slug:
            self.slug = slugify(self.nome)
        return super().save(*args, **kwargs)
        
    @property
    def individual(self):
        if not self.tem_configuracao_time():
            return None
        return self.configuracaotime.individual
        
    @property
    def em_duplas(self):
        if not self.tem_configuracao_time():
            return None
        return self.configuracaotime.em_duplas
        
    @property
    def inscricoes_encerradas(self):
        return timezone.now() > self.edicao.inscricao_fim
        
    @property
    def inscricoes_abertas(self):
        return timezone.now() >= self.edicao.inscricao_inicio and timezone.now() < self.edicao.inscricao_fim
    
    def tem_configuracao_time(self):
        return hasattr(self, 'configuracaotime')
        
    def inscricoes_aprovadas(self):
        if not self.tem_configuracao_time:
            return False
        return self.configuracaotime.times.filter(aprovado=True)
    
    def inscricoes_pendentes(self):
        if not self.tem_configuracao_time:
            return False
        return self.configuracaotime.times.filter(aprovado=None)
    
    def times_sem_numero(self):
        if not self.tem_configuracao_time:
            return False
        return self.configuracaotime.times.filter(aprovado=True, numero=0)

    def gera_partidas_chaveamento(self, eliminatorias : bool, disputa_terceiro_lugar : bool, ):
        qtd_times = len(self.inscricoes_aprovadas())
        mapa_chaves = {
            2: [2], 3:[3], 4:[4], 5:[5],
            6:[3, 3], 
            7:[4, 3], 
            8:[4, 4], 
            9:[3, 3, 3], 
            10:[3, 3, 2, 2], 
            11:[3, 3, 3, 2], 
            12:[3, 3, 3, 3], 
            13:[4, 3, 3, 3], 
            14:[3, 3, 3, 3, 2], 
            15:[3, 3, 3, 3, 3]
        }
        mapa_eliminatorias = { #quantos jogam uma eliminatória das oitavas, quantos passam direto para quartas, quantos direto para semifinais
            2:[], 
            3:[],
            4:[0, 0, 4], # total de partidas: 0 oitavas + 0 quartas + 2 semifinais + 1 final = 4 +1 (disputa de terceiro?)
            5:[0, 2, 3], # total de partidas: 0 oitavas + 1 quartas + 2 semifinais + 1 final = 4 +1 (disputa de terceiro?)
            6:[0, 4, 2], # total de partidas: 0 oitavas + 2 quartas + 2 semifinais + 1 final = 5 +1 (disputa de terceiro?)
            7:[0, 6, 1], # total de partidas: 0 oitavas + 3 quartas + 2 semifinais + 1 final = 6 +1 (disputa de terceiro?)
            8:[0, 8, 0], # total de partidas: 0 oitavas + 4 quartas + 2 semifinais + 1 final = 7 +1 (disputa de terceiro?)
            9:[2, 7, 0], # total de partidas: 1 oitavas + 4 quartas + 2 semifinais + 1 final = 8 +1 (disputa de terceiro?)
            10:[4, 6, 0], # total de partidas: 2 oitavas + 4 quartas + 2 semifinais + 1 final = 9 +1 (disputa de terceiro?)
            11:[6, 5, 0], # total de partidas: 3 oitavas + 4 quartas + 2 semifinais + 1 final = 10 +1 (disputa de terceiro?)
            12:[8, 4, 0], # total de partidas: 4 oitavas + 4 quartas + 2 semifinais + 1 final = 11 +1 (disputa de terceiro?)
            13:[10, 3, 0], # total de partidas: 5 oitavas + 4 quartas + 2 semifinais + 1 final = 12 +1 (disputa de terceiro?)
            14:[12, 2, 0], # total de partidas: 6 oitavas + 4 quartas + 2 semifinais + 1 final = 13 +1 (disputa de terceiro?)
            15:[14, 1, 0], # total de partidas: 7 oitavas + 4 quartas + 2 semifinais + 1 final = 14 +1 (disputa de terceiro?)
            16:[16, 0, 0], # total de partidas: 8 oitavas + 4 quartas + 2 semifinais + 1 final = 15 +1 (disputa de terceiro?)
        }
        #- 16 : direto das oitavas
        #- 15 equipes: um time passa direto para as oitavas
        #- 14: dois times passam direto para as oitavas
        #- 13: cinco jogos + 3 passam direto
        #- 12: 2 jogos + 6 passam diretos (4jogos + 4 direto)
        #- 11: 3 jogos + 5 passam direto
        #- 10: 2 jogos + 6 passam direto
        #- 9: 1 jogo + 7 direto
        #- 8: direto das quartas
        #- 7: 3 quartas + 1 direto
        #- 6: 2 quartas + 2 direto
        #- 5: 1 quarta + 3 direto
        #- <= 4: todos contra todos
        for k, v in mapa_chaves:
            if k != sum(mapa_chaves):
                print('PROBLEMA COM O CHAVEAMENTO DE '+k+' EQUIPES!')
                break
        grupos = mapa_chaves[qtd_times]
        # criar fase de grupos:
        #   partidas estarão com grupo definido (por uma letra)
        #                                      proxima_partida_vencedor nula
        #                                      proxima_partida_vencedor_indice 0 (ignorada)
        
        
        # criar fase eliminatória:
        #   partidas estarão com grupo indefinido (caractere espaço " ")
        #                                      proxima_partida_vencedor definida como a próxima partida a ser jogada pelo vencedor
        #                                      proxima_partida_vencedor_indice como 1 ou 2, conforme o vencedor será o time1 ou time2 da próxima partida
        # como fazer a disputa do terceiro lugar?
        
        # sorteio: raspadinha, bolinha no copo, roleta, moeda, bingo, dado
        
    def gera_partidas_chaveamento_eliminatorias(self, disputa_terceiro_lugar : bool ):
        if self.partidas.count() > 0:
            return
        
        times = self.inscricoes_aprovadas()
        qtd_times = len(times)

        if qtd_times < 4:
            disputa_terceiro_lugar = False

        if qtd_times == 2:
            partida = Partida(torneio = self)
            partida.numero = 1
            partida.grupo = ' '
            partida.numero_time1 = 1
            partida.numero_time2 = 2
            #partida.horario
            #partida.local
            #partida.arbitro
            partida.save()
        elif qtd_times == 3:
            #final
            partida_final = Partida(torneio = self)
            partida_final.numero = 2
            partida_final.grupo = ' '
            #partida_final.numero_time1 = ?
            partida_final.numero_time2 = 3
            partida_final.save()

            partida = Partida(torneio = self)
            partida.numero = 1
            partida.grupo = ' '
            partida.numero_time1 = 1
            partida.numero_time2 = 2
            partida.proxima_partida_vencedor = partida_final
            partida.proxima_partida_vencedor_indice = 1
            partida.save()
        elif qtd_times <= 32:
            qtd_partidas = qtd_times - 1 if not disputa_terceiro_lugar else qtd_times
            partidas = []
            for n in range(1, qtd_partidas+1):
                partidas.append(Partida(torneio = self, grupo = ' ', numero = n))
            
            indice_16avos = 0
            qtd_16avos = max(0, qtd_times - 16)
            qtd_passam_direto_para_oitavas = max(0, 2*16 - qtd_times)
            
            indice_oitavas = indice_16avos + qtd_16avos
            qtd_oitavas = max(0, min(8, qtd_times - 8))
            qtd_confrontos_direto_nas_oitavas = max(0, qtd_passam_direto_para_oitavas - 8)
            qtd_passam_direto_para_quartas = max(0, 2*8 - qtd_times)
            
            indice_quartas = indice_oitavas + qtd_oitavas
            qtd_quartas = max(0, min(4, qtd_times - 4))
            qtd_confrontos_direto_nas_quartas = max(0, qtd_passam_direto_para_quartas - 4)
            qtd_passam_direto_para_semifinais = max(0, 2*4 - qtd_times)
            
            indice_semifinais = indice_quartas + qtd_quartas
            qtd_semis = max(0, min(2, qtd_times - 2))
            qtd_confrontos_direto_nas_semifinais = max(0, qtd_passam_direto_para_semifinais - 2)
            qtd_passam_direto_para_final = max(0, 2*2 - qtd_times)

            indice_final = -1 # indice_semifinais + qtd_semis + (1 if disputa_terceiro_lugar else 0)
            indice_terceiro_lugar = -2

            num_time = 1
            for n in range(0, qtd_16avos):
                if num_time > qtd_times:
                    break
                partida = partidas[n + indice_16avos]
                partida.numero_time1 = num_time
                partida.numero_time2 = num_time + 1
                num_time += 2
                
                n_oitavas = qtd_confrontos_direto_nas_oitavas +( n  if n < qtd_passam_direto_para_oitavas else qtd_passam_direto_para_oitavas + (n - qtd_passam_direto_para_oitavas) // 2)
                pos_oitavas = 2 if n < qtd_passam_direto_para_oitavas else 1 + (n - qtd_passam_direto_para_oitavas) % 2
                partida.proxima_partida_vencedor = partidas[n_oitavas + indice_oitavas]
                partida.proxima_partida_vencedor_indice = pos_oitavas

            for n in range(0, qtd_oitavas):
                partida = partidas[n + indice_oitavas]
                if qtd_16avos == 0:
                    partida.numero_time1 = num_time
                    partida.numero_time2 = num_time + 1
                    num_time += 2
                elif num_time <= qtd_times:
                    partida.numero_time1 = num_time
                    num_time += 1
                    if n < qtd_confrontos_direto_nas_oitavas:
                        partida.numero_time2 = num_time
                        num_time += 1
                
                n_quartas = qtd_confrontos_direto_nas_quartas +( n  if n < qtd_passam_direto_para_quartas else qtd_passam_direto_para_quartas + (n - qtd_passam_direto_para_quartas) // 2)
                pos_quartas = 2 if n < qtd_passam_direto_para_quartas else 1 + (n - qtd_passam_direto_para_quartas) % 2
                partida.proxima_partida_vencedor = partidas[n_quartas + indice_quartas]
                partida.proxima_partida_vencedor_indice = pos_quartas

            for n in range(0, qtd_quartas):
                partida = partidas[n + indice_quartas]
                if qtd_oitavas == 0:
                    partida.numero_time1 = num_time
                    partida.numero_time2 = num_time + 1
                    num_time += 2
                elif num_time <= qtd_times:
                    partida.numero_time1 = num_time
                    num_time += 1
                    if n < qtd_confrontos_direto_nas_quartas:
                        partida.numero_time2 = num_time
                        num_time += 1
                
                n_semis = qtd_confrontos_direto_nas_semifinais + (n  if n < qtd_passam_direto_para_semifinais else qtd_passam_direto_para_semifinais + (n - qtd_passam_direto_para_semifinais) // 2)
                pos_semis = 2 if n < qtd_passam_direto_para_semifinais else 1 + (n - qtd_passam_direto_para_semifinais) % 2
                partida.proxima_partida_vencedor = partidas[n_semis + indice_semifinais]
                partida.proxima_partida_vencedor_indice = pos_semis

            for n in range(0, qtd_semis):
                partida = partidas[n + indice_semifinais]
                if qtd_quartas == 0:
                    partida.numero_time1 = num_time
                    partida.numero_time2 = num_time + 1
                    num_time += 2
                elif num_time <= qtd_times:
                    partida.numero_time1 = num_time
                    num_time += 1
                    if n < qtd_confrontos_direto_nas_semifinais:
                        partida.numero_time2 = num_time
                        num_time += 1
                
                pos_final = 2 if n < qtd_passam_direto_para_final else 1 + (n - qtd_passam_direto_para_final) % 2
                partida.proxima_partida_vencedor = partidas[indice_final]
                partida.proxima_partida_vencedor_indice = pos_final
            
            if qtd_passam_direto_para_final:
                partida = partidas[indice_final]
                partida.numero_time1 = num_time
                num_time += 1
                
            if disputa_terceiro_lugar:
                partida_terceiro_lugar = partidas[indice_terceiro_lugar]
                partidas[-4].proxima_partida_perdedor = partida_terceiro_lugar
                partidas[-4].proxima_partida_perdedor_indice = 1
                partidas[-3].proxima_partida_perdedor = partida_terceiro_lugar
                partidas[-3].proxima_partida_perdedor_indice = 2

            for p in reversed(partidas):
                p.save()
        # sorteio: raspadinha, bolinha no copo, roleta, moeda, bingo, dado
        

    def gera_partidas_chaveamento_todos_contra_todos(self, eliminatorias : bool, disputa_terceiro_lugar : bool, ):
        qtd_times = len(self.inscricoes_aprovadas())
        mapa_chaves = {
            2: [2], 3:[3], 4:[4], 5:[5],
            6:[3, 3], 
            7:[4, 3], 
            8:[4, 4], 
            9:[3, 3, 3], 
            10:[3, 3, 2, 2], 
            11:[3, 3, 3, 2], 
            12:[3, 3, 3, 3], 
            13:[4, 3, 3, 3], 
            14:[3, 3, 3, 3, 2], 
            15:[3, 3, 3, 3, 3]
        }
        mapa_eliminatorias = { #quantos jogam uma eliminatória das oitavas, quantos passam direto para quartas, quantos direto para semifinais
            2:[], 
            3:[],
            4:[0, 0, 4], # total de partidas: 0 oitavas + 0 quartas + 2 semifinais + 1 final = 4 +1 (disputa de terceiro?)
            5:[0, 2, 3], # total de partidas: 0 oitavas + 1 quartas + 2 semifinais + 1 final = 4 +1 (disputa de terceiro?)
            6:[0, 4, 2], # total de partidas: 0 oitavas + 2 quartas + 2 semifinais + 1 final = 5 +1 (disputa de terceiro?)
            7:[0, 6, 1], # total de partidas: 0 oitavas + 3 quartas + 2 semifinais + 1 final = 6 +1 (disputa de terceiro?)
            8:[0, 8, 0], # total de partidas: 0 oitavas + 4 quartas + 2 semifinais + 1 final = 7 +1 (disputa de terceiro?)
            9:[2, 7, 0], # total de partidas: 1 oitavas + 4 quartas + 2 semifinais + 1 final = 8 +1 (disputa de terceiro?)
            10:[4, 6, 0], # total de partidas: 2 oitavas + 4 quartas + 2 semifinais + 1 final = 9 +1 (disputa de terceiro?)
            11:[6, 5, 0], # total de partidas: 3 oitavas + 4 quartas + 2 semifinais + 1 final = 10 +1 (disputa de terceiro?)
            12:[8, 4, 0], # total de partidas: 4 oitavas + 4 quartas + 2 semifinais + 1 final = 11 +1 (disputa de terceiro?)
            13:[10, 3, 0], # total de partidas: 5 oitavas + 4 quartas + 2 semifinais + 1 final = 12 +1 (disputa de terceiro?)
            14:[12, 2, 0], # total de partidas: 6 oitavas + 4 quartas + 2 semifinais + 1 final = 13 +1 (disputa de terceiro?)
            15:[14, 1, 0], # total de partidas: 7 oitavas + 4 quartas + 2 semifinais + 1 final = 14 +1 (disputa de terceiro?)
            16:[16, 0, 0], # total de partidas: 8 oitavas + 4 quartas + 2 semifinais + 1 final = 15 +1 (disputa de terceiro?)
        }
        #- 16 : direto das oitavas
        #- 15 equipes: um time passa direto para as oitavas
        #- 14: dois times passam direto para as oitavas
        #- 13: cinco jogos + 3 passam direto
        #- 12: 2 jogos + 6 passam diretos (4jogos + 4 direto)
        #- 11: 3 jogos + 5 passam direto
        #- 10: 2 jogos + 6 passam direto
        #- 9: 1 jogo + 7 direto
        #- 8: direto das quartas
        #- 7: 3 quartas + 1 direto
        #- 6: 2 quartas + 2 direto
        #- 5: 1 quarta + 3 direto
        #- <= 4: todos contra todos
        for k, v in mapa_chaves:
            if k != sum(mapa_chaves):
                print('PROBLEMA COM O CHAVEAMENTO DE '+k+' EQUIPES!')
                break
        grupos = mapa_chaves[qtd_times]
        # criar fase de grupos:
        #   partidas estarão com grupo definido (por uma letra)
        #                                      proxima_partida_vencedor nula
        #                                      proxima_partida_vencedor_indice 0 (ignorada)
        
        
        # criar fase eliminatória:
        #   partidas estarão com grupo indefinido (caractere espaço " ")
        #                                      proxima_partida_vencedor definida como a próxima partida a ser jogada pelo vencedor
        #                                      proxima_partida_vencedor_indice como 1 ou 2, conforme o vencedor será o time1 ou time2 da próxima partida
        # como fazer a disputa do terceiro lugar?
        
        # sorteio: raspadinha, bolinha no copo, roleta, moeda, bingo, dado
        

    @property
    def partidas(self):
        return Partida.objects.filter(torneio = self)
    
    def qtd_partidas(self):
        return Partida.objects.filter(torneio = self).count()

class ConfiguracaoTime(models.Model):
    total_minimo = models.PositiveSmallIntegerField(_('quantidade total mínima de integrantes'), default=1, validators=[validators.MinValueValidator(1)])
    total_maximo = models.PositiveSmallIntegerField(_('quantidade total máxima de integrantes'), default=18)
    feminino_minimo = models.PositiveSmallIntegerField(_('quantidade mínima de integrantes mulheres'), default=0)
    feminino_maximo = models.PositiveSmallIntegerField(_('quantidade máxima de integrantes mulheres'), default=18)
    masculino_minimo = models.PositiveSmallIntegerField(_('quantidade mínima de integrantes homens'), default=0)
    masculino_maximo = models.PositiveSmallIntegerField(_('quantidade máxima de integrantes homens'), default=18)
    obs = models.CharField(_('Observação'), max_length=200, blank=True)
    torneio = models.OneToOneField(Torneio, on_delete=models.CASCADE, null=True, verbose_name=_('torneio'))

    class Meta:
        verbose_name_plural = _("ConfiguraçõesTime")

    def __str__(self):
        return _('Configuração de time de {torneio}').format(torneio=self.torneio)
    
    @property
    def individual(self):
        return self.total_minimo == 1 and self.total_maximo == 1
        
    @property
    def em_duplas(self):
        return self.total_minimo == 2 and self.total_maximo == 2
        
    def valido(self):
        return self.total_minimo <= self.masculino_minimo and self.total_minimo <= self.feminino_minimo and self.total_maximo >= self.masculino_maximo and self.total_maximo >= self.feminino_maximo

class Time(models.Model):
    configuracao = models.ForeignKey(ConfiguracaoTime, on_delete=models.CASCADE, related_name='times', verbose_name=_('Configuração dos times'))
    nome = models.CharField(_('nome'), max_length=50)
    cor1 = models.CharField(_('cor 1'), max_length=7, blank=True)
    cor2 = models.CharField(_('cor 2'), max_length=7, blank=True)
    aprovado = models.BooleanField(_('homologado'), default = None, null=True, blank=True )
    aprovacao_por = models.ForeignKey(User, on_delete=models.CASCADE, null=True, verbose_name='Homologação por')
    numero  = models.PositiveSmallIntegerField(_('número do time'), default=0) # número do time sorteado para o chaveamento. Zero significa não sorteado;
    criado_em = models.DateTimeField(_("data de criação"), auto_now_add=True)
    
    def __str__(self):
        return self.nome if self.numero == 0  else f"{self.nome} ({self.numero})"
    
    class Meta:
        permissions = [
            ("can_definir_numero_time", "Definir posição do time no chaveamento"),
        ]
    
    def conforme(self):
        integrantes = self.integrantes.all()

        total = len(integrantes)
        if self.configuracao.total_maximo > 0 and total > self.configuracao.total_maximo:
            return False
        if total < self.configuracao.total_minimo:
            return False

        masc_total = 0
        fem_total = 0
        out_total = 0
        for integrante in integrantes:
            if integrante.genero == 'M':
                masc_total = masc_total + 1
                if self.configuracao.masculino_maximo > 0 and masc_total > self.configuracao.masculino_maximo:
                    return False
            elif integrante.genero == 'F':
                fem_total = fem_total + 1
                if self.configuracao.feminino_maximo > 0 and fem_total > self.configuracao.feminino_maximo:
                    return False
            else:
                out_total = out_total + 1
        if masc_total < self.configuracao.masculino_minimo:
            return False
        if fem_total < self.configuracao.feminino_minimo:
            return False
        return True
    
    def qtd_vagas_disponiveis(self):
        qtd = len(self.integrantes.all())
        vagas = self.configuracao.total_maximo
        if qtd >= vagas:
            return 0
        return vagas - qtd

    def qtd_vagas_masculinas_disponiveis(self):
        qtd = len(self.integrantes.filter(genero='M'))
        vagas = self.configuracao.masculino_maximo
        if qtd >= vagas:
            return 0
        return vagas - qtd

    def qtd_vagas_femininas_disponiveis(self):
        qtd = len(self.integrantes.filter(genero='F'))
        vagas = self.configuracao.feminino_maximo
        if qtd >= vagas:
            return 0
        return vagas - qtd
    
    def obter_partidas(self):
        times1 = Partida.objects.filter(torneio=self.configuracao.torneio, time1=self)
        times2 = Partida.objects.filter(torneio=self.configuracao.torneio, time2=self)
        return times1.aggregate(times2)

class Integrante(models.Model):
    MASCULINO = 'M'
    FEMININO = 'F'
    OUTRO = 'O'
    
    GENERO = (
        (MASCULINO, 'masculino'), 
        (FEMININO, 'feminino'), 
        (OUTRO, 'outro'), 
    )
    
    time = models.ForeignKey(Time, on_delete=models.CASCADE, related_name='integrantes', verbose_name=_('time'))
    nome = models.CharField(_('nome'), max_length=200)
    genero = models.CharField(_('gênero'), max_length=1, choices=GENERO)
    criado_em = models.DateTimeField(_("data de criação"), auto_now_add=True)
    atualizado_em = models.DateTimeField(_("data de atualização"), auto_now=True)

class Partida(models.Model):
    torneio = models.ForeignKey(Torneio, on_delete=models.CASCADE, related_name='partidas', verbose_name=_('torneio'))
    numero = models.PositiveSmallIntegerField('número da partida')
    grupo = models.CharField(_('grupo'), max_length=1)
    numero_time1 = models.PositiveSmallIntegerField(_('número do time 1'), default=0)
    numero_time2 = models.PositiveSmallIntegerField(_('número do time 2'), default=0)
#    time1 = models.ForeignKey(Time, on_delete=models.CASCADE, related_name="times1", null=True)
#    time2 = models.ForeignKey(Time, on_delete=models.CASCADE, related_name="times2", null=True)
    horario = models.DateTimeField(_("horário da partida"), null=True)
    local = models.CharField(_('local'), max_length=30, blank=True)
    arbitro = models.CharField(_('árbitro'), max_length=30, blank=True)
    proxima_partida_vencedor = models.ForeignKey('self', null=True, on_delete=models.CASCADE, related_name='filhos_vencedores', verbose_name=_('próxima partida para o vencedor'))
    proxima_partida_vencedor_indice = models.PositiveSmallIntegerField(default=0, validators=[validators.MinValueValidator(0), validators.MaxValueValidator(2)], verbose_name=_('posição na próxima partida para o vencedor'))
    proxima_partida_perdedor = models.ForeignKey('self', null=True, on_delete=models.CASCADE, related_name='filhos_perdedores', verbose_name=_('próxima partida para o perdedor'))
    proxima_partida_perdedor_indice = models.PositiveSmallIntegerField(default=0, validators=[validators.MinValueValidator(0), validators.MaxValueValidator(2)], verbose_name=_('posição na próxima partida para o perdedor'))
    resultado_time1 = models.IntegerField(_('pontuação do time 1'), default=-1000)
    resultado_time2 = models.IntegerField(_('pontuação do time 2'), default=-1000)
    wo = models.BooleanField(_('WO'), default=False, null=False, blank=False)
    vencedor = models.SmallIntegerField(_('vencedor'), default=-1, validators=[validators.MinValueValidator(-1), validators.MaxValueValidator(2)]) # -1 é indefinido, 0 é empate, 1 é time 1 e 2 é time 2
    criado_em = models.DateTimeField(_("data de criação"), auto_now_add=True)
    atualizado_em = models.DateTimeField(_("data de atualização"), auto_now=True)

    class Meta:
        permissions = [
            ("can_definir_resultado_partida", "Definir resultado da partida e sua pontuação"),
        ]

    def time1(self):
        try:
            return Time.objects.get(configuracao=self.torneio.configuracaotime, numero=self.numero_time1)
        except:
            return None

    def time2(self):
        try:
            return Time.objects.get(configuracao=self.torneio.configuracaotime, numero=self.numero_time2)
        except:
            return None
    
    def time_vencedor(self):
        if self.vencedor == 1:
            return self.time1()
        elif self.vencedor == 2:
            return self.time2()
        return None
    
    def finalizada(self):
        return self.vencedor != -1
    
    def com_times_definidos(self):
        if self.numero_time1 != 0:
            if not self.time1():
                return False
        else:
            filho = self.partida_filho1()
            if not filho:
                return False
            if not filho.finalizada():
                return False
        if self.numero_time2 != 0:
            if not self.time2():
                return False
        else:
            filho = self.partida_filho2()
            if not filho:
                return False
            if not filho.finalizada():
                return False
        return True

    def partida_filho(self, indice):
        if indice <= 0 or indice > 2:
            return None # TODO raise exception
        try:
            return Partida.objects.get(torneio=self.torneio, proxima_partida_vencedor=self, proxima_partida_vencedor_indice = indice)
        except: #DoesNotExist
            try:
                return Partida.objects.get(torneio=self.torneio, proxima_partida_perdedor=self, proxima_partida_perdedor_indice = indice)
            except:
                return None
    
    def partida_filho1(self):
        return self.partida_filho(1)
        
    def partida_filho2(self):
        return self.partida_filho(2)
    
    def permutar_filhos(self):
        if self.numero_time1 == 0 or self.numero_time2 == 0:
            filho1 = self.partida_filho1()
            filho2 = self.partida_filho2()
            if filho1:
                if filho1.proxima_partida_vencedor == self:
                    filho1.proxima_partida_vencedor_indice = filho1.proxima_partida_vencedor_indice ^ 0x03
                elif filho1.proxima_partida_perdedor == self:
                    filho1.proxima_partida_perdedor_indice = filho1.proxima_partida_perdedor_indice ^ 0x03
                else:
                    raise(_("Erro interno na permuta do filho 1"))
                filho1.save()
            if filho2:
                if filho2.proxima_partida_vencedor == self:
                    filho2.proxima_partida_vencedor_indice = filho2.proxima_partida_vencedor_indice ^ 0x03
                elif filho2.proxima_partida_perdedor == self:
                    filho2.proxima_partida_perdedor_indice = filho2.proxima_partida_perdedor_indice ^ 0x03
                else:
                    raise(_("Erro interno na permuta do filho 2"))
                filho2.save()

        numero_time1 = self.numero_time1
        self.numero_time1 = self.numero_time2
        self.numero_time2 = numero_time1
        self.save()
    
    def permutar_com_partida(self, partida2):
        self.permutar_um_time_com_partida(partida2, 1)
        self.permutar_um_time_com_partida(partida2, 2)
    
    def permutar_um_time_com_partida(self, partida2, indice):
        partida1 = self
        
        p1_numero_time = partida1.numero_time1 if indice == 1 else partida1.numero_time2
        p2_numero_time = partida2.numero_time1 if indice == 1 else partida2.numero_time2
        
        if p1_numero_time == 0 or p2_numero_time == 0:
            p1_filho = partida1.partida_filho(indice)
            p2_filho = partida2.partida_filho(indice)
            if p1_filho:
                if p1_filho.proxima_partida_vencedor == partida1:
                    p1_filho.proxima_partida_vencedor = partida2
                elif p1_filho.proxima_partida_perdedor == partida1:
                    p1_filho.proxima_partida_perdedor = partida2
                else:
                    raise(_("Erro interno na permuta do filho 1 com partida 2"))
                p1_filho.save()
            if p2_filho:
                if p2_filho.proxima_partida_vencedor == partida2:
                    p2_filho.proxima_partida_vencedor = partida1
                elif p2_filho.proxima_partida_perdedor == partida2:
                    p2_filho.proxima_partida_perdedor = partida1
                else:
                    raise(_("Erro interno na permuta do filho 1 com partida 2"))
                p2_filho.save()

        if indice == 1:
            partida1.numero_time1 = p2_numero_time
            partida2.numero_time1 = p1_numero_time
        elif indice == 2:
            partida1.numero_time2 = p2_numero_time
            partida2.numero_time2 = p1_numero_time

        partida1.save()
        partida2.save()
    
    def rotulo_partida_filho(self, indice, so_numero = False):
        if indice <= 0 or indice > 2:
            return None # TODO raise exception
        
        p = None
        try:
            p = Partida.objects.get(torneio=self.torneio, proxima_partida_vencedor=self, proxima_partida_vencedor_indice = indice)
            if so_numero or not p.finalizada():
                return _("Vencedor Partida #{numero}").format(numero=p.numero)
        except: #DoesNotExist
            try:
                p = Partida.objects.get(torneio=self.torneio, proxima_partida_perdedor=self, proxima_partida_perdedor_indice = indice)
                if so_numero or not p.finalizada():
                    return _("Perdedor Partida #{numero}").format(numero=p.numero)
            except:
                return None
        if p:
            return p.time_vencedor().nome
        else:
            return None
    
    def rotulo_partida_filho1(self, so_numero = False):
        return self.rotulo_partida_filho(1, so_numero)
    
    def rotulo_partida_filho2(self, so_numero = False):
        return self.rotulo_partida_filho(2, so_numero)
    
    def rotulo_time(self, indice, so_numero = False):
        if indice <= 0 or indice > 2:
            return None # TODO raise exception
        
        if not so_numero:
            if indice == 1:
                time = self.time1()
                if time:
                    return time.nome
            elif indice == 2:
                time = self.time2()
                if time:
                    return time.nome
    
        numero = self.numero_time1 if indice == 1 else self.numero_time2

        if numero > 0:
            if self.torneio.individual:
                return _("Participante #{numero}").format(numero=numero)
            elif self.torneio.em_duplas:
                return _("Dupla #{numero}").format(numero=numero)
            else:
                return _("Time #{numero}").format(numero=numero)
        else:
            return self.rotulo_partida_filho(indice, so_numero)
    
    def rotulo_time1(self, so_numero = False):
        return self.rotulo_time(1, so_numero)
    
    def rotulo_time2(self, so_numero = False):
        return self.rotulo_time(2, so_numero)

    def grau(self):
        if not self.proxima_partida_vencedor:
            if not self.proxima_partida_perdedor:
                return 0
            else:
                return 1 + self.proxima_partida_perdedor.grau()
        return 1 + self.proxima_partida_vencedor.grau()
        
    def possivel_registrar_pontuacao(self):
        return not self.finalizada() and not self.torneio.times_sem_numero() and self.com_times_definidos()
    
    def pontuacoes(self):
        return Pontuacao.objects.filter(partida=self).order_by('set')

class Pontuacao(models.Model):
    partida = models.ForeignKey(Partida, on_delete=models.CASCADE, verbose_name=_('Partida'))
    set = models.PositiveSmallIntegerField(default=0, validators=[validators.MinValueValidator(0)], verbose_name=_('Set'))
    pontos_time1 = models.SmallIntegerField(default=0, verbose_name=_('Pontuação do time 1'))
    pontos_time2 = models.SmallIntegerField(default=0, verbose_name=_('Pontuação do time 2'))
    class Meta:
        verbose_name_plural = _("Pontuações")

class ChaveInscricao(models.Model):
    time = models.ForeignKey(Time, on_delete=models.CASCADE, verbose_name=_('time'))
    codigo = models.CharField(_('código'), max_length=128, unique=True)
    ativa = models.BooleanField(_('ativa'), default=True, null=False, blank=False )
    class Meta:
        verbose_name_plural = _("ChavesInscrição")

class AcessoChaveInscricao(models.Model):
    chave_inscricao = models.ForeignKey(ChaveInscricao, on_delete=models.CASCADE, related_name='acessos', verbose_name=_('Chave de inscrição'))
    #codigo = models.CharField(max_length=128)
    horario = models.DateTimeField(_("horário de acesso"))
    ip = models.GenericIPAddressField()
    class Meta:
        verbose_name_plural = _("AcessosChaveInscrição")
