#    Copyright (C) 2017-2018  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/>.

import datetime
import re

from bs4 import BeautifulSoup
from django.utils import timezone

from ...models import Course, Group, Room
from .abstractparser import AbstractParser


def add_time(date, time):
    ptime = datetime.datetime.strptime(time, "%H:%M")
    delta = datetime.timedelta(hours=ptime.hour, minutes=ptime.minute)
    return date + delta


class Parser(AbstractParser):
    def __get_event(self, event, event_week, today):
        """Renvoie une classe Course à partir d’un événement lu par BS4"""
        # On récupère la date de l’évènement à partir de la semaine
        # et de la semaine référencée, puis l’heure de début et de fin
        date = event_week + datetime.timedelta(int(event.day.text))
        begin = add_time(date, event.starttime.text)
        end = add_time(date, event.endtime.text)

        # On ne traite pas le cours si il commence après le moment du
        # traitement
        if today is not None and begin < today:
            return

        # Création de l’objet cours
        course = Course.objects.create(source=self.source, begin=begin,
                                       end=end)

        # On récupère les groupes concernés par les cours
        groups = [
            Group.objects.get_or_create(
                source=self.source, celcat_name=item.text
            )[0]
            for item in event.resources.group.find_all("item")
        ]
        course.groups.add(*groups)

        # On récupère le champ « remarque »
        if event.notes is not None:
            course.notes = "\n".join(event.notes.find_all(text=True))

        # On récupère le champ « nom »
        if event.resources.module is not None:
            course.name = event.resources.module.item.text
        elif event.category is not None:
            # Il est possible qu’un cours n’ait pas de nom.  Dans ce
            # cas, si le cours a un type, il devient son nom.
            course.type = event.category.text
            # Si il n’a pas de type, il obtiendra une valeur par
            # défaut définie à l’avance.

        # Récupération du type de cours
        if event.category is not None:
            course.type = event.category.text

        # Si un cours a une salle attribuée, on les insère dans la
        # base de données, et on les ajoute dans l’objet cours
        if event.resources.room is not None:
            rooms = [
                Room.objects.get_or_create(name=item.text)[0]
                for item in event.resources.room.find_all("item")
            ]
            course.rooms.add(*rooms)

        return course

    def get_events(self, today, year=None, week=None):
        """Récupère tous les cours disponibles dans l’emploi du temps Celcat.
        Le traîtement se limitera à la semaine indiquée si il y en a une."""
        for event in self.soup.find_all("event"):
            event_week = self.weeks[event.rawweeks.text]
            event_week_num = event_week.isocalendar()[1]  # Numéro de semaine

            # On passe le traitement si la semaine de l’événement ne
            # correspond pas à la semaine passée, ou qu’il ne contient
            # pas de groupe ou n’a pas de date de début ou de fin.
            if (
                (
                    event_week_num == week
                    and event_week.year == year
                    or year is None
                    or week is None
                )
                and event.resources.group is not None
                and event.starttime is not None
                and event.endtime is not None
            ):
                course = self.__get_event(event, event_week, today)

                # On renvoie le cours si il n’est pas nul
                if course is not None:
                    yield course

    def get_update_date(self):
        # Explication de la regex
        #
        # (\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+)
        # (\d+)                                 au moins un nombre
        #      /                                un slash
        #       (\d+)                           au moins un nombre
        #            /                          un slash
        #             (\d+)                     au moins un nombre
        #                  \s+                  au moins un espace
        #                     (\d+)             au moins un nombre
        #                          :            un deux-points
        #                           (\d+)       au moins un nombre
        #                                :      un deux-points
        #                                 (\d+) au moins un nombre
        datetime_regex = re.compile(r"(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+)")
        search = datetime_regex.search(self.soup.footer.text)
        if search is None:
            return None

        day, month, year, hour, minute, second = [
            int(v) for v in search.groups()
        ]
        date = datetime.datetime(year, month, day, hour, minute, second)
        return timezone.make_aware(date)

    def get_weeks(self):
        # Les semaines présentes dans l’emploi du temps sont toutes
        # stockées dans un élément span.  Il contient une chaîne de
        # caractère qui correspond à une forme d’ID, et un champ date,
        # qui correspond au lundi de cette semaine.  Un cours contient
        # un ID correspondant à une semaine, puis le nombre de jours
        # après le début de cette semaine.
        self.weeks = {}

        # Liste de toutes les semaines définies
        for span in self.soup.find_all("span"):
            # On parse la date et on la fait correspondre à l’ID
            self.weeks[span.alleventweeks.text] = timezone.make_aware(
                datetime.datetime.strptime(span["date"], "%d/%m/%Y")
            )

        return self.weeks

    def get_source(self):
        req = super(Parser, self).get_source()
        self.soup = BeautifulSoup(req.content, "html.parser")
        return self.soup