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

##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2023 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@gmail.com
#
##############################################################################

"""Génération des bulletins de note: super-classe pour les générateurs (HTML et PDF)

class BulletinGenerator:
 description
 supported_formats = [ 'pdf', 'html' ]
 .bul_title_pdf()
 .bul_table(format)
 .bul_part_below(format)
 .bul_signatures_pdf()

 .__init__ et .generate(format) methodes appelees par le client (sco_bulletin)

La préférence 'bul_class_name' donne le nom de la classe generateur.
La préférence 'bul_pdf_class_name' est obsolete (inutilisée).


"""
import collections
import io
import time
import traceback


import reportlab
from reportlab.platypus import (
    SimpleDocTemplate,
    DocIf,
    Paragraph,
    Spacer,
    Frame,
    PageBreak,
)
from reportlab.platypus import Table, TableStyle, Image, KeepInFrame

from flask import request
from flask_login import current_user

from app.scodoc import sco_utils as scu
from app.scodoc.sco_exceptions import NoteProcessError
from app import log
from app.scodoc import sco_formsemestre
from app.scodoc import sco_pdf
from app.scodoc.sco_pdf import PDFLOCK
import sco_version


class BulletinGenerator:
    "Virtual superclass for PDF bulletin generators" ""
    # Here some helper methods
    # see sco_bulletins_standard.BulletinGeneratorStandard subclass for real methods
    supported_formats = []  # should list supported formats, eg [ 'html', 'pdf' ]
    description = "superclass for bulletins"  # description for user interface
    list_in_menu = True  # la classe doit-elle est montrée dans le menu de config ?
    scale_table_in_page = True  # rescale la table sur 1 page
    multi_pages = False

    def __init__(
        self,
        infos,
        authuser=None,
        version="long",
        filigranne=None,
        server_name=None,
        with_img_signatures_pdf: bool = True,
    ):
        from app.scodoc import sco_preferences

        if not version in scu.BULLETINS_VERSIONS:
            raise ValueError("invalid version code !")
        self.infos = infos
        self.authuser = authuser  # nécessaire pour version HTML qui contient liens dépendant de l'utilisateur
        self.version = version
        self.filigranne = filigranne
        self.server_name = server_name
        self.with_img_signatures_pdf = with_img_signatures_pdf
        # Store preferences for convenience:
        formsemestre_id = self.infos["formsemestre_id"]
        self.preferences = sco_preferences.SemPreferences(formsemestre_id)
        self.diagnostic = None  # error message if any problem
        # Common PDF styles:
        #  - Pour tous les champs du bulletin sauf les cellules de table:
        self.FieldStyle = reportlab.lib.styles.ParagraphStyle({})
        self.FieldStyle.fontName = self.preferences["SCOLAR_FONT_BUL_FIELDS"]
        self.FieldStyle.fontSize = self.preferences["SCOLAR_FONT_SIZE"]
        self.FieldStyle.firstLineIndent = 0
        #  - Pour les cellules de table:
        self.CellStyle = reportlab.lib.styles.ParagraphStyle({})
        self.CellStyle.fontSize = self.preferences["SCOLAR_FONT_SIZE"]
        self.CellStyle.fontName = self.preferences["SCOLAR_FONT"]
        self.CellStyle.leading = (
            1.0 * self.preferences["SCOLAR_FONT_SIZE"]
        )  # vertical space
        # Marges du document PDF
        self.margins = (
            self.preferences["left_margin"],
            self.preferences["top_margin"],
            self.preferences["right_margin"],
            self.preferences["bottom_margin"],
        )

    def get_filename(self):
        """Build a filename to be proposed to the web client"""
        sem = sco_formsemestre.get_formsemestre(self.infos["formsemestre_id"])
        return scu.bul_filename_old(sem, self.infos["etud"], "pdf")

    def generate(self, format="", stand_alone=True):
        """Return bulletin in specified format"""
        if not format in self.supported_formats:
            raise ValueError("unsupported bulletin format (%s)" % format)
        try:
            PDFLOCK.acquire()  # this lock is necessary since reportlab is not re-entrant
            if format == "html":
                return self.generate_html()
            elif format == "pdf":
                return self.generate_pdf(stand_alone=stand_alone)
            else:
                raise ValueError("invalid bulletin format (%s)" % format)
        finally:
            PDFLOCK.release()

    def generate_html(self):
        """Return bulletin as an HTML string"""
        H = ['<div class="notes_bulletin">']
        # table des notes:
        H.append(self.bul_table(format="html"))  # pylint: disable=no-member
        # infos sous la table:
        H.append(self.bul_part_below(format="html"))  # pylint: disable=no-member
        H.append("</div>")
        return "\n".join(H)

    def generate_pdf(self, stand_alone=True):
        """Build PDF bulletin from distinct parts
        Si stand_alone, génère un doc PDF complet et renvoie une string
        Sinon, renvoie juste une liste d'objets PLATYPUS pour intégration
        dans un autre document.
        """
        from app.scodoc import sco_preferences

        formsemestre_id = self.infos["formsemestre_id"]
        marque_debut_bulletin = sco_pdf.DebutBulletin(
            self.infos["etud"]["nomprenom"],
            filigranne=self.infos["filigranne"],
            footer_content=f"""ScoDoc - Bulletin de {self.infos["etud"]["nomprenom"]} - {time.strftime("%d/%m/%Y %H:%M")}""",
        )
        story = []
        # partie haute du bulletin
        story += self.bul_title_pdf()  # pylint: disable=no-member
        index_obj_debut = len(story)

        # table des notes
        story += self.bul_table(format="pdf")  # pylint: disable=no-member
        # infos sous la table
        story += self.bul_part_below(format="pdf")  # pylint: disable=no-member
        # signatures
        story += self.bul_signatures_pdf()  # pylint: disable=no-member
        if self.scale_table_in_page:
            # Réduit sur une page
            story = [marque_debut_bulletin, KeepInFrame(0, 0, story, mode="shrink")]
        else:
            # Insere notre marqueur qui permet de générer les bookmarks et filigrannes:
            story.insert(index_obj_debut, marque_debut_bulletin)
        #
        # objects.append(sco_pdf.FinBulletin())
        if not stand_alone:
            if self.multi_pages:
                # Bulletins sur plusieurs page, force début suivant sur page impaire
                story.append(
                    DocIf("doc.page%2 == 1", [PageBreak(), PageBreak()], [PageBreak()])
                )
            else:
                story.append(PageBreak())  # insert page break at end

            return story
        else:
            # Generation du document PDF
            sem = sco_formsemestre.get_formsemestre(formsemestre_id)
            report = io.BytesIO()  # in-memory document, no disk file
            document = sco_pdf.BaseDocTemplate(report)
            document.addPageTemplates(
                sco_pdf.ScoDocPageTemplate(
                    document,
                    author="%s %s (E. Viennet) [%s]"
                    % (sco_version.SCONAME, sco_version.SCOVERSION, self.description),
                    title="Bulletin %s de %s"
                    % (sem["titremois"], self.infos["etud"]["nomprenom"]),
                    subject="Bulletin de note",
                    margins=self.margins,
                    server_name=self.server_name,
                    filigranne=self.filigranne,
                    preferences=sco_preferences.SemPreferences(formsemestre_id),
                )
            )
            document.build(story)
            data = report.getvalue()
        return data

    def buildTableObject(self, P, pdfTableStyle, colWidths):
        """Utility used by some old-style generators.
        Build a platypus Table instance from a nested list of cells, style and widths.
        P: table, as a list of lists
        PdfTableStyle: commandes de style pour la table (reportlab)
        """
        try:
            # put each table cell in a Paragraph
            Pt = [
                [Paragraph(sco_pdf.SU(x), self.CellStyle) for x in line] for line in P
            ]
        except:
            # enquête sur exception intermittente...
            log("*** bug in PDF buildTableObject:")
            log("P=%s" % P)
            # compris: reportlab is not thread safe !
            #   see http://two.pairlist.net/pipermail/reportlab-users/2006-June/005037.html
            # (donc maintenant protégé dans ScoDoc par un Lock global)
            self.diagnostic = "erreur lors de la génération du PDF<br>"
            self.diagnostic += "<pre>" + traceback.format_exc() + "</pre>"
            return []
        return Table(Pt, colWidths=colWidths, style=pdfTableStyle)


