# -*- 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 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_by_day: dict[str, list[dict]],
    firstmonth=9,
    lastmonth=7,
    dayattributes="",
):
    # Code simplifié en 2024: utilisé seulement pour calendrier évaluations
    """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)
    """
    T = [
        """<table id="maincalendar" class="maincalendar"
            border="3" cellpadding="1" cellspacing="1" frame="box">"""
    ]
    T.append("<tr>")
    month = firstmonth
    while True:
        T.append('<td valign="top">')
        T.append(_month_table_head(month))
        T.append(
            _month_table_body(
                month,
                year,
                events_by_day,
                dayattributes,
                is_work_saturday(),
            )
        )
        T.append(
            """
            </table>
        </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 _month_table_head(month):
    color = WHITE
    return f"""<table class="monthcalendar" border="0" cellpadding="0" cellspacing="0" frame="box">
     <tr bgcolor="{color}">
        <td class="calcol" colspan="2" align="center">{MONTHNAMES_ABREV[month - 1]}</td>
     </tr>\n"""


def _month_table_body(
    month,
    year,
    events_by_day: dict[str, list[dict]],
    trattributes="",
    work_saturday=False,
) -> str:
    """
    events : [event]
    event = [ yyyy-mm-dd, legend, href, color, descr ]  XXX
    """
    firstday, nbdays = calendar.monthrange(year, month)
    localtime = time.localtime()
    current_weeknum = time.strftime("%U", localtime)
    current_year = localtime[0]
    rows = []
    # cherche date du lundi de la 1ere semaine de ce mois
    monday = ddmmyyyy(f"1/{month}/{year}")
    while monday.weekday != 0:
        monday = monday.prev()

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

    for d in range(1, nbdays + 1):
        weeknum = time.strftime(
            "%U", time.strptime("%d/%d/%d" % (d, month, year), scu.DATE_FMT)
        )
        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
        # events this day ?
        events = events_by_day.get(f"{year}-{month:02}-{d:02}", [])
        color = None
        ev_txts = []
        for ev in events:
            color = ev.get("color")
            href = ev.get("href", "")
            description = ev.get("description", "")
            if href:
                href = f'href="{href}"'
            if description:
                description = f"""title="{html.escape(description, quote=True)}" """
            if href or description:
                ev_txts.append(f"""<a {href} {description}>{ev.get("title", "")}</a>""")
            else:
                ev_txts.append(ev.get("title", "&nbsp;"))
        #
        cc = []
        if color is not None:
            cc.append(f'<td bgcolor="{color}" class="calcell">')
        else:
            cc.append('<td class="calcell">')

        cc.append(f"{', '.join(ev_txts)}</td>")
        cells = "".join(cc)
        if day == "D":
            monday = monday.next_day(7)
        if weeknum == current_weeknum and current_year == year and weekclass != "wkend":
            weekclass += " currentweek"
        rows.append(
            f"""<tr bgcolor="{bgcolor}" class="{weekclass}" {attrs}>
            <td class="calday">{d}{day}</td>{cells}</tr>"""
        )

    return "\n".join(rows)