#    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/>.

from django.test import TestCase
from django.utils import timezone

from .models import Course, Group, Room, Source, Timetable, Year
from .utils import tz_now

import datetime


class CourseTestCase(TestCase):
    def setUp(self):
        dt = tz_now()

        self.year = Year(name="L2", slug="l2")
        self.year.save()

        source = Source(url="http://example.org/")
        source.save()

        self.timetable = Timetable(year=self.year, name="Test timetable 2",
                                   source=source, slug="test-timetable2")
        self.timetable.save()

        cma = Group.objects.create(celcat_name="L1 info s2 CMA", source=source)
        tda2 = Group.objects.create(celcat_name="L1 info s2 TDA2",
                                    source=source)
        self.tpa21 = Group.objects.create(celcat_name="L1 info s2 TPA21",
                                          source=source)

        cmb = Group.objects.create(celcat_name="L1 info s2 CMB", source=source)
        tdb2 = Group.objects.create(celcat_name="L1 info s2 TDB2",
                                    source=source)
        self.tpb21 = Group.objects.create(celcat_name="L1 info s2 TPB21",
                                          source=source)

        for group in (cma, tda2, self.tpa21, cmb, tdb2, self.tpb21,):
            course = Course.objects.create(
                name="{0} course".format(group.name), type="cours",
                source=source, begin=dt, end=dt)
            course.groups.add(group)

    def test_get_courses_for_group(self):
        tpa21_courses = Course.objects.get_courses(self.tpa21)
        tpb21_courses = Course.objects.get_courses(self.tpb21)

        tpa21_course_names = ["L1 info s2 CMA course",
                              "L1 info s2 TDA2 course",
                              "L1 info s2 TPA21 course"]
        tpb21_course_names = ["L1 info s2 CMB course",
                              "L1 info s2 TDB2 course",
                              "L1 info s2 TPB21 course"]

        for courses, names in ((tpa21_courses, tpa21_course_names,),
                               (tpb21_courses, tpb21_course_names,),):
            for course in courses:
                self.assertIn(course.name, names)
                names.remove(course.name)


