# -*- mode: python -*-
# -*- coding: utf-8 -*-

##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2024 Emmanuel Viennet.  All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#   Emmanuel Viennet      emmanuel.viennet@viennet.net
#
##############################################################################

"""Génération calendrier (ancienne présentation)
"""

import calendar
import datetime
import html
import time

from app.scodoc.sco_exceptions import ScoValueError, ScoInvalidDateError
from app.scodoc import sco_preferences
import app.scodoc.sco_utils as scu


def is_work_saturday():
    "Vrai si le samedi est travaillé"
    return int(sco_preferences.get_preference("work_saturday"))


def MonthNbDays(month, year):
    "returns nb of days in month"
    if month > 7:
        month = month + 1
    if month % 2:
        return 31
    elif month == 2:
        if calendar.isleap(year):
            return 29
        else:
            return 28
    else:
        return 30


class ddmmyyyy(object):
    """immutable dates"""

    def __init__(self, date=None, fmt="ddmmyyyy", work_saturday=False):
        self.work_saturday = work_saturday
        if date is None:
            return
        try:
            if fmt == "ddmmyyyy":
                self.day, self.month, self.year = date.split("/")
            elif fmt == "iso":
                self.year, self.month, self.day = date.split("-")
            else:
                raise ValueError("invalid format spec. (%s)" % fmt)
            self.year = int(self.year)
            self.month = int(self.month)
            self.day = int(self.day)
        except ValueError:
            raise ScoValueError("date invalide: %s" % date)
        # accept years YYYY or YY, uses 1970 as pivot
        if self.year < 1970:
            if self.year > 100:
                raise ScoInvalidDateError("Année invalide: %s" % self.year)
            if self.year < 70:
                self.year = self.year + 2000
            else:
                self.year = self.year + 1900
        if self.month < 1 or self.month > 12:
            raise ScoInvalidDateError("Mois invalide: %s" % self.month)

        if self.day < 1 or self.day > MonthNbDays(self.month, self.year):
            raise ScoInvalidDateError("Jour invalide: %s" % self.day)

        # weekday in 0-6, where 0 is monday
        self.weekday = calendar.weekday(self.year, self.month, self.day)

        self.time = time.mktime((self.year, self.month, self.day, 0, 0, 0, 0, 0, 0))

    def iswork(self):
        "returns true if workable day"
        if self.work_saturday:
            nbdays = 6
        else:
            nbdays = 5
        if (
            self.weekday >= 0 and self.weekday < nbdays
        ):  # monday-friday or monday-saturday
            return 1
        else:
            return 0

    def __repr__(self):
        return "'%02d/%02d/%04d'" % (self.day, self.month, self.year)

    def __str__(self):
        return "%02d/%02d/%04d" % (self.day, self.month, self.year)

    def ISO(self):
        "iso8601 representation of the date"
        return "%04d-%02d-%02d" % (self.year, self.month, self.day)

    def next_day(self, days=1):
        "date for the next day (nota: may be a non workable day)"
        day = self.day + days
        month = self.month
        year = self.year

        while day > MonthNbDays(month, year):
            day = day - MonthNbDays(month, year)
            month = month + 1
            if month > 12:
                month = 1
                year = year + 1
        return self.__class__(
            "%02d/%02d/%04d" % (day, month, year), work_saturday=self.work_saturday
        )

    def prev(self, days=1):
        "date for previous day"
        day = self.day - days
        month = self.month
        year = self.year
        while day <= 0:
            month = month - 1
            if month == 0:
                month = 12
                year = year - 1
            day = day + MonthNbDays(month, year)

        return self.__class__(
            "%02d/%02d/%04d" % (day, month, year), work_saturday=self.work_saturday
        )

    def next_monday(self):
        "date of next monday"
        return self.next_day((7 - self.weekday) % 7)

    def prev_monday(self):
        "date of last monday, but on sunday, pick next monday"
        if self.weekday == 6:
            return self.next_monday()
        else:
            return self.prev(self.weekday)

    def __cmp__(self, other):  # #py3 TODO à supprimer
        """return a negative integer if self < other,
        zero if self == other, a positive integer if self > other"""
        return int(self.time - other.time)

    def __eq__(self, other):
        return self.time == other.time

    def __ne__(self, other):
        return self.time != other.time

    def __lt__(self, other):
        return self.time < other.time

    def __le__(self, other):
        return self.time <= other.time

    def __gt__(self, other):
        return self.time > other.time

    def __ge__(self, other):
        return self.time >= other.time

    def __hash__(self):
        "we are immutable !"
        return hash(self.time) ^ hash(str(self))


