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

##############################################################################
#
# ScoDoc
#
# 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@viennet.net
#
##############################################################################

"""
Formulaires configuration logos

Contrib @jmp, dec 21
"""

from flask import flash, url_for, redirect, render_template
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed
from wtforms import SubmitField, FormField, validators, FieldList
from wtforms import ValidationError
from wtforms.fields.simple import StringField, HiddenField

from app.models import Departement
from app.scodoc import sco_logos, html_sco_header
from app.scodoc import sco_utils as scu

from app.scodoc.sco_config_actions import LogoInsert
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_logos import find_logo


JAVASCRIPTS = html_sco_header.BOOTSTRAP_MULTISELECT_JS + []

CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS

# class ItemForm(FlaskForm):
#     """Unused Generic class to document common behavior for classes
#     * ScoConfigurationForm
#     * DeptForm
#     * LogoForm
#     Some or all of these implements:
#         * Composite design pattern (ScoConfigurationForm and DeptForm)
#             - a FieldList(FormField(ItemForm))
#             - FieldListItem are created by browsing the model
#             - index dictionnary to provide direct access to a SubItemForm
#             - the direct access method (get_form)
#         * have some information added to be displayed
#             - information are collected from a model object
#     Common methods:
#         * build(model) (not for LogoForm who has no child)
#             for each child:
#                 * create en entry in the FieldList for each subitem found
#                 * update self.index
#                 * fill_in additional information into the form
#                 * recursively calls build for each chid
#             some spécific information may be added after standard processing
#             (typically header/footer description)
#          * preview(data)
#             check the data from a post and build a list of operations that has to be done.
#             for a two phase process:
#               * phase 1 (list all opérations)
#               * phase 2 (may be confirmation and execure)
#                   - if no op found: return to the form with a message 'Aucune modification trouvée'
#                   - only one operation found: execute and go to main page
#                   - more than 1 operation found. asked form confirmation (and execution if confirmed)
#
#     Someday we'll have time to refactor as abstract classes but Abstract FieldList makes this
#       a bit complicated
#     """

#  Terminology:
# dept_id : identifies a dept in modele (= list_logos()). None designates globals logos
# dept_key : identifies a dept in this form only (..index[dept_key], and fields 'dept_key').
#           'GLOBAL' designates globals logos (we need a string value to set up HiddenField
GLOBAL = "_"


def dept_id_to_key(dept_id):
    if dept_id is None:
        return GLOBAL
    return dept_id


def dept_key_to_id(dept_key):
    if dept_key == GLOBAL:
        return None
    return dept_key


def logo_name_validator(message=None):
    def validate_logo_name(form, field):
        name = field.data if field.data else ""
        if "." in name:
            raise ValidationError(message)
        if not scu.is_valid_filename(name):
            raise ValidationError(message)

    return validate_logo_name


class AddLogoForm(FlaskForm):
    """Formulaire permettant l'ajout d'un logo (dans un département)"""

    from app.scodoc.sco_config_actions import LogoInsert

    dept_key = HiddenField()
    name = StringField(
        label="Nom",
        validators=[
            logo_name_validator("Nom de logo invalide (alphanumérique, _)"),
            validators.Length(
                max=20, message="Un nom ne doit pas dépasser 20 caractères"
            ),
            validators.DataRequired("Nom de logo requis (alphanumériques ou '-')"),
        ],
    )
    upload = FileField(
        label="Sélectionner l'image",
        validators=[
            FileAllowed(
                scu.LOGOS_IMAGES_ALLOWED_TYPES,
                f"n'accepte que les fichiers image {', '.join(scu.LOGOS_IMAGES_ALLOWED_TYPES)}",
            ),
            validators.DataRequired("Fichier image manquant"),
        ],
    )
    do_insert = SubmitField("ajouter une image")

    def __init__(self, *args, **kwargs):
        kwargs["meta"] = {"csrf": False}
        super().__init__(*args, **kwargs)

    def id(self):
        return f"id=add_{self.dept_key.data}"

    def validate_name(self, name):
        dept_id = dept_key_to_id(self.dept_key.data)
        if dept_id == GLOBAL:
            dept_id = None
        if find_logo(logoname=name.data, dept_id=dept_id, strict=True) is not None:
            raise validators.ValidationError("Un logo de même nom existe déjà")

    def select_action(self):
        if self.data["do_insert"]:
            if self.validate():
                return LogoInsert.build_action(self.data)
        return None

    def opened(self):
        if self.do_insert.data:
            if self.name.errors:
                return "open"
            if self.upload.errors:
                return "open"
        return ""