class GroupTestCase(TestCase):
    def setUp(self):
        self.year = Year(name="L1", slug="l1")
        self.year.save()

        self.source = Source(url="http://example.org/")
        self.source.save()

        self.timetable = Timetable(year=self.year, name="Test timetable",
                                   source=self.source, slug="test-timetable")
        self.timetable.save()

        Group.objects.create(celcat_name="L1 info s2 CMA", source=self.source)
        Group.objects.create(celcat_name="L1 info s2 TDA2", source=self.source)
        Group.objects.create(celcat_name="L1 info s2 TPA21",
                             source=self.source)

        Group.objects.create(celcat_name="L1 info s2 CMB", source=self.source)
        Group.objects.create(celcat_name="L1 info s2 TDB2", source=self.source)
        Group.objects.create(celcat_name="L1 info s2 TPB21",
                             source=self.source)

        Group.objects.create(celcat_name="L1 info (toutes sections et "
                             "semestres confondus)", source=self.source)

        # Cas spéciaux de groupes sans semestre. Normalement un groupe
        # sans semestre ne possède pas de sous-groupe non plus, mais
        # certains cas font foirer la regex. Voici un exemple trouvé
        # dans la base de données de production.
        Group.objects.create(celcat_name="M1 GC (toutes sections et semestres "
                             "confondus)", source=self.source)

        # Doit appartenir au groupe au-dessus.
        Group.objects.create(celcat_name="M1 GC s2 GA111", source=self.source)

        # Cas spécial avec les parenthèses
        Group.objects.create(celcat_name="M1 CHI-TCCM (EM) (toutes sections et"
                             " semestres confondus)", source=self.source)
        Group.objects.create(celcat_name="M1 CHI-TCCM (EM) s2 TPA12",
                             source=self.source)

    def test_corresponds(self):
        cma = Group.objects.get(celcat_name="L1 info s2 CMA",
                                source=self.source)
        tda2 = Group.objects.get(celcat_name="L1 info s2 TDA2",
                                 source=self.source)
        tpa21 = Group.objects.get(celcat_name="L1 info s2 TPA21",
                                  source=self.source)

        cmb = Group.objects.get(celcat_name="L1 info s2 CMB",
                                source=self.source)
        tdb2 = Group.objects.get(celcat_name="L1 info s2 TDB2",
                                 source=self.source)
        tpb21 = Group.objects.get(celcat_name="L1 info s2 TPB21",
                                  source=self.source)

        general = Group.objects.get(celcat_name="L1 info (toutes sections et "
                                    "semestres confondus)", source=self.source)

        self.assertFalse(cma.corresponds_to(*tda2.group_info))
        self.assertFalse(cma.corresponds_to(*tpa21.group_info))
        self.assertFalse(tda2.corresponds_to(*tpa21.group_info))

        self.assertFalse(cmb.corresponds_to(*tdb2.group_info))
        self.assertFalse(cmb.corresponds_to(*tpb21.group_info))
        self.assertFalse(tdb2.corresponds_to(*tpb21.group_info))

        self.assertFalse(cmb.corresponds_to(*tda2.group_info))
        self.assertFalse(cmb.corresponds_to(*tpa21.group_info))
        self.assertFalse(tdb2.corresponds_to(*tpa21.group_info))

        self.assertTrue(tda2.corresponds_to(*cma.group_info))
        self.assertTrue(tpa21.corresponds_to(*cma.group_info))
        self.assertTrue(tpa21.corresponds_to(*tda2.group_info))

        self.assertTrue(tdb2.corresponds_to(*cmb.group_info))
        self.assertTrue(tpb21.corresponds_to(*cmb.group_info))
        self.assertTrue(tpb21.corresponds_to(*tdb2.group_info))

        self.assertFalse(tda2.corresponds_to(*cmb.group_info))
        self.assertFalse(tpa21.corresponds_to(*cmb.group_info))
        self.assertFalse(tpa21.corresponds_to(*tdb2.group_info))

        self.assertFalse(general.corresponds_to(*cma.group_info))
        self.assertFalse(general.corresponds_to(*cmb.group_info))
        self.assertFalse(general.corresponds_to(*tda2.group_info))
        self.assertFalse(general.corresponds_to(*tdb2.group_info))
        self.assertFalse(general.corresponds_to(*tpa21.group_info))
        self.assertFalse(general.corresponds_to(*tpb21.group_info))

        self.assertTrue(cma.corresponds_to(*general.group_info))
        self.assertTrue(cmb.corresponds_to(*general.group_info))
        self.assertTrue(tda2.corresponds_to(*general.group_info))
        self.assertTrue(tdb2.corresponds_to(*general.group_info))
        self.assertTrue(tpa21.corresponds_to(*general.group_info))
        self.assertTrue(tpb21.corresponds_to(*general.group_info))

    def test_corresponds_no_semester(self):
        general = Group.objects.get(celcat_name="M1 GC (toutes sections et "
                                    "semestres confondus)", source=self.source)
        ga111 = Group.objects.get(celcat_name="M1 GC s2 GA111",
                                  source=self.source)

        self.assertTrue(ga111.corresponds_to(*general.group_info))
        self.assertFalse(general.corresponds_to(*ga111.group_info))

    def test_correspond_parenthesis(self):
        general = Group.objects.get(celcat_name="M1 CHI-TCCM (EM) (toutes"
                                    " sections et semestres confondus)")
        a12 = Group.objects.get(celcat_name="M1 CHI-TCCM (EM) s2 TPA12")

        self.assertTrue(a12.corresponds_to(*general.group_info))
        self.assertFalse(general.corresponds_to(*a12.group_info))

    def test_get(self):
        cma = Group.objects.get(name="L1 info s2 CMA", source=self.source)
        tda2 = Group.objects.get(name="L1 info s2 TDA2", source=self.source)
        tpa21 = Group.objects.get(name="L1 info s2 TPA21", source=self.source)

        cmb = Group.objects.get(name="L1 info s2 CMB", source=self.source)
        tdb2 = Group.objects.get(name="L1 info s2 TDB2", source=self.source)
        tpb21 = Group.objects.get(name="L1 info s2 TPB21", source=self.source)

        general = Group.objects.get(celcat_name="L1 info (toutes sections et "
                                    "semestres confondus)", source=self.source)

        self.assertEqual(cma.celcat_name, "L1 info s2 CMA")
        self.assertEqual(tda2.celcat_name, "L1 info s2 TDA2")
        self.assertEqual(tpa21.celcat_name, "L1 info s2 TPA21")

        self.assertEqual(cmb.celcat_name, "L1 info s2 CMB")
        self.assertEqual(tdb2.celcat_name, "L1 info s2 TDB2")
        self.assertEqual(tpb21.celcat_name, "L1 info s2 TPB21")

        self.assertEqual(general.celcat_name, "L1 info (toutes sections et "
                         "semestres confondus)")

    def test_parse(self):
        cma = Group.objects.get(celcat_name="L1 info s2 CMA",
                                source=self.source)
        tda2 = Group.objects.get(celcat_name="L1 info s2 TDA2",
                                 source=self.source)
        tpa21 = Group.objects.get(celcat_name="L1 info s2 TPA21",
                                  source=self.source)

        cmb = Group.objects.get(celcat_name="L1 info s2 CMB",
                                source=self.source)
        tdb2 = Group.objects.get(celcat_name="L1 info s2 TDB2",
                                 source=self.source)
        tpb21 = Group.objects.get(celcat_name="L1 info s2 TPB21",
                                  source=self.source)

        general = Group.objects.get(celcat_name="L1 info (toutes sections et "
                                    "semestres confondus)", source=self.source)

        self.assertEqual(cma.group_info, ("L1 info", 2, "A"))
        self.assertEqual(tda2.group_info, ("L1 info", 2, "A2"))
        self.assertEqual(tpa21.group_info, ("L1 info", 2, "A21"))

        self.assertEqual(cmb.group_info, ("L1 info", 2, "B"))
        self.assertEqual(tdb2.group_info, ("L1 info", 2, "B2"))
        self.assertEqual(tpb21.group_info, ("L1 info", 2, "B21"))

        self.assertEqual(general.group_info, ("L1 info", None, ""))

    def test_parse_no_semester(self):
        general = Group.objects.get(celcat_name="M1 GC (toutes sections et "
                                    "semestres confondus)", source=self.source)
        ga111 = Group.objects.get(celcat_name="M1 GC s2 GA111",
                                  source=self.source)

        self.assertEqual(general.group_info, ("M1 GC", None, ""))
        self.assertEqual(ga111.group_info, ("M1 GC", 2, "A111"))

    def test_parse_parenthesis(self):
        general = Group.objects.get(celcat_name="M1 CHI-TCCM (EM) (toutes"
                                    " sections et semestres confondus)")
        a12 = Group.objects.get(celcat_name="M1 CHI-TCCM (EM) s2 TPA12")

        self.assertEqual(general.group_info, ("M1 CHI-TCCM (EM)", None, ""))
        self.assertEqual(a12.group_info, ("M1 CHI-TCCM (EM)", 2, "A12"))