# ---------------------------------------------------------------------------
def make_formsemestre_bulletinetud(
    infos,
    version=None,  # short, long, selectedevals
    format="pdf",  # html, pdf
    stand_alone=True,
    with_img_signatures_pdf: bool = True,
):
    """Bulletin de notes

    Appelle une fonction générant le bulletin au format spécifié à partir des informations infos,
    selon les préférences du semestre.

    """
    from app.scodoc import sco_preferences

    version = version or "long"
    if not version in scu.BULLETINS_VERSIONS:
        raise ValueError("invalid version code !")

    formsemestre_id = infos["formsemestre_id"]
    bul_class_name = sco_preferences.get_preference("bul_class_name", formsemestre_id)

    gen_class = None
    for bul_class_name in (
        sco_preferences.get_preference("bul_class_name", formsemestre_id),
        # si pas trouvé (modifs locales bizarres ,), ré-essaye avec la valeur par défaut
        bulletin_default_class_name(),
    ):
        if infos.get("type") == "BUT" and format.startswith("pdf"):
            gen_class = bulletin_get_class(bul_class_name + "BUT")
        if gen_class is None:
            gen_class = bulletin_get_class(bul_class_name)

    if gen_class is None:
        raise ValueError(f"Type de bulletin PDF invalide (paramètre: {bul_class_name})")

    try:
        PDFLOCK.acquire()
        bul_generator = gen_class(
            infos,
            authuser=current_user,
            version=version,
            filigranne=infos["filigranne"],
            server_name=request.url_root,
            with_img_signatures_pdf=with_img_signatures_pdf,
        )
        if format not in bul_generator.supported_formats:
            # use standard generator
            log(
                "Bulletin format %s not supported by %s, using %s"
                % (format, bul_class_name, bulletin_default_class_name())
            )
            bul_class_name = bulletin_default_class_name()
            gen_class = bulletin_get_class(bul_class_name)
            bul_generator = gen_class(
                infos,
                authuser=current_user,
                version=version,
                filigranne=infos["filigranne"],
                server_name=request.url_root,
                with_img_signatures_pdf=with_img_signatures_pdf,
            )

        data = bul_generator.generate(format=format, stand_alone=stand_alone)
    finally:
        PDFLOCK.release()

    if bul_generator.diagnostic:
        log("bul_error: %s" % bul_generator.diagnostic)
        raise NoteProcessError(bul_generator.diagnostic)

    filename = bul_generator.get_filename()

    return data, filename