class LogoForm(FlaskForm):
    """Embed both presentation of a logo (cf. template file configuration.j2)
    and all its data and UI action (change, delete)"""

    dept_key = HiddenField()
    logo_id = HiddenField()
    upload = FileField(
        label="Remplacer l'image",
        validators=[
            FileAllowed(
                scu.LOGOS_IMAGES_ALLOWED_TYPES,
                f"n'accepte que les fichiers image {', '.join(scu.LOGOS_IMAGES_ALLOWED_TYPES)}",
            )
        ],
    )
    do_delete = SubmitField("Supprimer")
    do_rename = SubmitField("Renommer")
    new_name = StringField(
        label="Nom",
        validators=[
            logo_name_validator("Nom de logo invalide (alphanumérique, _)"),
            validators.Length(
                max=20, message="Un nom ne doit pas dépasser 20 caractères"
            ),
            validators.DataRequired("Nom de logo requis (alphanumériques ou '-')"),
        ],
    )

    def __init__(self, *args, **kwargs):
        kwargs["meta"] = {"csrf": False}
        super().__init__(*args, **kwargs)
        logo = find_logo(
            logoname=self.logo_id.data, dept_id=dept_key_to_id(self.dept_key.data)
        )
        if logo is None:
            raise ScoValueError("logo introuvable")
        self.logo = logo.select()
        self.description = None
        self.titre = None
        self.can_delete = True
        if self.dept_key.data == GLOBAL:
            if self.logo_id.data == "header":
                self.can_delete = False
                self.description = ""
                self.titre = "Logo en-tête"
            if self.logo_id.data == "footer":
                self.can_delete = False
                self.titre = "Logo pied de page"
                self.description = ""
        else:
            if self.logo_id.data == "header":
                self.description = "Se substitue au header défini au niveau global"
                self.titre = "Logo en-tête"
            if self.logo_id.data == "footer":
                self.description = "Se substitue au footer défini au niveau global"
                self.titre = "Logo pied de page"

    def id(self):
        idstring = f"{self.dept_key.data}_{self.logo_id.data}"
        return f"id={idstring}"

    def select_action(self):
        from app.scodoc.sco_config_actions import LogoRename
        from app.scodoc.sco_config_actions import LogoUpdate
        from app.scodoc.sco_config_actions import LogoDelete

        if self.do_delete.data and self.can_delete:
            return LogoDelete.build_action(self.data)
        if self.upload.data and self.validate():
            return LogoUpdate.build_action(self.data)
        if self.do_rename.data and self.validate():
            return LogoRename.build_action(self.data)
        return None

    def opened(self):
        if self.upload.data and self.upload.errors:
            return "open"
        if self.new_name.data and self.new_name.errors:
            return "open"
        return ""


class DeptForm(FlaskForm):
    dept_key = HiddenField()
    dept_name = HiddenField()
    add_logo = FormField(AddLogoForm)
    logos = FieldList(FormField(LogoForm))

    def __init__(self, *args, **kwargs):
        kwargs["meta"] = {"csrf": False}
        super().__init__(*args, **kwargs)

    def id(self):
        return f"id=DEPT_{self.dept_key.data}"

    def is_local(self):
        if self.dept_key.data == GLOBAL:
            return None
        return True

    def select_action(self):
        action = self.add_logo.form.select_action()
        if action:
            return action
        for logo_entry in self.logos.entries:
            logo_form = logo_entry.form
            action = logo_form.select_action()
            if action:
                return action
        return None

    def get_form(self, logoname=None):
        """Retourne le formulaire associé à un logo. None si pas trouvé"""
        if logoname is None:  # recherche de département
            return self
        return self.index.get(logoname, None)

    def opened(self):
        if self.add_logo.opened():
            return "open"
        for logo_form in self.logos:
            if logo_form.opened():
                return "open"
        return ""

    def count(self):
        compte = len(self.logos.entries)
        if compte == 0:
            return "vide"
        elif compte == 1:
            return "1 élément"
        else:
            return f"{compte} éléments"