class RoomTestCase(TestCase):
    def setUp(self):
        self.day = datetime.datetime(year=2018, month=1, day=27)

        self.year = Year.objects.create(name="L1")
        self.source = Source.objects.create(url="http://example.org/")

        # Pas besoin de créer plus de groupes que ça, ni de le rendre
        # global
        group = Group.objects.create(celcat_name="L1 info s2 CMA",
                                     source=self.source)

        self.rooms = [Room.objects.create(name="0"),
                      Room.objects.create(name="1"),
                      Room.objects.create(name="2"),
                      Room.objects.create(name="3"),
                      Room.objects.create(name="4"),
                      Room.objects.create(name="5"),
                      Room.objects.create(name="6")]

        hours = [({"begin": datetime.time(hour=14, minute=0)},),
                 ({"begin": datetime.time(hour=16, minute=0)},),
                 ({"begin": datetime.time(hour=13, minute=30)},
                  {"begin": datetime.time(hour=16, minute=0)}),
                 ({"begin": datetime.time(hour=14, minute=0), "duration": 4},),
                 ({"begin": datetime.time(hour=15, minute=30),
                   "duration": 1},),
                 ({"begin": datetime.time(hour=13, minute=0)},
                  {"begin": datetime.time(hour=17, minute=0)}),
                 ()]

        for i, room in enumerate(self.rooms):
            for rn in hours[i]:
                begin = timezone.make_aware(
                    datetime.datetime.combine(self.day, rn["begin"]))
                end = begin + datetime.timedelta(hours=rn.get("duration", 2))

                course = Course.objects.create(source=self.source,
                                               begin=begin, end=end)
                course.groups.add(group)
                course.rooms.add(room)

    def test_qsjps(self):
        begin = timezone.make_aware(datetime.datetime.combine(
            self.day, datetime.time(hour=15, minute=0)))
        end = begin + datetime.timedelta(hours=2)

        rooms = Room.objects.qsjps(begin, end)
        self.assertEqual(rooms.count(), 2)

        self.assertNotIn(self.rooms[0], rooms)
        self.assertNotIn(self.rooms[1], rooms)
        self.assertNotIn(self.rooms[2], rooms)
        self.assertNotIn(self.rooms[3], rooms)
        self.assertNotIn(self.rooms[4], rooms)
        self.assertIn(self.rooms[5], rooms)
        self.assertIn(self.rooms[6], rooms)