# d = ddmmyyyy( '21/12/99' )
def DateRangeISO(date_beg, date_end, workable=1):
    """returns list of dates in [date_beg,date_end]
    workable = 1 => keeps only workable days"""
    if not date_beg:
        raise ScoValueError("pas de date spécifiée !")
    if not date_end:
        date_end = date_beg
    r = []
    work_saturday = is_work_saturday()
    try:
        cur = ddmmyyyy(date_beg, work_saturday=work_saturday)
        end = ddmmyyyy(date_end, work_saturday=work_saturday)
    except (AttributeError, ValueError) as e:
        raise ScoValueError("date invalide !") from e
    while cur <= end:
        if (not workable) or cur.iswork():
            r.append(cur)
        cur = cur.next_day()

    return [x.ISO() for x in r]


def day_names():
    """Returns week day names.
    If work_saturday property is set, include saturday
    """
    if is_work_saturday():
        return scu.DAY_NAMES[:-1]
    else:
        return scu.DAY_NAMES[:-2]


def next_iso_day(date):
    "return date after date"
    d = ddmmyyyy(date, fmt="iso", work_saturday=is_work_saturday())
    return d.next_day().ISO()


def YearTable(
    year,
    events=[],
    firstmonth=9,
    lastmonth=7,
    halfday=0,
    dayattributes="",
    pad_width=8,
):
    """Generate a calendar table
    events = list of tuples (date, text, color, href [,halfday])
             where date is a string in ISO format (yyyy-mm-dd)
             halfday is boolean (true: morning, false: afternoon)
    text  = text to put in calendar (must be short, 1-5 cars) (optional)
    if halfday, generate 2 cells per day (morning, afternoon)
    """
    T = [
        '<table id="maincalendar" class="maincalendar" border="3" cellpadding="1" cellspacing="1" frame="box">'
    ]
    T.append("<tr>")
    month = firstmonth
    while 1:
        T.append('<td valign="top">')
        T.append(MonthTableHead(month))
        T.append(
            MonthTableBody(
                month,
                year,
                events,
                halfday,
                dayattributes,
                is_work_saturday(),
                pad_width=pad_width,
            )
        )
        T.append(MonthTableTail())
        T.append("</td>")
        if month == lastmonth:
            break
        month = month + 1
        if month > 12:
            month = 1
            year = year + 1
    T.append("</table>")
    return "\n".join(T)


# ------ HTML Calendar functions (see YearTable function)

# MONTH/DAY NAMES:

MONTHNAMES = (
    "Janvier",
    "Février",
    "Mars",
    "Avril",
    "Mai",
    "Juin",
    "Juillet",
    "Aout",
    "Septembre",
    "Octobre",
    "Novembre",
    "Décembre",
)

MONTHNAMES_ABREV = (
    "Jan.",
    "Fév.",
    "Mars",
    "Avr.",
    "Mai&nbsp;",
    "Juin",
    "Juil",
    "Aout",
    "Sept",
    "Oct.",
    "Nov.",
    "Déc.",
)

DAYNAMES = ("Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche")

DAYNAMES_ABREV = ("L", "M", "M", "J", "V", "S", "D")

# COLORS:

WHITE = "#FFFFFF"
GRAY1 = "#EEEEEE"
GREEN3 = "#99CC99"
WEEKDAYCOLOR = GRAY1
WEEKENDCOLOR = GREEN3


def MonthTableHead(month):
    color = WHITE
    return """<table class="monthcalendar" border="0" cellpadding="0" cellspacing="0" frame="box">
     <tr bgcolor="%s"><td class="calcol" colspan="2" align="center">%s</td></tr>\n""" % (
        color,
        MONTHNAMES_ABREV[month - 1],
    )


def MonthTableTail():
    return "</table>\n"


