#    Copyright (C) 2017  Alban Gruin
#
#    celcatsanitizer 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.
#
#    celcatsanitizer 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 celcatsanitizer.  If not, see <http://www.gnu.org/licenses/>.

from django.db import models
from django.db.models import Count, Manager, Q
from django.db.models.functions import ExtractWeek, ExtractYear
from django.utils import timezone
from django.utils.text import slugify

from .utils import parse_group


class SlugModel(models.Model):
    def save(self):
        if not self.slug:
            self.slug = slugify(self.name)

        super(SlugModel, self).save()


    class Meta:
        abstract = True


class Year(SlugModel):
    name = models.CharField(max_length=16, verbose_name="année")
    slug = models.SlugField(max_length=16, unique=True, default="")

    def __str__(self):
        return self.name


    class Meta:
        verbose_name = "année"
        verbose_name_plural = "années"


class Timetable(SlugModel):
    year = models.ForeignKey(Year, on_delete=models.CASCADE,
                             verbose_name="année")
    name = models.CharField(max_length=64, verbose_name="nom")
    url = models.URLField(max_length=255, verbose_name="URL")
    slug = models.SlugField(max_length=64, default="")

    last_update_date = models.DateTimeField(verbose_name="dernière mise à jour Celcat", null=True)

    def __str__(self):
        return self.year.name + " " + self.name


    class Meta:
        unique_together = (("year", "name"), ("year", "slug"),)
        verbose_name = "emploi du temps"
        verbose_name_plural = "emplois du temps"


class GroupManager(Manager):
    def get_relevant_children(self, group):
        parent_in = self.get_queryset().filter(parent=group)
        return self.get_queryset().filter(Q(parent=group) | Q(parent__in=parent_in)) \
                                  .annotate(children_count=Count("children")) \
                                  .filter(children_count=0, hidden=False) \
                                  .order_by("name")

    def get_relevant_groups(self, *args, **criteria):
        return self.get_queryset().filter(*args, **criteria) \
                                  .annotate(children_count=Count("children")) \
                                  .filter(children_count=0, hidden=False)


class Group(models.Model):
    objects = GroupManager()

    name = models.CharField(max_length=255, verbose_name="nom")
    celcat_name = models.CharField(max_length=255,
                                   verbose_name="nom dans Celcat")
    timetable = models.ForeignKey(Timetable, on_delete=models.CASCADE,
                                  verbose_name="emploi du temps")

    mention = models.CharField(max_length=128)
    subgroup = models.CharField(max_length=1, verbose_name="sous-groupe",
                                null=True)
    td = models.IntegerField(verbose_name="groupe de TD", null=True)
    tp = models.IntegerField(verbose_name="groupe de TP", null=True)
    parent = models.ForeignKey("self", verbose_name="groupe parent", null=True,
                               default=None, related_name="children")

    slug = models.SlugField(max_length=64, default="")

    hidden = models.BooleanField(verbose_name="caché", default=False)

    def corresponds_to(self, timetable_id, mention, subgroup, td, tp):
        return self.timetable.id == timetable_id and \
            self.mention.startswith(mention) and \
            (self.subgroup == subgroup or self.subgroup is None) and \
            (self.td == td or self.td is None or td is None) and \
            (self.tp == tp or self.tp is None or tp is None)

    @property
    def group_info(self):
        return self.timetable.id, self.mention, self.subgroup, self.td, self.tp

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        if self.name == "":
            self.name = self.celcat_name
            self.slug = slugify(self.name)

        self.mention, self.subgroup, self.td, self.tp = parse_group(self.name)
        super(Group, self).save()


    class Meta:
        index_together = ("mention", "subgroup", "td", "tp",)
        unique_together = (("name", "timetable",),
                           ("celcat_name", "timetable",),
                           ("slug", "timetable",),)

        verbose_name = "groupe"
        verbose_name_plural = "groupes"


class Room(models.Model):
    name = models.CharField(max_length=255, unique=True, verbose_name="nom")

    def __str__(self):
        return self.name


    class Meta:
        verbose_name = "salle"
        verbose_name_plural = "salles"


class CourseManager(Manager):
    def get_courses_for_group(self, group, **criteria):
        groups_criteria = []
        if group.subgroup is not None:
            groups_criteria.append(Q(groups__subgroup__isnull=True) | \
                                   Q(groups__subgroup=group.subgroup))
        if group.td is not None:
            groups_criteria.append(Q(groups__td__isnull=True) | Q(groups__td=group.td))
        if group.tp is not None:
            groups_criteria.append(Q(groups__tp__isnull=True) | Q(groups__tp=group.tp))

        return self.get_queryset() \
                   .filter(*groups_criteria,
                           groups__mention=group.mention,
                           timetable=group.timetable, **criteria) \
                   .order_by("begin")

    def get_weeks(self, **criteria):
        return self.get_queryset() \
                   .filter(**criteria) \
                   .order_by("groups__name", "year", "week") \
                   .annotate(_=Count(("groups", "year", "week", "begin")),
                             year=ExtractYear("begin"),
                             week=ExtractWeek("begin"))


class Course(models.Model):
    objects = CourseManager()

    name = models.CharField(max_length=255, verbose_name="nom", default="Sans nom",
                            null=True) # TODO 0.11: null=False
    type_ = models.CharField(name="type", max_length=255,
                             verbose_name="type de cours", null=True)
    timetable = models.ForeignKey(Timetable, on_delete=models.CASCADE,
                                  verbose_name="emploi du temps")
    notes = models.TextField(verbose_name="remarques", blank=True, null=True)

    groups = models.ManyToManyField(Group, verbose_name="groupes")
    rooms = models.ManyToManyField(Room, verbose_name="salles")

    begin = models.DateTimeField(verbose_name="début du cours", db_index=True)
    end = models.DateTimeField(verbose_name="fin du cours")

    last_update = models.DateTimeField(verbose_name="dernière mise à jour",
                                       default=timezone.now)

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        if self.type is not None:
            self.type = self.type.replace("COURS", "cours")
            self.type = self.type.replace("REUNION", "réunion")
        if self.name is not None:
            self.name = self.name.split("(")[0].strip()

        super(Course, self).save(*args, **kwargs)


    class Meta:
        verbose_name = "cours"
        verbose_name_plural = "cours"