#    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 functools import reduce

from django.db import models
from django.db.models import 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 Source(models.Model):
    url = models.URLField(max_length=255, verbose_name="URL", unique=True)
    last_update_date = models.DateTimeField(verbose_name="dernière mise à jour Celcat",
                                            null=True, blank=True)

    def __str__(self):
        return self.url

    @property
    def formatted_timetables(self):
        return ", ".join([str(timetable) for timetable in self.timetables.iterator()])


    class Meta:
        verbose_name = "source d’emploi du temps"
        verbose_name_plural = "sources d’emploi du temps"


class TimetableManager(Manager):
    def get_queryset(self):
        return super(Manager, self).get_queryset().select_related("year")


class Timetable(SlugModel):
    objects = TimetableManager()

    year = models.ForeignKey(Year, on_delete=models.CASCADE,
                             verbose_name="année")
    name = models.CharField(max_length=64, verbose_name="nom")
    slug = models.SlugField(max_length=64, default="")
    source = models.ForeignKey(Source, on_delete=models.CASCADE,
                               verbose_name="source", related_name="timetables")

    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_parents(self, group):
        groups_criteria = Q(subgroup="")

        if len(group.subgroup) != 0:
            groups_criteria |= reduce(lambda x, y: x | y,
                                      [Q(subgroup=group.subgroup[:i])
                                       for i in range(1, len(group.subgroup) + 1)])

        return self.get_queryset().filter(groups_criteria,
                                          Q(semester=None) | Q(semester=group.semester),
                                          mention=group.mention,
                                          source=group.source)


class Group(SlugModel):
    objects = GroupManager()

    name = models.CharField(max_length=255, verbose_name="nom")
    celcat_name = models.CharField(max_length=255,
                                   verbose_name="nom dans Celcat")
    source = models.ForeignKey(Source, on_delete=models.CASCADE,
                               verbose_name="source d’emploi du temps")

    mention = models.CharField(max_length=128)
    semester = models.IntegerField(verbose_name="semestre", null=True)
    subgroup = models.CharField(max_length=16, verbose_name="sous-groupe", default="")

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

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

    def corresponds_to(self, mention, semester, subgroup):
        subgroup_corresponds = True
        if self.subgroup is not None and subgroup is not None:
            subgroup_corresponds = self.subgroup.startswith(subgroup)

        return (self.mention.startswith(mention) or \
                mention.startswith(self.mention)) and \
                (self.semester == semester or semester is None) and \
                subgroup_corresponds

    @property
    def group_info(self):
        return self.mention, self.semester, self.subgroup

    def __str__(self):
        return self.name

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

        self.mention, self.semester, self.subgroup = parse_group(self.name)
        if self.subgroup is None:
            self.subgroup = ""

        super(Group, self).save()


    class Meta:
        index_together = ("mention", "semester", "subgroup",)
        unique_together = (("name", "source",),
                           ("celcat_name", "source",),
                           ("slug", "source",),)

        verbose_name = "groupe"
        verbose_name_plural = "groupes"


class Room(SlugModel):
    name = models.CharField(max_length=255, unique=True, verbose_name="nom")
    slug = models.SlugField(max_length=64, default="", unique=True)

    def __str__(self):
        return self.name

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


class CourseManager(Manager):
    def get_courses(self, obj, **criteria):
        qs = self.get_queryset()
        if isinstance(obj, Group):
            qs = qs.filter(groups__in=Group.objects.get_parents(obj), **criteria) \
                   .prefetch_related("rooms")
        elif isinstance(obj, Room):
            qs = qs.filter(rooms__in=(obj,), **criteria)

        return qs.order_by("begin")

    def get_weeks(self, **criteria):
        return self.get_queryset() \
                   .filter(**criteria) \
                   .order_by("groups__name", "year", "week") \
                   .annotate(year=ExtractYear("begin"),
                             week=ExtractWeek("begin")) \
                   .values("groups__mention", "groups__semester",
                           "groups__subgroup", "year", "week")


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

    name = models.CharField(max_length=255, verbose_name="nom", default="Sans nom")
    type_ = models.CharField(name="type", max_length=255,
                             verbose_name="type de cours", null=True)
    source = models.ForeignKey(Source, 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"