def MonthTableBody(
    month, year, events=[], halfday=0, trattributes="", work_saturday=False, pad_width=8
):
    firstday, nbdays = calendar.monthrange(year, month)
    localtime = time.localtime()
    current_weeknum = time.strftime("%U", localtime)
    current_year = localtime[0]
    T = []
    # cherche date du lundi de la 1ere semaine de ce mois
    monday = ddmmyyyy("1/%d/%d" % (month, year))
    while monday.weekday != 0:
        monday = monday.prev()

    if work_saturday:
        weekend = ("D",)
    else:
        weekend = ("S", "D")

    if not halfday:
        for d in range(1, nbdays + 1):
            weeknum = time.strftime(
                "%U", time.strptime("%d/%d/%d" % (d, month, year), "%d/%m/%Y")
            )
            day = DAYNAMES_ABREV[(firstday + d - 1) % 7]
            if day in weekend:
                bgcolor = WEEKENDCOLOR
                weekclass = "wkend"
                attrs = ""
            else:
                bgcolor = WEEKDAYCOLOR
                weekclass = "wk" + str(monday).replace("/", "_")
                attrs = trattributes
            color = None
            legend = ""
            href = ""
            descr = ""
            # event this day ?
            # each event is a tuple (date, text, color, href)
            #  where date is a string in ISO format (yyyy-mm-dd)
            for ev in events:
                ev_year = int(ev[0][:4])
                ev_month = int(ev[0][5:7])
                ev_day = int(ev[0][8:10])
                if year == ev_year and month == ev_month and ev_day == d:
                    if ev[1]:
                        legend = ev[1]
                    if ev[2]:
                        color = ev[2]
                    if ev[3]:
                        href = ev[3]
                    if len(ev) > 4 and ev[4]:
                        descr = ev[4]
            #
            cc = []
            if color is not None:
                cc.append('<td bgcolor="%s" class="calcell">' % color)
            else:
                cc.append('<td class="calcell">')

            if href:
                href = 'href="%s"' % href
            if descr:
                descr = 'title="%s"' % html.escape(descr, quote=True)
            if href or descr:
                cc.append("<a %s %s>" % (href, descr))

            if legend or d == 1:
                if pad_width is not None:
                    n = pad_width - len(legend)  # pad to 8 cars
                    if n > 0:
                        legend = (
                            "&nbsp;" * (n // 2) + legend + "&nbsp;" * ((n + 1) // 2)
                        )
            else:
                legend = "&nbsp;"  # empty cell
            cc.append(legend)
            if href or descr:
                cc.append("</a>")
            cc.append("</td>")
            cell = "".join(cc)
            if day == "D":
                monday = monday.next_day(7)
            if (
                weeknum == current_weeknum
                and current_year == year
                and weekclass != "wkend"
            ):
                weekclass += " currentweek"
            T.append(
                '<tr bgcolor="%s" class="%s" %s><td class="calday">%d%s</td>%s</tr>'
                % (bgcolor, weekclass, attrs, d, day, cell)
            )
    else:
        # Calendar with 2 cells / day
        for d in range(1, nbdays + 1):
            weeknum = time.strftime(
                "%U", time.strptime("%d/%d/%d" % (d, month, year), "%d/%m/%Y")
            )
            day = DAYNAMES_ABREV[(firstday + d - 1) % 7]
            if day in weekend:
                bgcolor = WEEKENDCOLOR
                weekclass = "wkend"
                attrs = ""
            else:
                bgcolor = WEEKDAYCOLOR
                weekclass = "wk" + str(monday).replace("/", "_")
                attrs = trattributes
            if (
                weeknum == current_weeknum
                and current_year == year
                and weekclass != "wkend"
            ):
                weeknum += " currentweek"

            if day == "D":
                monday = monday.next_day(7)
            T.append(
                '<tr bgcolor="%s" class="wk%s" %s><td class="calday">%d%s</td>'
                % (bgcolor, weekclass, attrs, d, day)
            )
            cc = []
            for morning in (True, False):
                color = None
                legend = ""
                href = ""
                descr = ""
                for ev in events:
                    ev_year = int(ev[0][:4])
                    ev_month = int(ev[0][5:7])
                    ev_day = int(ev[0][8:10])
                    if ev[4] is not None:
                        ev_half = int(ev[4])
                    else:
                        ev_half = 0
                    if (
                        year == ev_year
                        and month == ev_month
                        and ev_day == d
                        and morning == ev_half
                    ):
                        if ev[1]:
                            legend = ev[1]
                        if ev[2]:
                            color = ev[2]
                        if ev[3]:
                            href = ev[3]
                        if len(ev) > 5 and ev[5]:
                            descr = ev[5]
                #
                if color is not None:
                    cc.append('<td bgcolor="%s" class="calcell">' % (color))
                else:
                    cc.append('<td class="calcell">')
                if href:
                    href = 'href="%s"' % href
                if descr:
                    descr = 'title="%s"' % html.escape(descr, quote=True)
                if href or descr:
                    cc.append("<a %s %s>" % (href, descr))
                if legend or d == 1:
                    n = 3 - len(legend)  # pad to 3 cars
                    if n > 0:
                        legend = (
                            "&nbsp;" * (n // 2) + legend + "&nbsp;" * ((n + 1) // 2)
                        )
                else:
                    legend = "&nbsp;&nbsp;&nbsp;"  # empty cell
                cc.append(legend)
                if href or descr:
                    cc.append("</a>")
                cc.append("</td>\n")
            T.append("".join(cc) + "</tr>")
    return "\n".join(T)