####

# Liste des types des classes de générateurs de bulletins PDF:
BULLETIN_CLASSES = collections.OrderedDict()


def register_bulletin_class(klass):
    BULLETIN_CLASSES[klass.__name__] = klass


def bulletin_class_descriptions():
    return [
        BULLETIN_CLASSES[class_name].description
        for class_name in BULLETIN_CLASSES
        if BULLETIN_CLASSES[class_name].list_in_menu
    ]


def bulletin_class_names() -> list[str]:
    "Liste les noms des classes de bulletins à présenter à l'utilisateur"
    return [
        class_name
        for class_name in BULLETIN_CLASSES
        if BULLETIN_CLASSES[class_name].list_in_menu
    ]


def bulletin_default_class_name():
    return bulletin_class_names()[0]


def bulletin_get_class(class_name: str) -> BulletinGenerator:
    """La class de génération de bulletin de ce nom,
    ou None si pas trouvée
    """
    return BULLETIN_CLASSES.get(class_name)


def bulletin_get_class_name_displayed(formsemestre_id):
    """Le nom du générateur utilisé, en clair"""
    from app.scodoc import sco_preferences

    bul_class_name = sco_preferences.get_preference("bul_class_name", formsemestre_id)
    gen_class = bulletin_get_class(bul_class_name)
    if gen_class is None:
        return "invalide ! (voir paramètres)"
    return gen_class.description