def _make_dept_id_name():
    """Cette section assure que tous les départements sont traités (y compris ceux qu'ont pas de logo au départ)
    et détermine l'ordre d'affichage des DeptForm (GLOBAL d'abord, puis par ordre alpha de nom de département)
    -> [ (None, None), (dept_id, dept_name)...  ]"""
    depts = [(None, GLOBAL)]
    for dept in (
        Departement.query.filter_by(visible=True).order_by(Departement.acronym).all()
    ):
        depts.append((dept.id, dept.acronym))
    return depts


def _ordered_logos(modele):
    """sort logoname alphabetically but header and footer moved at start. (since there is no space in logoname)"""

    def sort(name):
        if name == "header":
            return " 0"
        if name == "footer":
            return " 1"
        return name

    order = sorted(modele.keys(), key=sort)
    return order


def _make_dept_data(dept_id, dept_name, modele):
    dept_key = dept_id_to_key(dept_id)
    data = {
        "dept_key": dept_key,
        "dept_name": dept_name,
        "add_logo": {"dept_key": dept_key},
    }
    logos = []
    if modele is not None:
        for name in _ordered_logos(modele):
            logos.append({"dept_key": dept_key, "logo_id": name})
    data["logos"] = logos
    return data


def _make_depts_data(modele):
    data = []
    for dept_id, dept_name in _make_dept_id_name():
        data.append(
            _make_dept_data(
                dept_id=dept_id, dept_name=dept_name, modele=modele.get(dept_id, None)
            )
        )
    return data


def _make_data(modele):
    data = {
        "depts": _make_depts_data(modele=modele),
    }
    return data


class LogosConfigurationForm(FlaskForm):
    "Panneau de configuration des logos"
    depts = FieldList(FormField(DeptForm))

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    # def _set_global_logos_infos(self):
    #     "specific processing for globals items"
    #     global_header = self.get_form(logoname="header")
    #     global_header.description = (
    #         "image placée en haut de certains documents documents PDF."
    #     )
    #     global_header.titre = "Logo en-tête"
    #     global_header.can_delete = False
    #     global_footer = self.get_form(logoname="footer")
    #     global_footer.description = (
    #         "image placée en pied de page de certains documents documents PDF."
    #     )
    #     global_footer.titre = "Logo pied de page"
    #     global_footer.can_delete = False

    # def _build_dept(self, dept_id, dept_name, modele):
    #     dept_key = dept_id or GLOBAL
    #     data = {"dept_key": dept_key}
    #     entry = self.depts.append_entry(data)
    #     entry.form.build(dept_name, modele.get(dept_id, {}))
    #     self.index[str(dept_key)] = entry.form

    # def build(self, modele):
    #     "Build the Form hierachy (DeptForm, LogoForm) and add extra data (from modele)"
    #     # if entries already initialized (POST). keep subforms
    #     self.index = {}
    #     # create entries in FieldList (one entry per dept
    #     for dept_id, dept_name in self.dept_id_name:
    #         self._build_dept(dept_id=dept_id, dept_name=dept_name, modele=modele)
    #     self._set_global_logos_infos()

    def get_form(self, dept_key=GLOBAL, logoname=None):
        """Retourne un formulaire:
         * pour un département (get_form(dept_id)) ou à un logo (get_form(dept_id, logname))
         * propre à un département (get_form(dept_id, logoname) ou global (get_form(logoname))
        retourne None si le formulaire cherché ne peut être trouvé
        """
        dept_form = self.index.get(dept_key, None)
        if dept_form is None:  # département non trouvé
            return None
        return dept_form.get_form(logoname)

    def select_action(self):
        for dept_entry in self.depts:
            dept_form = dept_entry.form
            action = dept_form.select_action()
            if action:
                return action
        return None


def config_logos():
    "Page de configuration des logos"
    # nb: le contrôle d'accès (SuperAdmin) doit être fait dans la vue
    form = LogosConfigurationForm(
        data=_make_data(
            modele=sco_logos.list_logos(),
        )
    )
    if form.is_submitted():
        action = form.select_action()
        if action:
            action.execute()
            flash(action.message)
            return redirect(url_for("scodoc.configure_logos"))
        else:
            if not form.validate():
                scu.flash_errors(form)

    return render_template(
        "config_logos.j2",
        scodoc_dept=None,
        title="Configuration ScoDoc",
        form=form,
    )