// Encoding: UTF-8
var debug; // à toutes fins utiles

/* utilitaire pour récupérer le csrftoken entre autres */
function getCookie(name) {
    let cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        const cookies = document.cookie.split(';');
        for (let i = 0; i < cookies.length; i++) {
	    const cookie = cookies[i].trim();
	    // Does this cookie string begin with the name we want?
	    if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
	    }
        }
    }
    return cookieValue;
}

/**
 * Affiche un "dialogue" d'attente pour l'effacer à la fin d'une requête ajax
 * @param message un message texte éventuel
 * @param container le futur parent du "dialogue"
 * @return le "dialogue" à supprimer
 **/
function attente(message, container){
    var laclasse = "wait"
    if (message==undefined) message=""
    if (container==undefined) {
	container = $("body")
	laclasse += " body"
    }
    var d = $("<div class='"+laclasse+"'><img src='static/img/timer3.gif'/><div>"+message+"</div></div>")
    container.append(d)
    d.fadeIn()
    return d
}

/**
 * plugin pour jQuery qui ajoute une animation à un post
 **/
(function($) {

    $.postAnimation = function( url, context, doneFunc ) {
	var d = attente()
        $.post(url, context, doneFunc).always(function(){d.remove()})
    };

})( jQuery );

/**
 * Vérification du format d'un chaîne de date
 * La chaine doit ressembler à aaa-mm-jj, l'année doit être postérieure
 * à 1900, le mois inférieur ou égal à 12, le jour inférieur ou égal à 31
 * @param s chaîne à tester
 **/
function isISOformat(s){
    m = s.match(/^(\d{4})-(\d{2})-(\d{2})$/);
    if (m){
	return parseInt(m[1]) > 1900 && parseInt(m[2]) <= 12 && parseInt(m[3]) <= 31;
    }
    return false;
}

/* utilitaire pour formater une durée en minutes et secondes */
function dureeMinSec(tstamp){
    duration = parseInt((Date.now()-tstamp)/1000);
    min = Math.floor(duration / 60);
    sec = duration - 60*min;
    if (min < 10) min = "0"+min;
    if (sec < 10) sec = "0"+sec;
    return ""+min+":"+sec;
}

function gotolink(elt){
    // pour changer l'adresse
    var url = $(elt).attr("href").match(/#(.*)/)[1];
    document.location = "/" + url + "#" + url;
}

/**
 * Crée un fonction $.redirectPost qui permet de créer un formulaire
 * afin de poster des données vers une URL qu'on va ouvrir.
 **/
$.extend(
{
    redirectPost: function(location, args)
    {
        var form = '';
        $.each( args, function( key, value ) {
            form += '<input type="hidden" name="'+key+'" value="'+value+'">';
        });
        $('<form action="'+location+'" method="POST">'+form+'</form>').appendTo('body').submit();
    }
});

/* fonctions pour constantes.html */
function newConstante(){
    editConstante(-1);
}
function editConstante(id){
    $.redirectPost("/constantes_modif", {
	numero: id,
	csrfmiddlewaretoken: getCookie('csrftoken'),
    });
}

function cloneConstante(id){
    $.redirectPost("/constantes_modif", {
	numero: id,
	clonage: true,
	csrfmiddlewaretoken: getCookie('csrftoken'),
    });
}

function delConstante(id){
    $( "#dialog" ).dialog({
	title: I18N.Attention,
	buttons: [
	    {
		text: I18N.Oui,
		click: function() {
		    $( this ).dialog( "close" );
		    delConstante_ok(id);
		}
	    },
	    {
		text: I18N.Non,
		click: function() {
		    $( this ).dialog( "close" );
		}
	    },
	]
    });
}

function delConstante_ok(id){
    $.post(
	"/constantes_del",
	{
	    numero: id,
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	}
    ).done(
	function (data) {
	    location.reload();
	}
    );
}

/* fonctions pour constantes_modif.html */
/**
 * fait apparaître un message temporaire signalant l'enregistrement
 **/
function signale_saved(){
    $.post(
	"/notifSaveSuccess", {
	    csrfmiddlewaretoken: getCookie('csrftoken')
	}
    ).done(function(data){
	$(data.html)
	    .insertBefore('#form')
	    .delay(1000)
	    .fadeOut(function() {
		$(this).remove();
		document.location = "/constantes";
	    });
    });
}

/* fonctions pour inventaire.html */
function inventaire_init(){
    $("#idlivre").on("keydown", function(e){
	if (e.keyCode == 13){
	    e.preventDefault();
	    aQuiCeLivre(e.target.value);
	}
    });

    $( "#accordion" ).accordion({collapsible: true, active: false});

    /* peuplement de l'inventaire des livres non prêtés */
    new AfficheurDeManuels(
	"#quelsLivres",
	"#nb-affiches",
	"#nb-total",
	"#livres-non-pretes .timericon",
	"#en-stock",
	"#livres-non-pretes",
	"/inventaire_non_pretes_premiers",
	"/inventaire_non_pretes_suivants",
    ).init_collection();

    /* peuplement de l'inventaire des prêts */
    new AfficheurDeManuels(
	"#quelsLivresPretes",
	"#nb-pretes-affiches",
	"#nb-pretes-total",
	"#livres-pretes .timericon",
	"#en-pret",
	"#livres-pretes",
	"/inventaire_pretes_premiers",
	"/inventaire_pretes_suivants",
    ).init_collection();

    /* peuplement de l'inventaire des élèves avec au moins un livre prêté */
    new AfficheurDeleves(
	"#classe_pretes",
	"#eleves-pretes-affiches",
	"#eleves-pretes-total",
	"#eleves-pretes .timericon",
	"#el-prets",
	"#eleves-pretes",
	"/eleves_pretes_premiers",
	"/eleves_pretes_suivants",
    ).init_collection();

    /* peuplement de l'inventaire des élèves sans aucun livre prêté */
    new AfficheurDeleves(
	"#classe_sans_prets",
	"#eleves-sans-prets-affiches",
	"#eleves-sans-prets-total",
	"#eleves-sans-prets .timericon",
	"#el-sans-prets",
	"#eleves-sans-prets",
	"/eleves_sans_prets_premiers",
	"/eleves_sans_prets_suivants",
    ).init_collection();

    /**
     * on efface la sélection de livres non-prêtés si la page est rafraîchie
     * puis on met du contenu dans les inventaires de livres non-pretes
     * et prêtés
     **/
    $(window).ready(
	function(){
	    $("#quelsLivres").val("");
	    $("#quelsLivresPretes").val("");
	}
    );
}

function aQuiCeLivre(ident){
    var input = $("#idlivre");
    // on efface le contenu du champ de saisie et on en garde la valeur
    // sous forme de placeholder
    input.val("");
    input.attr({placeholder: ident,});
    $.post(
	"/aqui",
	{
	    ident: ident,
	    csrfmiddlewaretoken: getCookie('csrftoken')
	},
    ).done(
	function(data){
	    $("#donnees_livre").html(data);
	}
    );
}

/**
 * Fonction de rappel pour afficher un état des livres prêtés, par classe
 **/
function etat_classe(elt){
    var value = $(elt).val();
    $("#classe-input").val(value);
    $("#classe-form").submit();
}

/**
 * Initialisation pour la page /prets
 * @param nomprenom : si ce paramètre n'est pas une chaîne vide,
 *     on passe tout de suite à l'affichage du tableau
 **/
function prets_init(nomprenom){
    var nomprenomInput = $("#nomprenom");
    nomprenomInput.autocomplete({
	minLength: 3,
	source: function(request, response){
	    $.ajax({
		url: "/correspondance_nom",
		type: "post",
		data: {
		    term: nomprenomInput.val(),
		    csrfmiddlewaretoken: getCookie('csrftoken')
		},
		dataType: "json",
		success: function(data) {
		    response($.map( data, function( item ) {
			return {
			    label: item,
			    value: item
			}
		    }));
		}
	    });
	},
	select: function (e, ui) {
            if (ui.item.value) {
		affiche_tableau_prets(ui.item.value);
		$("#codelivre").focus();
            }
	}
    });

    nomprenomInput.on("keydown", function(e){
	if (e.keyCode == 13){
	    e.preventDefault();
	    affiche_tableau_prets(nomprenomInput.val())
	    $("#codelivre").focus();
	}
    });
    if (nomprenom) {
	affiche_tableau_prets(nomprenom);
	$("#codelivre").focus();
    }

    var codeLivreInput = $("#codelivre");
    codeLivreInput.on("keydown", function(e){
	if (e.keyCode == 13){
	    e.preventDefault();
	    // on démarre le prêt de nouveau livre
	    prete_livre(nomprenomInput.val(), codeLivreInput.val());
	    // on évite que le code du livre reste actif
	    codeLivreInput.attr({placeholder: codeLivreInput.val()});
	    codeLivreInput.val("");
	}
    });
    ajuste_nouveaux_livres();
    /**
     * Si dès le début de l'affichage de la page, il y a un nom, alors
     * c'est qu'il s'agit d'un reload : on peut afficher le tableau des prêts
     **/
    if(nomprenomInput.val()){
	affiche_tableau_prets(nomprenomInput.val());
    }
}

/**
 * Initialisation pour la page /choix_eleves
 **/
function choix_eleves_init(){
    // fonction de rappel pour la sélection « un par un »
    var nomprenomInput = $("#nomprenom");
    nomprenomInput.autocomplete({
	minLength: 3,
	source: function(request, response){
	    $.ajax({
		url: "/correspondance_nom",
		type: "post",
		data: {
		    term: nomprenomInput.val(),
		    showclass: true,
		    csrfmiddlewaretoken: getCookie('csrftoken')
		},
		dataType: "json",
		success: function(data) {
		    response($.map( data, function( item ) {
			return {
			    label: item,
			    value: item
			}
		    }));
		}
	    });
	},
	select: function (e, ui) {
            if (ui.item.value) {
		valide_choix_eleve(ui.item.value);
		setTimeout(function(){nomprenomInput.val("");}, 0);
            }
	}
    });

    nomprenomInput.on("keydown", function(e){
	if (e.keyCode == 13){
	    e.preventDefault();
	    valide_choix_eleve(nomprenomInput.val());
	    setTimeout(function(){nomprenomInput.val("");}, 0);
	}
    });
    // fonction de rappel pour la sélection par classe
    var class_select = $("#cl")
    class_select.on("change", valide_choix_classe)
    // fonction de rappel pour « Au moins N livres prêtés »
    var amp = $("#au_moins_pretes")
    amp.on("keydown", function(e){
	if (e.keyCode == 13){
	    e.preventDefault();
	    $.postAnimation(
		"/amp", {
		    csrfmiddlewaretoken: getCookie('csrftoken'),
		    showclass: true,
		    n: amp.val(),
		},
		function(data){
		    if (data.ok){
			data.nomprenoms.forEach(
			    function(nomprenom){
				valide_choix_eleve(nomprenom)
			    }
			)
		    }
		}
	    )
	    setTimeout(function(){
		amp.attr("placeholder", amp.val())
		amp.val("");
	    }, 0);
	}
    })
    // fonction de rappel pour « Au plus N livres prêtés »
    var app = $("#au_plus_pretes")
    app.on("keydown", function(e){
	if (e.keyCode == 13){
	    e.preventDefault();
	    $.postAnimation(
		"/app", {
		    csrfmiddlewaretoken: getCookie('csrftoken'),
		    showclass: true,
		    n: app.val(),
		},
		function(data){
		    if (data.ok){
			data.nomprenoms.forEach(
			    function(nomprenom){
				valide_choix_eleve(nomprenom)
			    }
			)
		    }
		}
	    )
	    setTimeout(function(){
		app.attr("placeholder", app.val())
		app.val("");
	    }, 0);
	}
    })
    // fonction de rappel pour « A emprunté tel livre »
    var code = $("#code")
    code.on("keydown", function(e){
	if (e.keyCode == 13){
	    e.preventDefault();
	    $.postAnimation(
		"/etl",{
		    csrfmiddlewaretoken: getCookie('csrftoken'),
		    showclass: true,
		    code: code.val(),
		},
		function(data){
		    if (data.ok){
			data.nomprenoms.forEach(
			    function(nomprenom){
				valide_choix_eleve(nomprenom)
			    }
			)
		    }
		}
	    )
	    setTimeout(function(){
		code.attr("placeholder", code.val())
		code.val("");
	    }, 0);
	}
    })
    // fonction de rappel pour « Mot-clé de message »
    var cle = $("#cle")
    cle.on("keydown", function(e){
	if (e.keyCode == 13){
	    e.preventDefault();
	    $.postAnimation(
		"/mcm",{
		    csrfmiddlewaretoken: getCookie('csrftoken'),
		    showclass: true,
		    cle: cle.val(),
		},
		function(data){
		    if (data.ok){
			data.nomprenoms.forEach(
			    function(nomprenom){
				valide_choix_eleve(nomprenom)
			    }
			)
		    }
		}
	    )
	    setTimeout(function(){
		cle.attr("placeholder", code.val())
		cle.val("");
	    }, 0);
	}
    })
}

function valide_choix_classe( event){
    var cl = $("#cl").val()
    if (! cl.length) return
    $.post(
	"/eleves_de_classe", {
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	    classe : cl,
	    showclass:true,
	},
	function(data){
	    data.nomprenoms.forEach(
		function(nomprenom){
		    valide_choix_eleve(nomprenom)
		}
	    )
	}
    )
}

/**
 * ajoute un élève dans l'affichage des élèves choisis
 **/
function valide_choix_eleve(nomprenom){
    var choix_div = $("#choix")
    var deja_choisis = $("#choix .nom")
    var deja_noms = []
    deja_choisis.each(function(_,elt){deja_noms.push($(elt).text())})
    if (deja_noms.indexOf(nomprenom) >= 0) return
    // on ajoute le nomprenom car il n'est pas déjà là
    var img = '<img src="/static/img/edit-delete.svg" style="width: 16px;"/>'
    var button = "<button onclick='del_choix_eleve(this)' style='margin: 2px;'>"+img+"</button>"
    var nouveau = $("<div class='nom')>"+button+nomprenom+"</div>")
    if (deja_choisis.length == 0){
	choix_div.append(nouveau)
    } else {
	var i
	for (i = 0; i < deja_choisis.length; i++){
	    if ($(deja_choisis[i]).text() >= nomprenom) break;
	}
	if (i == 0) {
	    nouveau.insertBefore($(deja_choisis[i]))
	} else {
	    nouveau.insertAfter($(deja_choisis[i-1]))
	}
    }
    
}

/**
 * retire un élève choisi, pour la page /choix_eleves
 **/
function del_choix_eleve(elt){
    $(elt).parent().remove()
}

/**
 * Efface le champ de saisie du nom/prénom
 * pour la page /prets ou pour la page /retours
 **/
function efface_nomprenom(){
    var i = $("#nomprenom");
    /* pour la page /prets */
    $("#tableau_prets").fadeOut();
    /* pour la page /retours */
    $("#impressions").fadeOut();
    $("#livres-rendus").fadeOut();
    $("#nouveaux_livres").fadeOut();
    i.val("");
    i.focus();
}

/**
 * Prête effectivement une série de manuels à quelqu'un
 **/
function prets_go(){
    var nomprenom = $("#nomprenom");
    var liste_codes = [];
    var etats = []
    // attention, on ne prête que les manuels non déjà prêtés,
    // d'où le selecteur qui ne prend que dans #nouveaux_livres
    $("#nouveaux_livres .id_manuel").each(function(i, elt){
	var tr = $(elt).parent();
	var id = parseInt($(elt).text().trim());
	liste_codes.push(id);
	var sel = tr.find("select");
	etats.push(parseInt(sel.val()));
    });
    $.post(
	"/prets_go",
	{
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	    nomprenom: nomprenom.val(),
	    liste_codes: JSON.stringify(liste_codes),
	    etats: JSON.stringify(etats),
	},
	function(data){
	    $("#shortmessage").fadeIn();
	    setTimeout(function(){
		$("#shortmessage").fadeOut();
		location.reload();
	    }, 2000);
	}
    );
}

/**
 * Montre ou ne montre pas la section des nouveaux livres dans la page /prets
 * selon que le tableau des nouveaux livres contient plus d'une ligne ou non
 * Émet aussi un message d'avertissement quand on donne plus d'une fois
 * le même titre
 **/
function ajuste_nouveaux_livres(){
    var div = $("#nouveaux_livres");
    var multiples = parseInt($("#multiples").val())
    var table = div.find("table");
    var tr = table.find("tr");
    var nb_livres = tr.length - 1;
    $.post(
	"/nouveaux_livres_caption", {
	    nb_livres: nb_livres,
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	}
    ).done(function(data){
	if (nb_livres < 1) div.hide(); else {
	    div.fadeIn();
	    $("#caption0").text(data.caption);
	}
    });
    var titres = Object();
    $(".title_manuel").each(function(i, elt){
	var titre = $(elt).text().trim();
	if (!(titre in titres)) titres[titre] = 1;
	else titres[titre] += 1;
    });
    // on sait le contenu de titres, on prépare des messages localisés
    $.post(
	"/pret_multiple_msg", {
	    lestitres: JSON.stringify(titres),
	    multiples: multiples,
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	}
    ).done(function(data){
	var messages = data.messages;
	if (messages.length){
	    // des messages sont à afficher, on met une icone sonore
	    document.querySelector("#soundicon1").play()
	    var msgdiv = $("#messages");
	    msgdiv.html("");
	    messages.forEach(function(m){
		msgdiv.append($("<p>").text(m));
	    });
	    msgdiv.dialog({
		title: data.title,
		width: 500,
		modal: true,
		buttons: [
		    {
			text: I18N.Non,
			click: function() { $( this ).dialog( "close" ); }
		    },
		    {
			text: I18N.Oui,
			click: function() {
			    $( this ).dialog( "close" );
			    $("#multiples").val(""+ (multiples + 1));
			}
		    },
		],
	    });
	} else {
	    // fait entendre une icône sonore
	    document.querySelector("#soundicon2").play()
	}
    });
    lob(); // réaffiche la liste des livres à emprunter
}

/**
 * Fait en sorte de prêter un livre à un élève si possible
 * @param nomprenom la désignation de l'élève
 * @code le code du livre
 **/
function prete_livre(nomprenom, code){
    var cases_codes = $(".id_manuel");
    var deja_codes = [];
    cases_codes.each(function(i,elt){
	deja_codes.push($(elt).text().trim());
    });
    $.post(
	"/prete_livre",
	{
	    nomprenom: nomprenom,
	    code: code,
	    deja_codes: JSON.stringify(deja_codes),
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	},
	function(data){
	    if (! data.ok){
		if (data.nomprenom.length > 0){
		    alerteLivreDunTiers(data);
		} else {
		    document.querySelector("#soundicon3").play();
		    var messages = $("#messages")
		    messages.html("");
		    data.messages.forEach(function(m){
			messages.append($("<p>").text(m));
		    });
		    messages.dialog({
			dialogClass: "alert",
			modal: true,
			title: I18N.erreurs,
			buttons: [],
		    })
		}
	    } else {
		// c'est OK pour un prêt
		var tr = $(data.tr_manuel);
		$("#nouveaux_livres table").prepend(tr);
		ajuste_nouveaux_livres();
	    }
	}
    );
}

/**
 * Annule une proposition de prêt : retire une ligne du tableau
 * $("#nouveaux_livres table"), ajuste la visibilité du tableau
 **/
function deprete_livre(elt){
    $(elt).parent().parent().remove();
    ajuste_nouveaux_livres();
}

function affiche_tableau_prets(nomprenom){
    if (! nomprenom.length) return
    $.post(
	"/tableau_prets",
	{
	    nomprenom: nomprenom,
	    csrfmiddlewaretoken: getCookie('csrftoken')
	},
    ).done(
	function(data)
	{
	    // laisser le temps d'afficher le code HTML qui peut
	    // contenir un message, avant de traiter ce message
	    setTimeout(show_message, 0)
	    if ((! data.caution_en_cours) & data.nomprenom.length > 0){
		/* l'élève existe, mais il n'a pas encore de caution valide */
		$("#impressions").fadeOut();
		$("#tableau_prets").fadeOut();
		$("#meme-classe").fadeOut();
		$("#messages").html(data.pas_de_caution);
		$("#messages").dialog({
		    title: data.title,
		    buttons: [
			{
			    text: I18N.Non,
			    click: function() { $( this ).dialog( "close" ); }
			},
			{
			    text: I18N.Oui,
			    click: function() {
				$( this ).dialog( "close" );
				nouvelle_caution($("#nomprenom").val());
			    }
			},
			
		    ],
		});
		return
	    }
	    if (data.msg.length > 0){
		$("#impressions").fadeOut();
		$("#tableau_prets").fadeOut();
		$("#meme-classe").fadeOut();
		$("#messages").html("<p>"+data.msg+"</p>");
		$("#messages").dialog({title: data.title_error});
		return
	    }
	    // affichera la liste des livres dès que possible
	    $("#tableau_prets").html(data.html_prets).fadeIn();
	    affiche_meme_classe_pour_prets(data.classe, data.nomprenom);
	}
    );
}

/**
 * Produit un dialogue basé sur le contenur de $("#message")
 **/
function show_message(){
    var message=$("#message")
    if(message.text().trim()){
	message.dialog({
	    title: message.data("title"),
	    height: 300,
	    modal: true,
	    buttons: [
		{
		    text: message.data("button"),
		    click: function() {
			$( this ).dialog( "close" )
			setTimeout(
			    function(){del_message(message.data('id'))},
			    0)
		    }
		}
	    ]
	})
    }
}

/**
 * supprime un message contenu dans $("#message") et fait un petit feedback
 **/
function del_message(ident){
    $.post("/del_message", {
	ident: ident,
	csrfmiddlewaretoken: getCookie('csrftoken'),
    }, function(data){
	if (data.ok) {
	    var message = $("#message")
	    message.text(data.message)
	    message.dialog({
		title: "",
		buttons: [
		    {
			text: "Ok",
			icon: "ui-icon-heart",
			click: function() {
			    $( this ).dialog( "close" );
			}
		    }
		]
	    })
	}
    })
}

/**
 * Crée une nouvelle caution pour un élève
 **/
function nouvelle_caution(nomprenom){
    $.post(
	"/nouvelle_caution",
	{
	    nomprenom: nomprenom,
	    csrfmiddlewaretoken: getCookie('csrftoken')
	},
    ).done(
	function(data){
	    affiche_tableau_prets(data.nomprenom);
	}
    );
}


/**
 * Affiche les élèves d'une classe dans un DIV #meme-classe
 * chaque nom d'élève permet de faire un lien vers l'affichage de ses prêts
 * @param cl le nom d'une classe
 * @param nomprenom le nom d'un élève
 **/
function affiche_meme_classe_pour_prets(cl, nomprenom){
    $("#meme-classe").fadeIn();
    $.post (
	"/classe_pour_prets",
	{
	    classe: cl,
	    nomprenom: nomprenom,
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	},
	function(data){
	    $("#meme-classe").html(data.html);
	    scrolle_vers_meme_eleve();
	}
    );
}

/**
 * Scrolle le conteneur du tableau de la même classe pour afficher
 * l'élève intéressant.
 **/
function scrolle_vers_meme_eleve(){
    $("#meme-classe").ready(function(){
	var m = $(".meme-eleve").first();
	var c = m.parents(".liste-scroll-y").first();
	var h = m.offset().top - c.offset().top;
	c.animate({
	    scrollTop: h
	},1000);
    });
}

/**
 * Affiche les élèves d'une classe dans un DIV #meme-classe
 * chaque nom d'élève permet de faire un lien vers l'affichage de ses retours
 * @param cl le nom d'une classe
 * @param nomprenom le nom d'un élève
 **/
function affiche_meme_classe_pour_retours(cl, nomprenom){
    $("#meme-classe").fadeIn();
    $.post (
	"/classe_pour_retours",
	{
	    classe: cl,
	    nomprenom: nomprenom,
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	},
	function(data){
	    $("#meme-classe").html(data.html);
	    scrolle_vers_meme_eleve();
	    $("#meme-classe").ready(
		function(){
		    desactive_impressions();
		}
	    );
	}
    );
}

/**
 * Ouvre l'URL /eleves sur un élève donné par son nom et son prenom
 **/
function goto_base_eleves(nomprenom){
    var form = $("<form>").attr({
	display: "none",
	method: "post",
	action: "/eleves",
	id: "goto_form",
    });
    $("body").append(form);
    form.append($("<input>").attr({name: "csrfmiddlewaretoken"}).val(
	getCookie('csrftoken')));
    form.append($("<input>").attr({name: "nomprenom"}).val(
	nomprenom));
    form.submit();
}

/**
 * Agit dans le cadre de la fenêtre des prêts. Met en place le nom et le prénom,
 * puis déclenche l'affichage des prêts pour cet élève
 * @param nomprenom identifie l'élève
 **/
function montre_prets(nomprenom){
    $("#nomprenom").val(nomprenom);
    valide_champ("#nomprenom");
    $("#codelivre").focus();
}

/**
 * Agit dans le cadre de la fenêtre des retours.
 * Met en place le nom et le prénom,
 * puis déclenche l'affichage des prêts pour cet élève
 * @param nomprenom identifie l'élève
 **/
function montre_retours(nomprenom){
    $("#nomprenom").val(nomprenom);
    valide_champ("#nomprenom");
    $("#codelivre").focus();
}

/**
 * Crée la fiche de prêt de livres, au format PDF pour un élève
 **/
function prets_mkPDF(){
    $("#codelivre").focus();
    var nomprenom = $("#nomprenom").val();
    var form = $("<form>").attr({
	display: "none",
	method: "post",
	action: "/prets_mkPDF",
	id: "pdfForm",
    });
    form.append($("<input>").attr({name: "csrfmiddlewaretoken"}).val(
	getCookie('csrftoken')));
    form.append($("<input>").attr({name: "nomprenom"}).val(
	nomprenom));
    $("body").append(form);
    $("body").ready(function(){
	form.submit();
	form.remove();
    })
}

/**
 * Crée les avenants à une fiche de prêt de livres, au format PDF pour un élève
 * @param n numéro de l'avenant 1 ou 2 (pour haut de page, bas de page)
 **/
function avenant_mkPDF(n){
    $("#codelivre").focus();
    var nomprenom = $("#nomprenom").val();
    var form = $("<form>").attr({
	display: "none",
	method: "post",
	action: "/avenant_mkPDF",
	id: "pdfForm"+n,
    });
    form.append($("<input>").attr({name: "csrfmiddlewaretoken"}).val(
	getCookie('csrftoken')));
    form.append($("<input>").attr({name: "nomprenom"}).val(nomprenom));
    form.append($("<input>").attr({name: "n"}).val(n));
    $("body").append(form);
    $("body").ready(function(){
	form.submit();
	form.remove();
    })
}

/* fonctions pour stats.html */

function stats_init(){
    var div_manuels = $("#liste_manuels");
    demande_manuels()

    $("#cat_manuel").on("change", function(){
	$("#debut").val("0");
	demande_manuels();
    });
}

function demande_manuels(){
    $.post(
	"/liste_manuels",
	{
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	    debut: $("#debut").val(),
	    cat_manuel: $("#cat_manuel").val(),
	}
    ).done(
	function(data){
	    $("#liste_manuels").html(data);
	}
    );
}

function enVoirPlus(){
    // modification du paramètre "debut"
    $("#debut").val($("#les_manuels").attr("data_fin"));
    demande_manuels();
}

/**
 * Crée une fonction à rappeler en cas de scroll d'un afficheur : regarde
 * si l'icône d'attente est visible dans le cadre scrollé ; si elle est visible
 * l'objet that appelle sa fonction continue_collection() qui affichera plus
 * d'items affichables
 * @param that l'objet pour lequel on veut définir la fonction
 * @return une procédure sans argument
 **/
function prolongateur(that){
    return function(){
	if (that.fini) return;
	if (that.icon.offset() == undefined || that.scrollable.offset() == undefined) return;
	var bottom = that.icon.offset().top - that.scrollable.offset().top + that.icon.height();
	var height = that.scrollable.height() + 50;
	if (bottom > 0 && bottom < height) {
	    /* le bas de la zone affichable est visible,
	       il est temps de prolonger l'affichage */
	    that.continue_collection();
	}
    }
}

/**
 * Une classe pour afficher des références de manuels dans un widget
 * scrollable
 **/
class AfficheurDeManuels{
    /**
     * Conctructeur de la classe AfficheurDeManuels
     * @param sel_input sélecteur du champ pour choisir le manuel
     * @param sel_partiel sélecteur de l'afficheur du compte de
     *     manuels déjà affichés
     * @param sel_total sélecteur de l'afficheur du total de manuels
     * @param sel_icon sélecteur de l'icône d'attente
     * @param sel_collection sélecteur da la collection de widgets de manuels
     * @param sel_scrollable sélecteur du DIV scrollable
     * @param url_init l'URL d'une requête AJAX pour commencer la collection
     * @param url_continue l'URL d'une requête AJAX pour continuer la collection
     **/
    constructor(
	sel_input,
	sel_partiel,
	sel_total,
	sel_icon,
	sel_collection,
	sel_scrollable,
	url_init,
	url_continue,
    ){
	/* enregistrement de paramètres pour plus tard */
	this.input = $(sel_input);           // elt jQuery : choix de manuel
	this.partiel = $(sel_partiel);       // elt jQuery : compte les manuels
	this.total = $(sel_total);           // elt jQuery : total des manuels
	this.icon = $(sel_icon);             // elt jQuery : l'icone
	this.collection = $(sel_collection); // elt jQuery : la collection
	this.scrollable = $(sel_scrollable); // elt jQuery : DIV scrollable
	this.url_init = url_init;            // URL Ajax pour initialiser
	this.url_continue = url_continue;    // URL Ajax pour continuer

	/* ici c'est l'initialisation proprement dite */
	this.lock_prolonge = false;
	this.fini = false; // booléen : le scrollage n'est plus nécessaire ?
	this.scrollable.scroll(function(afficheur){
	    return function(event){
		afficheur.prolonge()
	    }
	}(this));
	this.init_autocomplete(this)
    }

    /**
     * Initialise le champ de saisie du titre de livre afin qu'il
     * permette l'autocomplétion.
     * @param afficheur sert à contourner l'usage de this dans les
     *   gestionnaires d'évènement
     **/
    init_autocomplete(afficheur){
	
	/**
	 * Renouvelle l'affichage dans la collection de manuels
	 * mais seulement si la valeur d'input diffère de son
	 * attribut "data-last"
	 * param val la valeur de titre à utiliser pour peupler la
	 *    collection; si val n'est pas défini, le titre est lu depuis
	 *    afficheur.input
	 **/
	function renew(val){
	    if (val == undefined) val = afficheur.input.val();
	    if (val != afficheur.input.attr("data-last")){
		afficheur.input.attr("data-last", val);
		afficheur.init_collection(val);
	    }
	}
	
	afficheur.input
	    .attr({"data-last": ""})
	    .on("keydown", function(e){
		if (e.keyCode == 13){
		    e.preventDefault();
		    renew();
		}
	    })
	    .on("blur", function(e){
		renew();
	    })
	    .autocomplete({
		minLength: 3,
		source: function(request, response){
		    $.post(
			"/correspondance_livre",
			{
			    term: afficheur.input.val(),
			    csrfmiddlewaretoken: getCookie('csrftoken')
			},
			function(data) {
			    response($.map( data, function( item ) {
				return {
				    label: item.title,
				    value: item.id,
				}
			    }));
			}
		    )},
		select: function (e, ui) {
		    if (ui.item.value) {
			/* modifie l'affichage des livres */
			renew(ui.item.value)
			/* remet le label dans l'input */
			setTimeout(function(){
			    afficheur.input.val(ui.item.label);
			});
		    }
		}
	    });
    }


	
    /**
     * Initialise le contenu de la collection de manuels visibles
     *
     * @param id_catalogue_ou_titre éventuellement défini ; s'il est numérique
     *    c'est une clé primaire donnent un titre de manuel ; si c'est un
     *    texte, on essaiera de préciser de quel mauel is s'agit
     **/
    init_collection(id_catalogue_ou_titre){
	this.fini = false;
	this.icon.show();
	var params = {
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	    id_catalogue: id_catalogue_ou_titre,
	};
	if(id_catalogue_ou_titre == undefined) {
	    /* on s'assure que le paramètre ne soit pas indéfini */
	    id_catalogue_ou_titre=""
	} else {
	    /* on s'assure que ce soit une chaîne de caractères */
	    id_catalogue_ou_titre += "";
	}
	if(! id_catalogue_ou_titre.match(/^\d+$/)){
	    /* on a détecté autre chose qu'un nombre */
	    /* modification des paramètres           */
	    params = {
		csrfmiddlewaretoken: getCookie('csrftoken'),
		title: id_catalogue_ou_titre,
	    };
	}
	var url = this.url_init;
	$.post(
	    this.url_init,
	    params,
	    function(afficheur){
		return function (data){
		    afficheur.collection.html(""); // r.à.z.
		    afficheur.ajoute_manuels(
			data.manuels, afficheur.collection);
		    afficheur.total.text(data.total);
		    afficheur.numero_a_jour(data);
		    afficheur.collection.ready(function(){
			if (data.fini){
			    afficheur.fini = true;
			    afficheur.icon.hide();
			}
		    });
		}
	    }(this)
	);
    }

    /**
     * Ajoute une série de manuels à la fin de l'afficheur
     * @param données des manuels
     * @param collection l'élément jQuery où on les met
     **/
    ajoute_manuels(manuels, collection){
	manuels.forEach(function(elt){
	    var id = elt[0], title = elt[1];
	    var div = $("<div>")
		.addClass("flexmini linkcursor")
		.attr({"data-id": id})
	    var p = $("<p>")
		.attr({title: title, "data-id": id})
		.on("click", function(e){
		    var code = $(e.target).attr("data-id");
		    $("#idlivre").val(code);
		    valide_champ("#idlivre");
		})
		.text(id);
	    div.append(p);
	    collection.append(div);
	});

    }

    /**
     * Fonction de rappel pour l'évènement scroll
     **/
    prolonge = prolongateur(this);
    
    /**
     * Continue d'afficher une collection et met fin, si besoin est
     **/
    continue_collection(){
	if (this.lock_prolonge) return;
	else this.lock_prolonge = true;
	var url = this.url_continue;
	$.post(
	    this.url_continue,
	    {
		csrfmiddlewaretoken: getCookie('csrftoken'),
	    },
	    function (afficheur){
		return function(data){
		    afficheur.ajoute_manuels(
			data.manuels, afficheur.collection);
		    afficheur.numero_a_jour(data);
		    afficheur.collection.ready(function(){
			if (data.fini){
			    afficheur.fini = true;
			    afficheur.icon.hide();
			}
		    });
		    afficheur.lock_prolonge = false;
		};
	    }(this));
    }

    /**
     * Met à jour le nombre de manuels affichés
     **/
    numero_a_jour(data){
	var numero = parseInt(data.numero);
	if (numero){
	    if (data.fini) {
		this.partiel.text(this.total.text());
	    } else {
		this.partiel.text(numero);
	    }
	}
    }
}

/* fonctions pour catalogue.html */

function newCatalogue(){
    editCatalogue(-1);
}
function editCatalogue(id){
    $.redirectPost("/catalogue_modif",
		   {
		       numero: id,
		       csrfmiddlewaretoken: getCookie('csrftoken'),
		   });
}

function cloneCatalogue(id){
    $.redirectPost("/catalogue_modif",
		   {
		       numero: id,
		       clonage: true,
		       csrfmiddlewaretoken: getCookie('csrftoken'),
		   });
}

function delCatalogue(id){
    $( "#dialog" ).dialog({
	title: I18N.Attention,
	buttons: [
	    {
		text: I18N.Oui,
		click: function() {
		    $( this ).dialog( "close" );
		    delCatalogue_ok(id);
		}
	    },
	    {
		text: I18N.Non,
		click: function() {
		    $( this ).dialog( "close" );
		}
	    },
	]
    });
}

function delCatalogue_ok(id){
    $.post(
	"/catalogue_del",
	{
	    numero: id,
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	}
    ).done(
	function (data) {
	    location.reload();
	}
    );
}

/**
 * Initialisation de la page catalogue_modif
 * @param categories une liste de catégories comme  ['2D', '1G', '1STMG', ...]
 **/
function catalogue_modif_init(categories, options){
    // selecteur parmi les types de classes
    var input1 = $("#id_categories"); // existe dans le formulaire
    var td = input1.parent();
    var select1 = $("<select id='cats'>");
    select1.append($("<option>").text($("#select-categorie").val()));
    for (var i=0; i < categories.length; i++){
	select1.append($("<option>").text(categories[i]));
    }
    td.append(select1);
    select1.on("change", function(){
	// on ajoute une nouvelle catégorie si elle n'est pas déjà là
	var val = $("#cats").val();
	if (categories.indexOf(val) >= 0){
	    var oldcat = input1.val();
	    if (oldcat.length > 0){
		if (oldcat.indexOf(val) < 0){
		    input1.val(oldcat + ", " + val);
		}
	    } else {
		input1.val(val);
	    }
	}
    });
    // sélecteur parmi les options possibles
    var input2 = $("#id_lib_option");
    var td = input2.parent();
    var select2 = $("<select id='opts'>");
    select2.append($("<option>").text("----"));
    for (var i=0; i < options.length; i++){
	select2.append($("<option>").text(options[i]));
    }
    td.append(select2);
    select2.on("change", function(){
	// on ajoute une nouvelle option si elle n'est pas déjà là
	var val = $("#opts").val();
	if (val[0] != "-"){
	    var oldopts = input2.val()
	    if (oldopts.indexOf(val) < 0){
		input2.val(oldopts + ", " + val);
	    }
	}
    });
}

/* fonctions pour catalogue_modif.html */
/**
 * fait apparaître un message temporaire signalant l'enregistrement
 **/
function signale_catalogue_saved(){
    $.post(
	"/notifSaveSuccess", {
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	}
    ).done(function(data){
	$(data.html)
	    .insertBefore('#form')
	    .delay(1000)
	    .fadeOut(function() {
		$(this).remove();
		document.location = "/catalogue";
	    });
    });
}

/**
 * @class TableauDeManuels un afficheur de données de manuels sous forme de
 * tableau à affichage progressif ; utile pour la page "/purger_prets"
 **/

class TableauDeManuels{
    /**
     * Conctructeur de la classe TableauDeManuels
     * @param sel_d1 sélecteur du champ de saisie de la date de début
     * @param sel_d2 sélecteur du champ de saisie de la date de fin
     * @param sel_numero sélecteur de l'afficheur du compte de
     *     manuels déjà affichés
     * @param sel_total sélecteur de l'afficheur du total de manuels
     * @param sel_icon sélecteur de l'icône d'attente
     * @param sel_table sélecteur du tableau de manuels
     * @param sel_scrollable sélecteur du DIV scrollable
     * @param url_init l'URL d'une requête AJAX pour commencer la collection
     * @param url_continue l'URL d'une requête AJAX pour continuer la collection
     **/
    constructor(
	sel_d1,
	sel_d2,
	sel_numero,
	sel_total,
	sel_icon,
	sel_table,
	sel_scrollable,
	url_init,
	url_continue,
    ){
	/* enregistrement de paramètres pour plus tard */
	this.d1 = $(sel_d1);                 // elt jQuery : date de début
	this.d2 = $(sel_d2);                 // elt jQuery : date de fin
	this.numero = $(sel_numero);         // elt jQuery : compte les manuels
	this.total = $(sel_total);           // elt jQuery : total des manuels
	this.icon = $(sel_icon);             //  elt jQuery : l'icone
	this.table = $(sel_table);           //  elt jQuery : le tabeau
	this.scrollable = $(sel_scrollable); // elt jQuery : DIV scrollable
	this.url_init = url_init;            // URL Ajax pour initialiser
	this.url_continue = url_continue;    // URL Ajax pour continuer

	/* ici c'est l'initialisation proprement dite */
	this.self = this;
	this.lock_prolonge = false;
	this.fini = false; // booléen : le scrollage n'est plus nécessaire ?
	this.scrollable.scroll(function(afficheur){
	    return function(event){
		afficheur.prolonge()
	    }
	}(this));
	/* ajoute des traitements particulier pour les champs de dates */
	function dateInputs(afficheur){
	    [afficheur.d1, afficheur.d2].forEach( function(input){
		function renew(){
		    afficheur.init_table();
		}
		input
		    .attr({"data-last": ""})
		    .on("keydown", function(e){
			if (e.keyCode == 13){
			    e.preventDefault();
			    renew();
			}})
		    .on("blur", function(e){
			renew();
		    })
	    });
	}
	dateInputs(this);
	
    }

    /**
     * Vérifie que this.d1 et this.d2 contiennent bien des dates indéfinies
     * ou alors à un format acceptable
     **/
    bad_date(){
	var d1 = this.d1.val();
	var d2 = this.d2.val();
	return ( d1 && ! isISOformat(d1)) || ( d2 && ! isISOformat(d2)) 
    }

    /**
     * Initialise le contenu du tableau de manuels ; prend en compte
     * les dates de début et de fin, après vérification que leur format
     * soit acceptable
     * param force s'il est vrai, ça crée le tableau sans chercher
     * à vérifier si les contenus des champs de date ont varié
     **/
    init_table(force){
	this.fini = false;
	if (! force){
	    /* on ne force pas l'affichage du tableau : */
	    /* les dates sont à vérifier                */
	    if (this.bad_date()){
		$("#message").dialog({
		    dialogClass: "alert",
		    title: I18N.date_erreur,
		    buttons: [
			{
			    text: I18N.OK,
			    click: function() {
				$( this ).dialog( "close" );
			    }
			}
		    ]
		});
		return;
	    } else {
		/* les dates sont valides */
		if (this.d1.val() == this.d1.attr("data-last") && this.d2.val() == this.d2.attr("data-last")){
		    /* aucun changement dans les dates, rien à faire ici */
		    return
		} else {
		    /* une des dates au moins a changé ; on retient cela */
		    this.d1.attr("data-last", this.d1.val());
		    this.d2.attr("data-last", this.d2.val());
		}
	    }
	}
	$.post(
	    this.url_init,
	    {
		csrfmiddlewaretoken: getCookie('csrftoken'),
		date1: this.d1.val(),
		date2: this.d2.val(),
	    },
	    function(afficheur){
		return function (data){
		    /* effacement des lignes de données du tableau */
		    afficheur.table.find("tr").not(':first').remove();
		    /* mise en place des nouvelles données */
		    afficheur.ajoute_manuels(data.manuels, afficheur.table);
		    /* met à jour le décompte des manuels affichés */
		    if (data.numero)
			afficheur.numero.text(parseInt(data.numero));
		    afficheur.total.text(data.total);
		    afficheur.table.ready(function(){
			afficheur.icon.show();
			afficheur.prolonge();
		    });
		}
	    }(this)
	);
    }

    /**
     * Ajoute des manuels
     * @param manuels une liste
     * @param t le tableau où ajouter la liste
     **/
    ajoute_manuels(manuels,t){
	manuels.forEach( function(item){
	    var tr = $("<tr>");
	    tr.append($("<td>").text(item.prete));
	    tr.append($("<td>").text(item.rendu));
	    tr.append($("<td>").text(item.eleve));
	    tr.append($("<td>").text(item.titre));
	    t.append(tr);
	});
    }

    /**
     * Fonction de rappel pour l'évènement scroll
     **/
    prolonge = prolongateur(this);

    /**
     * Continue d'afficher le tableau et met fin, si besoin est
     **/
    continue_table(){
	if (this.lock_prolonge) return;
	else this.lock_prolonge = true;
	$.post(
	    this.url_continue,
	    {
		csrfmiddlewaretoken: getCookie('csrftoken'),
	    },
	    function (afficheur){
		return function(data){
		    afficheur.ajoute_manuels(
			data.manuels, afficheur.table);
		    afficheur.numero.text(parseInt(data.numero)+1);
		    afficheur.table.ready(function(){
			if (data.fini){
			    afficheur.fini = true;
			    afficheur.icon.hide();
			} else {
			    afficheur.fini = false;
			    afficheur.prolonge();
			}
		    });
		    afficheur.lock_prolonge = false;
		};
	    }(this));
    }    
}

/**
 * Initialise l'afficheur de tableau progressif pour la page "/purger_prets"
 **/
function purger_prets_init(){
    var d1 = $("#date1"), d2 = $("#date2");
    var tm = new TableauDeManuels(
	"#date1",
	"#date2",
	"#numero",
	"#total",
	".timericon",
	"#table-retours",
	"#scrollable",
	"/purger_prets_premiers",
	"/purger_prets_suivants",
    ).init_table(true); /* on crée le tableau "forcément" */

}

/**
 * Propose un dialogue pour vérifier si on veut vraiment purger des prêts
 * et si c'est OK, .... Go !
 **/
function purger_prets_go(){
    $("#count").text($("#total").text());
    $("#message-suppression").dialog({
	title: I18N.Attention,
	dialogClass: "alert",
	  buttons: [
	      {
		  text: I18N.Oui,
		  click: function() {
		      $( this ).dialog( "close" );
		      $.post(
			  "/purger_prets_go",
			  {
			      csrfmiddlewaretoken: getCookie('csrftoken'),
			      date1: $("#date1").val(),
			      date2: $("#date2").val(),
			  },
			  function(data){
			      var d = $("<div>").text(data.message).addClass("notif");
			      $("body").append(d);
			      d.dialog({
				  title: data.title,
				  buttons: [
				      {
					  text: I18N.OK,
					  click: function() {
					      $( this ).dialog( "close" );
					      location.replace(location.pathname);
					  }
				      }
				  ]
			      })
			  }
		      );
		  }
	      },
	      {
		  text: I18N.Non,
		  icon: "ui-icon-heart",
		  click: function() {
		      $( this ).dialog( "close" );
		  }
	      },
	  ]

    })
}

/**
 * met en place un temporisateur en attendant l'arrivée des
 * données de sauvegarde
 **/
function waitDB(format){
    /* le ralentissement ne vaut que pour le format JSON */
    if(format != "json") return;
    $("#wait").dialog({
	modal: true,
	buttons: [
	    {
		text: "",
		icon: "ui-icon-heart",
		click: function() {
		    $( this ).dialog( "close" );
		},
		showText: false,
	    }
	],
    });
    setTimeout(function(){$("#wait").dialog("close");}, 20000);
}


/* fonctions pour eleves.html */
function eleve_modif_init(){
    /* on cache le champ a_verifier */
    var a_verifier = $("#id_a_verifier");
    a_verifier.parents("tr").hide();
}

function eleves_init(){
    /* on cache le champ a_verifier */
    var a_verifier = $("#id_a_verifier");
    a_verifier.parents("tr").hide();
    /* On examine s'il y a une valeur dans le champ caché numero */
    if ($("#numero").val()){
	$("#donnees-eleve").show();
	$("#boutons0").hide();
    } else {
	$("#donnees-eleve").hide();
    }
    var classeInput = $("#classe");
    classeInput.autocomplete({
	minLength: 2,
	source: function(request, response){
	    $.ajax({
		url: "/correspondance_classe",
		type: "post",
		data: {
		    term: classeInput.val(),
		    csrfmiddlewaretoken: getCookie('csrftoken')
		},
		dataType: "json",
		success: function(data) {
		    response($.map( data, function( item ) {
			return {
			    label: item,
			    value: item
			}
		    }));
		}
	    });
	},
	select: function (e, ui) {
            if (ui.item.value) {
		affiche_donnees_classe(ui.item.value)
            }
	}
    });
	
    classeInput.on("keydown", function(e){
	if (e.keyCode == 13){
	    e.preventDefault();
	    affiche_donnees_classe(classeInput.val())
	}
    });
    
    var nomprenomInput = $("#nomprenom");
    nomprenomInput.autocomplete({
	minLength: 3,
	source: function(request, response){
	    $.ajax({
		url: "/correspondance_nom",
		type: "post",
		data: {
		    term: nomprenomInput.val(),
		    csrfmiddlewaretoken: getCookie('csrftoken')
		},
		dataType: "json",
		success: function(data) {
		    response($.map( data, function( item ) {
			return {
			    label: item,
			    value: item
			}
		    }));
		}
	    });
	},
	select: function (e, ui) {
            if (ui.item.value) {
		affiche_donnees_eleve(ui.item.value)
            }
	}
    });

    nomprenomInput.on("keydown", function(e){
	if (e.keyCode == 13){
	    e.preventDefault();
	    affiche_donnees_eleve(nomprenomInput.val())
	}
    });
    
}

/**
 * Imprime une carte de membre, dans le contexte de l'URL /eleves
 * quand on s'intéreresse à un élève seul.
 **/
function imprime_une_carte_membre(){
    var nomprenom = $("#nomprenom").val();
    var form = $("<form>").attr({
	action: "/cartes_membres_print",
	method: "post",
    }).css({
	display: "none",
    });
    var i1 = $("<input>").attr({
	name: "nomprenom",
	value: nomprenom,
    });
    var i2 = $("<input>").attr({
	name: "csrfmiddlewaretoken",
	value: getCookie('csrftoken'),
    });
    form.append(i1).append(i2);
    $("body").append(form);
    form.submit();
}

function affiche_donnees_classe(cl){
    /* on évite le "rebond" si la donnée est déjà connue */
    if ($("#classe").attr("data-last") == $("#classe").val()) return;
    /* on active un "anti-rebond" pour cet affichage */
    $("#classe").attr("data-last", $("#classe").val())
    /* on vide le champ voisin */
    $("#nomprenom").val("");
    $("#nomprenom").attr("data-last","");
    /* il ne faut pas envoyer de classe vide */
    if (cl.length == 0) {
	cl = "?";
    }
    $.post(
	"/eleves",
	{
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	    choix_classe: cl,
	},
	function (data){
	    $("#donnees-classe").html(data);
	    $("#donnees-eleve").hide();
	    $("#boutons0").hide();
	    $("#donnees-classe").show();
	});
}

function affiche_donnees_eleve(nomprenom){
    /* on évite le "rebond" si la donnée est déjà connue */
    if ($("#nomprenom").attr("data-last") == $("#nomprenom").val()) return;
    /* on active un "anti-rebond" pour cet affichage */
    $("#nomprenom").attr("data-last", $("#nomprenom").val())
    /* on vide le champ voisin */
    $("#classe").val("");
    $("#classe").attr("data-last","");
    /* il ne faut pas envoyer de nomprenom vide */
    if (nomprenom.length == 0) {
	nomprenom = "?";
    }
    $.post(
	"/eleves",
	{
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	    nomprenom: nomprenom,
	},
	function (data){
	    $("#donnees-eleve").html(data);
	    $("#donnees-eleve").show();
	    $("#donnees-classe").hide();
	    $("#boutons0").hide();
	    $("#form-eleve table input, #form-eleve table select").each(
		function(i, item){
		    $(item).change(
			function(){
			    $("#modif_db").show();
			}
		    )
		}
	    );
	}
    );
}

/**
 * fait apparaître un message temporaire signalant l'enregistrement
 **/
function signale_eleve_saved(){
    $.post(
	"/notifSaveSuccess", {
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	}
    ).done(function(data){
	$(data.html)
	    .insertBefore('#contents')
	    .css({"z-index": 100})
	    .delay(2000)
	    .fadeOut(function() {
		$(this).remove();
	    });
    });
}

/**
 * Replit le champ élève et passe à la suite, quand on clique sur un
 * élève dans une liste de classe de la page /eleves
 **/
function choixEleve(elt){
    var nomprenom = $(elt).text();
    nomprenom = nomprenom.trim();
    setTimeout(function (){
	$("#classe").val("");
	$("#nomprenom").val(nomprenom);
	valide_champ("#nomprenom");
    });
}

/**
 * Ouvre la page des prêts
 * @param nomprenom désigne un élève
 **/
function goto_prets(nomprenom){
    var form = $("<form>").attr({
	action: "/prets",
	method: "post",
    }).css({
	display: "none",
    });
    var input1 = $("<input>").attr({
	name: "csrfmiddlewaretoken",
    }).val(getCookie('csrftoken'));
    var input2 = $("<input>").attr({
	name: "nomprenom",
    }).val(nomprenom);
    form.append(input1).append(input2);
    $("body").append(form);
    form.submit();
}

/**
 * Ouvre la page des retours
 * @param nomprenom désigne un élève
 **/
function goto_retours(nomprenom){
    var form = $("<form>").attr({
	action: "/retours",
	method: "post",
    }).css({
	display: "none",
    });
    var input1 = $("<input>").attr({
	name: "csrfmiddlewaretoken",
    }).val(getCookie('csrftoken'));
    var input2 = $("<input>").attr({
	name: "nomprenom",
    }).val(nomprenom);
    form.append(input1).append(input2);
    $("body").append(form);
    form.submit();
}


/**
 * Ouvre la page des prêts
 * @param nomprenom désigne un élève
 **/
function goto_prets(nomprenom){
    var form = $("<form>").attr({
	action: "/prets",
	method: "post",
    }).css({
	display: "none",
    });
    var input1 = $("<input>").attr({
	name: "csrfmiddlewaretoken",
    }).val(getCookie('csrftoken'));
    var input2 = $("<input>").attr({
	name: "nomprenom",
    }).val(nomprenom);
    form.append(input1).append(input2);
    $("body").append(form);
    form.submit();
}


/**
 * Ouvre la page de création de codes-barres pour un manuel
 * @param numero la clé primaire du manuel dans le catalogue
 **/
function code_barre(numero){
    var form = $("<form>").attr({
	action: "/codes_barres",
	method: "post",
    }).css({
	display: "none",
    });
    var input1 = $("<input>").attr({
	name: "csrfmiddlewaretoken",
    }).val(getCookie('csrftoken'));
    var input2 = $("<input>").attr({
	name: "numero",
    }).val(numero);
    form.append(input1).append(input2);
    $("body").append(form);
    form.submit();    
}

/**
 * Initialisation de la page /codes_barres
 **/
function codes_barres_init(){
    $("#nombre").spinner();
    $("#new button").click( function () {
	$.post(
	    "/button_codes_barres", {
		csrfmiddlewaretoken: getCookie('csrftoken'),
	    }
	).done(function(data){
	    var text = data.text.replace(
		"{n}", $("#nombre").val()
	    ).replace(
		"{t}", $("#title").text()
	    );
	    $("#dialog p").text(text);
	    $("#dialog").dialog({
		title: I18N.Confirm,
		buttons: [
		    {
			text: I18N.Oui,
			click: function() {
			    $( this ).dialog( "close" );
			    $("#new").submit();
			}
		    },
		    {
			text: I18N.Non,
			click: function() {
			    $( this ).dialog( "close" );
			}
		    },
		],
	    });
	});
    });
}

/**
 * Initialisation de la page /codes_barres_print
 **/
function codes_barres_print_init(button){
    /**
     * on consulte la liste des positions tabou pour
     * imprimer la première page
     * @param button permet de vérifier si on vient de faire un clic
     *    sur le bouton de rafaîchissement de la prévisualisation
     **/
    var tabou = JSON.parse($("#positions_tabou").val());
    var svg = $("svg"); // peut être vide, tant que l'appel Ajax est en cours
    if (svg.length) {
	var rect = svg.find("rect");
	rect.each(function(i, item){
	    if($(item).attr("class") == "ko"){
		tabou.push({
		    col: item.dataset.col,
		    row: item.dataset.row,
		})
	    }
	})
    }
    if (button && $(button).attr("id") == "redoButton"){
	/* on a cliqué sur le bouton pour rafraîchir la planche d'étiquettes */
	/* on efface la prévisualisation */
	var preview = $("#preview");
	preview.html("");
	/* et on place l'icône d'attente */
	var img = $("<img>").attr({
	    src: "/static/img/timer1.gif",
	    alt: I18N.Attendre,
	});
	preview.append(img);
    }
    if (button && $(button).attr("id") == "resetButton"){
	/* on a cliqué sur le bouton pour rafraîchir la planche d'étiquettes */
	/* on lève les tabous */
	tabou = [];
	/* on efface la prévisualisation */
	var preview = $("#preview");
	preview.html("");
	/* et on place l'icône d'attente */
	var img = $("<img>").attr({
	    src: "/static/img/timer1.gif",
	    alt: I18N.Attendre,
	});
	preview.append(img);
    }
    $.post(
	"/codes_barres_preview",
	{
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	    id_catalogue: $("#id_catalogue").val(),
	    date: $("#date").val(),
	    tabou: JSON.stringify(tabou),
	},
	function(data){
	    /* enregistre les positions déjà tabou */
	    $("#positions_tabou").val(data.tabou)
	    /* on remplace la prévisualisation */
	    var preview = $("#preview");
	    preview.html(data.msg);
	    /* un fond blanc pour l'image SVG */
	    var div = $("<div>").css({
		display: "inline-block",
		background: "white",
		border: "1px solid navy",
	    });
	    preview.append(div);
	    div.append(data.svg);
	}
    );
}

function toggleLabel(elt){
    var rect = elt.lastElementChild;
    var classe = rect.getAttribute("class")
    var fill;
    var title;
    if (classe == "ok"){
	classe = "ko";
	fill = "rgba(255,200,200,0.6)";
    }else{
	classe = "ok";
	fill = "rgba(200,255,200,0.3)";
    }
    rect.setAttribute("class", classe);
    rect.setAttribute("fill", fill);
}

/**
 * Lance la production des étiquettes au format PDF
 **/
function codes_barres_print_go(){
    var form = $("<form>").attr({
	action: "/codes_barres_print_go",
	method: "post",
    }).css({
	display: "none",
    });
    var input1 = $("<input>").attr({
	type: "text",
	name: "csrfmiddlewaretoken",
    }).val(getCookie('csrftoken'));
    var input2 = $("<input>").attr({
	name: "id_catalogue",
    }).val($("#id_catalogue").val());
    var input3 = $("<input>").attr({
	name: "date",
    }).val($("#date").val());
    var input4 =  $("<input>").attr({
	name: "tabou",
    }).val($("#positions_tabou").val());
    form.append(input1).append(input2).append(input3).append(input4);
    $("body").append(form);
    form.submit();
    form.remove();
}

/**
 * Imprime la fiche de retour de livres
 **/
function imprime_restitution(){
     var form = $("<form>").attr({
	action: "/imprime_restitution",
	method: "post",
    }).css({
	display: "none",
    });
    var liste_manuels = [];
    $("td.id_manuel").each(function(index, elt){
	liste_manuels.push($(elt).text());
    });
    liste_manuels = JSON.stringify(liste_manuels);
    var input1 = $("<input>").attr({
	type: "text",
	name: "csrfmiddlewaretoken",
    }).val(getCookie('csrftoken'));
    var input2 = $("<input>").attr({
	name: "nomprenom",
    }).val($("#nomprenom").val());
    var input3 = $("<input>").attr({
	name: "liste_manuels",
    }).val(liste_manuels);
    form.append(input1).append(input2).append(input3);
    $("body").append(form);
    form.submit();
    form.remove();
   
}

/**
 * Imprime la fiche de mémoire pour pertes
 **/
function imprime_memoire(){
     var form = $("<form>").attr({
	action: "/imprime_memoire",
	method: "post",
    }).css({
	display: "none",
    });
    var liste_manuels = [];
    $("td.id_manuel").each(function(index, elt){
	liste_manuels.push($(elt).text());
    });
    liste_manuels = JSON.stringify(liste_manuels);
    var input1 = $("<input>").attr({
	type: "text",
	name: "csrfmiddlewaretoken",
    }).val(getCookie('csrftoken'));
    var input2 = $("<input>").attr({
	name: "nomprenom",
    }).val($("#nomprenom").val());
    var input3 = $("<input>").attr({
	name: "liste_manuels",
    }).val(liste_manuels);
    form.append(input1).append(input2).append(input3);
    $("body").append(form);
    form.submit();
    form.remove();
   
}


/**
 * Initialisation pour la page /retours
 * @param nomprenom : si ce paramètre n'est pas une chaîne vide,
 *     on passe tout de suite à l'affichage du tableau
 * @param livre : si ce paramètre n'est pas vide, on gère le rendu du livre
 *     défini par ce code, par l'élève défini par nomprenom
 **/
function retours_init(nomprenom, livre){
    var nomprenomInput = $("#nomprenom");
    nomprenomInput.autocomplete({
	minLength: 3,
	source: function(request, response){
	    $.ajax({
		url: "/correspondance_nom",
		type: "post",
		data: {
		    term: nomprenomInput.val(),
		    csrfmiddlewaretoken: getCookie('csrftoken')
		},
		dataType: "json",
		success: function(data) {
		    response($.map( data, function( item ) {
			return {
			    label: item,
			    value: item
			}
		    }));
		}
	    });
	},
	select: function (e, ui) {
            if (ui.item.value) {
		affiche_tableau_retours(ui.item.value);
		$("#codelivre").focus();
            }
	}
    });

    nomprenomInput.on("keydown", function(e){
	if (e.keyCode == 13){
	    e.preventDefault();
	    affiche_tableau_retours(nomprenomInput.val())
	    $("#codelivre").focus();
	}
    });
    
    if (nomprenom) {
	affiche_tableau_retours(nomprenom, livre);
	$("#codelivre").focus();
    }

    var codeLivreInput = $("#codelivre");
    codeLivreInput.on("keydown", function(e){
	if (e.keyCode == 13){
	    e.preventDefault();
	    // on démarre le rendu de nouveau livre
	    rend_livre(nomprenomInput.val(), codeLivreInput.val());
	    // on évite que le code du livre reste actif
	    codeLivreInput.attr({placeholder: codeLivreInput.val()});
	    codeLivreInput.val("");
	}
    });
    /**
     * Si dès le début de l'affichage de la page, il y a un nom, alors
     * c'est qu'il s'agit d'un reload : on peut afficher le tableau des retours
     **/
    if(nomprenomInput.val()){
	affiche_tableau_retours(nomprenomInput.val())
    }

    // s'il n'y a pas de données dans le tableau des prêts on ne le montre pas
    if ($("tr.manuel").length) {
	$("#livres-rendus").fadeIn();
    } else {
	$("#livres-rendus").hide();
    }
}

/**
 * Examine si tous les livres ont été soit rendus soit perdus ;
 * quand c'est le cas, on rend visible $("#impressions"), sinon on le cache
 * @param force_hide si ce paramètre optionnel est vrai on cache les boutons et
 *        le tableau de livres rendus
 **/
function desactive_impressions(force_hide){
    if (force_hide) {
	$("#impressions").fadeOut();
	$("#livres-rendus").fadeOut();
	$("#rend-caution").fadeOut();
	return
    }
    var nlivres = $("tr.manuel").length;
    var nperdus = $("tr.perdu").length;
    var npayes = $("tr.perdu button.paiement-fait").length;
    var nrendus = $("tr.rendu").length;
    if (nlivres != nperdus + nrendus){
	$("#impressions").fadeOut();
	$("#rend-caution").fadeOut();
    } else {
	$("#impressions").fadeIn()
	$("#rend-caution").fadeIn();
	if (nperdus-npayes > 0){
	    $("#fiche_memoire").removeClass("hidden");
	    $("#tout_rendu").addClass("hidden");
	}
	else {
	    $("#fiche_memoire").addClass("hidden");
	    $("#tout_rendu").removeClass("hidden");
	}
    }
}

/**
 * Affiche la liste des livres prêtés/rendus/perdus/payés pour un élève
 * @param nomprenom le nom et le prénom de l'élève
 * @param livre un livre éventuellement déjà rendu à l'instant (par un tiers)
 **/
function affiche_tableau_retours(nomprenom, livre){
    if (! nomprenom.length) return
    $.post(
	"/tableau_retours",
	{
	    nomprenom: nomprenom,
	    csrfmiddlewaretoken: getCookie('csrftoken')
	},
	function(data){
	    $("#message").text(data.message)
	    $("#message").data("id", data.ident)
	    // laisser le temps d'afficher le code HTML qui peut
	    // contenir un message, avant de traiter ce message
	    setTimeout(show_message, 0)
	    if (data.msg.length > 0){
		$("#impressions").fadeOut();
		$("#livres-rendus").fadeOut();
		$("#messages").html("<p>"+data.msg+"</p>");
		$("#messages").dialog({title: data.title_error});
		return
	    }
	    $("#livres-rendus").fadeIn();
	    // on peuple le tableau dans #meme-classe
	    if(data.classe)
		affiche_meme_classe_pour_retours(data.classe, data.nomprenom);
	    // on efface les lignes obsolètes
	    $("#livres-rendus tr.manuel").remove();
	    // on place les nouvelles lignes
	    var tbody = $("#livres-rendus tbody");
	    data.lignes.forEach(function(html){
		    tbody.append($(html));
	    });
	    ajuste_etat_final(); // retouche la colonne de l'état final
	    cache_colonnes() ;   // cache certaines colonnes
	    $("#livres-rendus").fadeIn();
	    desactive_impressions();
	    /* si un livre vient d'être rendu par un tiers, on gère ça. */
	    if (livre){
		var codeLivreInput = $("#codelivre");
		var nomprenomInput = $("#nomprenom");
		codeLivreInput.val(livre);
		rend_livre(nomprenomInput.val(), codeLivreInput.val());
		// on évite que le code du livre reste actif
		codeLivreInput.attr({placeholder: codeLivreInput.val()});
		codeLivreInput.val("");
	    }
	},
    )
}

/**
 * ajuste la colonne de l'état final en tenant compte de l'état initial
 * si l'état initial était "Neuf", l'état final sera "Bon" par défaut ;
 * dans tous les autres cas, sa valeur par défaut est celle de l'état initial.
 * @param rows si des lignes sont déjà définies on traite celles-là et
 * pas toutes les lignes possibles
 **/
function ajuste_etat_final(rows){
    if (rows == undefined) rows = $("#livres-rendus tr.manuel");
    rows.each(function(i, row){
	row = $(row);
	// aligne l'état final sur l'état initial sauf s'il était neuf
	var etat_initial = row.find("td.etat-initial").text().trim();
	var sel = row.find("td.etat-final select")
	if (etat_initial == I18N.Neuf && sel.val() == I18N.Neuf){
	    sel.val(I18N.Bon);
	}
	var perdu = row.find("td.perdu input").prop("checked");
	if (perdu){
	    sel.val(I18N.Autre);
	    sel.prop('disabled', true);
	}
    });
    // fait entendre une icône sonore
    document.querySelector("#soundicon2").play()
}

/**
 * fonction de rappel pour le bouton qui propose de payer les
 * livres perdus dans le tableau des livres prêtés
 **/
function rembourser(elt){
    var msgdiv = $("#messages");
    $.post("/rembourser", {
	pret: $(elt).data("pret"),
	csrfmiddlewaretoken: getCookie('csrftoken'),
    }, function(data){
	msgdiv.html(data.html);
	msgdiv.dialog({
	    title: data.title,
	    width: 500,
	    modal: true,
	    buttons: [
		{
		    text: I18N.Non,
		    click: function() { $( this ).dialog( "close" ); }
		},
		{
		    text: I18N.Oui,
		    click: function() {
			$( this ).dialog( "close" );
			setTimeout(
			    function(){
				rembourser_go(
				    $(elt).data("pret"),
				    $("#paiement input:checked").val()
				);
			    },
			    0
			);
		    }
		},
	    ],

	});
    })
}

/**
 * Fonction rappelée quand on valide le dialogue au sujet du remboursement.
 * @param pret le numéro d'un prêt
 * @param mode un entier, qui est une clé de models.DICT_MODES_PAIEMENT_CHOIX
 **/
function rembourser_go(pret, mode){
    $.post("/rembourser_go", {
	pret: pret,
	mode: mode,
	csrfmiddlewaretoken: getCookie('csrftoken'),
    }, function(data){
	affiche_tableau_retours($("#nomprenom").val());
    });
}

/**
 * Cache les colonnes inutilisées, pour lesquelles chacune des lignes
 * sans exception possède un attribut "hidden" ... dans un tel cas
 * on cache aussi le titre de la colonne ; cependant, si une seule ligne
 * est dépourvue de l'attribut "hidden", alors on retire cete attribut
 * à toutes les autres lignes de la colonne.
 **/
function cache_colonnes(){
    // on ne fait rien si le tableau n'a aucune ligne de données !
    if (!$("tr.manuel")) return;
    var pas_traite = !($("td.traite").length);
    if (pas_traite){
	// on récupère la liste de classes des colonnes
	var classes_des_colonnes = Array.from (
	    $("tr.titres th"), elt => $(elt).attr("class"));
	/**
	 * si chaque ligne d'une colonne est hidden,
	 * on cache le titre aussi ; sinon on montre tout
	 **/
	for(var i=0; i < classes_des_colonnes.length; i++ ){
	    var cl = classes_des_colonnes[i];
	    // pour chacune des classes ...
	    // on considère la colonne (sans le titre) avec cette classe
	    var col = $("tr.manuel td."+cl);
	    if (col.length){
		col.addClass("traite"); // on marque "traitée" cette colonne
		var colhidden = $("tr.manuel td."+cl+".hidden");
		// on considère le titre de la colonne avec cette classe
		var h = $("tr.titres th."+cl);
		if (colhidden.length == col.length){
		    col.addClass("traite");
		    h.addClass("hidden");
		} else {
		    col.removeClass("hidden").addClass("traite");
		    h.removeClass("hidden")
		}
	    }

	};
    }
}

/**
 * fonction de rappel pour rendre un livre ;
 * si le code est bien celui d'un livre prêté à l'élève, ça effectue
 * le retour ; sinon ça déclenche une alerte
 **/
function rend_livre(nomprenom, code){
    $.post(
	"/rend_livre",
	{
	    nomprenom: nomprenom,
	    code: code,
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	},
	function(data){
	    if (data.rendu){
		// met la ligne de prêt en vert
		// le parseInt élimine les zéros au début du code
		var ligne_pret = $("tr[data-code='"+ parseInt(data.code)+"']");
		var nouvelle_ligne = $(data.ligne);
		ligne_pret.after(nouvelle_ligne);
		ligne_pret.remove();
		ajuste_etat_final(nouvelle_ligne);
		desactive_impressions();
	    } else {
		// il y a une faute ; si le paramètre eleve est défini
		// alors le livre lui était prêté, sinon c'est qu'il
		// est quelque part en stock
		if (! data.manuel){
		    alerteCodeInconnu(data);
		} else if (data.nomprenom){
		    alerteLivreDunTiers(data);
		} else {
		    alerteLivreNonPrete(data);
		}
	    }
	}
    );
}

/**
 * Affiche une alerte au sujet d'un code de livre inconnu
 * @param data une structure de donnée dont le champ "code" est utilisable.
 **/
function alerteCodeInconnu(data){
    document.querySelector("#soundicon1").play()
    var messages = $("#messages");
    messages.html("");
    messages.append($("<p>").text(
	$("#alerte-code").val().replace("{c}", data.code)
    ));
    messages.dialog({
	title: $("#alerte-code").data("title"),
	modal: true,
	width: "400px",
    });}

/**
 * Affiche une alerte au sujet du livre d'une tierce personne, dont le code
 * est donné quand on effectue les retours de livres d'un élève
 * @param data une structure de donnée dont les champs "nomprenom", "code" et
 *    "manuel" sont utilisables.
 **/
function alerteLivreDunTiers(data){
    document.querySelector("#soundicon1").play();
    var messages = $("#messages");
    messages.html("");
    var alerte = $("#alerte-tiers").data("rendre")
    if (! alerte) return
    alerte = alerte.
	replace("{c}", data.code).
	replace("{m}", data.manuel).
	replace("{e}", data.nomprenom)
    if (data.est_paye){
	alerte += $("#alerte-tiers").data("paye").
	replace("{p}", data.paye).
	replace("{e}", data.nomprenom).
	replace("{d}", data.date_paiement)
    } else {
	alerte += $("#alerte-tiers").data("onglets").
	    replace("{e}",data.nomprenom)
    }
    messages.append($("<p>").html(alerte));
    var buttons = [
	{
	    text: "Ok",
	    icon: "ui-icon-heart",
	    click: function() {
		$( this ).dialog( "close" );
	    }
	}
    ]
    if (!data.est_paye){ // le livre est prêté (déclaré perdu ou non)
	buttons = [
	    {
		text: I18N.Non,
		click: function() {
		    rendre_livre_tiers({
			code: data.code,
			nomprenom: data.nomprenom,
			onglet: false,
		    });
		    $( this ).dialog( "close" );
		}
	    },
	    {
		text: I18N.Oui,
		click: function() {
		    rendre_livre_tiers({
			code: data.code,
			nomprenom: data.nomprenom,
			onglet: true,
		    });
		    $( this ).dialog( "close" );
		}
	    }
	]
    }
    
    messages.dialog({
	title: $("#alerte-tiers").data("title"),
	modal: true,
	width: "400px",
	buttons: buttons,
    })
}

/**
 * fonction de rappel pour rendre un livre appartenant à un élève tiers.
 * @param obj un objet avec les propriété code, nomprenom et onglet ;
 *            code désigne un livre, nomprenom un eleve ; si onglet est vrai
 *            on crée un nouvel onglet
 **/

function rendre_livre_tiers(obj){
    var f = $("#livre_tiers");
    $("#nomprenom_cache").val(obj.nomprenom);
    $("#codelivre_cache").val(obj.code);
    /* quand il ne faut pas d'onglet on supprime l'attribut target="_blank" */
    if (!obj.onglet){
	f.removeAttr("target");
    }
    f.submit();
}

/**
 * Affiche une alerte au sujet d'un livre connu, mais en prêt.
 * @param data une structure de donnée dont les champs "code" et
 *    "manuel" sont utilisables.
 **/
function alerteLivreNonPrete(data){
    var rendus = $("tr.rendu>td.id_manuel>div")
    var rendus_ids = []
    rendus.each((i,div) => rendus_ids.push($(div).text().trim()))
    var messages = $("#messages");
    messages.html("");
    
    if (rendus_ids.indexOf(data.code) >= 0) {
	/* le livre est à l'écran, parmi les lignes en vert (déjà rendus) */
	messages.append($("<p>").text(
	    $("#alerte-nonprete").data("dejarendu").
		replace("{c}", data.code)
	))
	} else {
	    /* le livre n'est pas encore rendu */
	    messages.append($("<p>").text(
		$("#alerte-nonprete").val().
		    replace("{c}", data.code).
		    replace("{m}", data.manuel)
	    ))
    }
    messages.dialog({
	title: $("#alerte-nonprete").data("title"),
	modal: true,
	width: "400px",
    });
    document.querySelector("#soundicon1").play()
}

/**
 * force le retour d'un livre à une date donnée
 * param args les arguments, un objet avec les propriétés code : code du livre
 *            et date : date à laquelle il était rendu
 **/
function force_retour(obj){
    rend_livre($("#nomprenom").val(), obj.code);
}

/**
 * Annule le retour d'un livre à une date donnée
 * @param obj un objet avec les propriétés code : le code-barre d'un manuel et
 date : la date à laquelle il était rendu
 **/

function annule_retour(obj){
    $.post(
	"/annule_retour",
	{
	    code: obj.code,
	    date: obj.date,
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	},
	function(data){
	    if (data.ok){
		var ligne = $("tr[data-code='"+parseInt(data.code)+"']");
		ligne.removeClass(["rendu", "degrade", "perdu"]);
		ligne.find("td.date-retour").html("");
		desactive_impressions();
	    }
	}
    );
}

/**
 * Changement de l'état final d'un manuel rendu
 * @param elt l'élément DOM où a eu lieu un évènement
 * @param code la clé du manuel dans la table d'inventaire
 **/
function change_etat_manuel_rendu(elt, code){
    var code_etat = elt.selectedOptions[0].getAttribute("val");
    date = $(elt).data("date");
    if (date.length != 10){
	date = "000" + date;
    }
    $.post(
	"/change_etat_manuel_rendu",
	{
	    code: code,
	    date: date,
	    code_etat: code_etat,
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	}, function(data){
	    if (data.ok){
		var ligne_pret = $("tr[data-code='"+parseInt(data.code)+"']");
		var nouvelle_ligne = $(data.ligne);
		nouvelle_ligne.find("select").val(data.etat);
		ligne_pret.after(nouvelle_ligne);
		ligne_pret.remove();
	    }
	}
    );
}

/**
 * Un manuel est déclaré perdu : on traite cette information
 * @param elt l'élément DOM où a eu lieu un évènement
 * @param code la clé du manuel dans la table d'inventaire
 **/
function traite_perte(elt, code){
    var perte = $(elt).prop("checked")
    $.post(
	"/traite_perte",
	{
	    perte: perte,
	    code: code,
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	},
	function(data){
	    var ligne_pret = $("tr[data-code='"+parseInt(data.code)+"']");
	    var nouvelle_ligne = $(data.ligne);
	    nouvelle_ligne.find("select").val(data.etat)
	    ligne_pret.after(nouvelle_ligne);
	    ligne_pret.remove();
	    ajuste_etat_final();
	    desactive_impressions();
	}
    );
}

/**
 * Une classe pour afficher des références de manuels dans un widget
 * scrollable
 **/
class AfficheurDeleves{
    /**
     * Conctructeur de la classe AfficheurDeleves
     * @param sel_input sélecteur du champ pour choisir une classe
     * @param sel_partiel sélecteur de l'afficheur du compte d'élèves
     *    déjà affichés
     * @param sel_total sélecteur de l'afficheur du total d'élèves
     * @param sel_icon sélecteur de l'icône d'attente
     * @param sel_collection sélecteur de la collection de widgets d'élèves
     * @param sel_scrollable sélecteur du DIV scrollable
     * @param url_init l'URL d'une requête AJAX pour commencer la collection
     * @param url_continue l'URL d'une requête AJAX pour continuer la collection
     **/
    constructor(
	sel_input,
	sel_partiel,
	sel_total,
	sel_icon,
	sel_collection,
	sel_scrollable,
	url_init,
	url_continue,
    ){
	/* enregistrement de paramètres pour plus tard */
	this.input = $(sel_input);           // elt jQuery : choix de manuel
	this.partiel = $(sel_partiel);       // elt jQuery : compte les manuels
	this.total = $(sel_total);           // elt jQuery : total des manuels
	this.icon = $(sel_icon);             //  elt jQuery : l'icone
	this.collection = $(sel_collection); //  elt jQuery : la collection
	this.scrollable = $(sel_scrollable); // elt jQuery : DIV scrollable
	this.url_init = url_init;            // URL Ajax pour initialiser
	this.url_continue = url_continue;    // URL Ajax pour continuer

	/* ici c'est l'initialisation proprement dite */
	this.lock_prolonge = false;
	this.fini = false; // booléen : le scrollage n'est plus nécessaire ?
	this.scrollable.scroll(function(afficheur){
	    return function(event){
		afficheur.prolonge()
	    }
	}(this));
	this.init_autocomplete(this)
    }

    /**
     * Initialise le champ de saisie du titre de livre afin qu'il
     * permette l'autocomplétion.
     * @param afficheur sert à contourner l'usage de this dans les
     *   gestionnaires d'évènement
     **/
    init_autocomplete(afficheur){
	
	/**
	 * Renouvelle l'affichage dans la collection d'élèves
	 * mais seulement si la valeur d'input diffère de son
	 * attribut "data-last"
	 * param val la valeur de titre à utiliser pour peupler la
	 *    collection; si val n'est pas défini, le titre est lu depuis
	 *    afficheur.input
	 **/
	function renew(val){
	    if (val == undefined) val = afficheur.input.val();
	    if (val != afficheur.input.attr("data-last")){
		afficheur.input.attr("data-last", val);
		afficheur.init_collection(val);
	    }
	}
	
	afficheur.input
	    .attr({"data-last": ""})
	    .on("keydown", function(e){
		if (e.keyCode == 13){
		    e.preventDefault();
		    renew();
		}
	    })
	    .on("blur", function(e){
		renew();
	    })
	    .autocomplete({
		minLength: 2,
		source: function(request, response){
		    $.post(
			"/correspondance_classe",
			{
			    term: afficheur.input.val(),
			    csrfmiddlewaretoken: getCookie('csrftoken')
			},
			function(data) {
			    response($.map( data, function( item ) {
				return {
				    label: item,
				    value: item,
				}
			    }));
			}
		    )},
		select: function (e, ui) {
		    if (ui.item.value) {
			/* modifie l'affichage des élèves */
			renew(ui.item.value)
			/* remet le label dans l'input */
			setTimeout(function(){
			    afficheur.input.val(ui.item.label);
			});
		    }
		}
	    });
    }


	
    /**
     * Initialise le contenu de la collection d'élèves visibles
     *
     * @param id_classe éventuellement défini ; s'il est numérique
     *    c'est une clé primaire de classe
     **/
    init_collection(id_classe){
	if (id_classe == undefined) id_classe = ""
	this.icon.show();
	$.post(
	    this.url_init,
	    {
		csrfmiddlewaretoken: getCookie('csrftoken'),
		id_classe: id_classe,
	    },
	    function(afficheur){
		return function (data){
		    afficheur.collection.html(""); // r.à.z.
		    afficheur.ajoute_eleves(
			data.eleves, afficheur.collection);
		    afficheur.total.text(data.total);
		    afficheur.numero_a_jour(data);
		    afficheur.collection.ready(function(){
			if (data.fini){
			    afficheur.fini = true;
			    afficheur.icon.hide();
			} else {
			    afficheur.fini = false;
			    afficheur.prolonge();
			}
		    });
		}
	    }(this)
	);
    }

    /**
     * Ajoute une série d'élèves à la fin de l'afficheur
     * @param eleves données des élèves
     * @param collection l'élément jQuery où on les met
     **/
    ajoute_eleves(eleves, collection){
	eleves.forEach(function(elt){
	    var id = elt[0], nomprenom = elt[1], pretes = elt[2];
	    var div = $("<div>")
		.addClass("flexmini linkcursor")
		.attr({"data-id": id})
	    var p = $("<p>")
		.attr({
		    title: $("#ade").data("savoirplus"),
		    "data-id": id,
		    "data-nomprenom": nomprenom,
		})
		.text($("#ade").data("nompretes")
		      .replace("{e}", nomprenom)
		      .replace("{p}", pretes))
		.on("click", function(e){
		    // on va ouvrir la page /retours avec le bon argument
		    var nomprenom = $(e.target).attr("data-nomprenom");
		    var form = $("<form>")
			.attr({
			    method: "post",
			    action: "/retours",
			    style: "display:none",
			    target: "blank",
			})
			.append($("<input>").attr({
			    name: "csrfmiddlewaretoken",
			    value: getCookie('csrftoken'),
			}))
			.append($("<input>").attr({
			    name: "nomprenom",
			    value: nomprenom,
			}));
		    $("body").append(form);
		    form.submit();
		});
	    div.append(p);
	    collection.append(div);
	});

    }

    /**
     * Fonction de rappel pour l'évènement scroll
     **/
    prolonge = prolongateur(this);
    
    /**
     * Continue d'afficher une collection et met fin, si besoin est
     **/
    continue_collection(){
	if (this.lock_prolonge) return;
	else this.lock_prolonge = true;
	$.post(
	    this.url_continue,
	    {
		csrfmiddlewaretoken: getCookie('csrftoken'),
	    },
	    function (afficheur){
		return function(data){
		    afficheur.ajoute_eleves(
			data.eleves, afficheur.collection);
		    afficheur.numero_a_jour(data);
		    afficheur.collection.ready(function(){
			if (data.fini){
			    afficheur.fini = true;
			    afficheur.icon.hide();
			} else {
			    afficheur.fini = false;
			    afficheur.prolonge();
			}
		    });
		    afficheur.lock_prolonge = false;
		};
	    }(this));
    }

    /**
     * Met à jour le nombre d'élèves affichés
     **/
    numero_a_jour(data){
	var numero = parseInt(data.numero);
	if (numero){
	    if (data.fini) {
		this.partiel.text(this.total.text());
	    } else {
		this.partiel.text(numero);
	    }
	}
    }
}

function delMessages(id){
    $( "#dialog" ).dialog({
	title: I18N.Attention,
	buttons: [
	    {
		text: I18N.Oui,
		click: function() {
		    $( this ).dialog( "close" );
		    delMessages_ok(id);
		}
	    },
	    {
		text: I18N.Non,
		click: function() {
		    $( this ).dialog( "close" );
		}
	    },
	]
    });
}

function delMessages_ok(id){
    $.post(
	"/messages_del",
	{
	    numero: id,
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	}
    ).done(
	function (data) {
	    location.reload();
	}
    );
}

/**
 * imprime les fiches de suivi
 * @param data (optionnel) ; s'il est défini, une seule classe est considérée
 *             et s'il est indéfini, on prend toutes les classes du tableau ;
 *             ou encore si c'est une chaîne qui commence par "eleve=", ça
 *             imprimera la fiche de suivi d'un seul élève
 **/
function fiches_suivi_print_go(data){
    if (typeof(data) == "undefined") data = "";
    var f = $("#go");
    /* vérification de la présence d'un seul élève ? */
    /* si c'est le cas, on revoie une liste avec un entier (son numéro) */
    if (data.startsWith("eleve=")){
	var numero_annee = data.replace("eleve=", "").split(",");
	var id_eleve = parseInt(numero_annee[0]);
	var annee = numero_annee[1];
	$("#classes_input").val(JSON.stringify([id_eleve]));
	$("#annee").val(annee);
	f.submit();
	return
    }
    /* récupération de la liste des classes */
    var classes = [];
    if (data.length) {
	classes.push(data);
    } else {
	$("#tableau_classes .libelle").each(function(i, elt){
	    classes.push($(elt).text().trim())
	});
    }
    $("#classes_input").val(JSON.stringify(classes));
    f.submit();
}

function newMessages(){
    editMessages(-1);
}
function editMessages(id){
    $.redirectPost("/messages_modif",
		   {
		       numero: id,
		       csrfmiddlewaretoken: getCookie('csrftoken'),
		   });
}

function cloneMessages(id){
    $.redirectPost("/messages_modif",
		   {
		       numero: id,
		       clonage: true,
		       csrfmiddlewaretoken: getCookie('csrftoken'),
		   });
}


function logout(href){
    location.replace("/admin/logout/?next="+href);
}

/**
 * Ouverture d'une page d'aide contextuelle dans un autre onglet
 * @param context peut être "True" auquel cas on va au sommaire de l'aide ;
     sinon c'est une chaîne qui permet de contruire l'URL d'accès à l'aide
 * @param lang facultatif, le choix de la locale ('fr-fr' par défaut)
 * @param choice un choix de locales possibles ; on conserve celui qui
 *   correspond au plus près à lang
 **/
function aide(context, lang, choice){
    if (lang === undefined) lang="fr-fr";
    if (choice === undefined) choice = ["fr-fr", "en-us", "es-es"]
    choice.forEach(function(elt){
	if (elt.search(lang) >= 0){
	    lang = elt;
	}
    });
    if (context == "True" || context == ""){
	// aide par défaut
	window.open(location.origin + "/aide/" + lang + "/index.html", '_blank');
    } else {
	// aide contextuelle
	var m = context.match(/(.*)(#.*)/);
	if (m){
	    var page = m[1], anchor = m[2];
	    window.open(location.origin + "/aide/" + lang +
			"/pages/" + page + "/index.html" + anchor, '_aide');
	} else {
	    window.open(location.origin + "/aide/" + lang +
			"/pages/" + context + "/index.html", '_aide');
	}
    }
}

/**
 * fonction utile à l'URL /stats
 * affiche les élèves d'une classe
 * @param name le nom de la classe
 **/
function showClasse(name){
    $.get("/classe", {
	c: name,
    }, function(data){
	debug = data;
	$("#la_classe").html(data.data).fadeIn();
    })
}

/**
 * initialisations pour la page /importeFromGemaSCO
 **/
function importeFromGemaSCO_init(){
    // on inhibe les évènements drop et dragover
    window.addEventListener("dragover",function(e){
	e.preventDefault();
    },false);
    window.addEventListener("drop",function(e){
	e.preventDefault();
    },false);
    // on marque comme éligibles les modèles ayant déjà leurs dépendances
    marque_eligibles();
}

function marque_eligibles(){
    var depends = [];
    $(".model").each(function(i, item){
	item=$(item);
	var eligible=true;
	var model = (item.attr("data-model"));
	var related = JSON.parse(item.attr("data-related"));
	related.forEach(function(r){
	    r = $(".model[data-model='" + r + "']");
	    if (! r.hasClass("traite")){
		// une dépendance n'est pas traitée : l'item n'est pas éligible
		eligible=false;
	    }
	});
	if (eligible){
	    item.addClass("eligible");
	}
    });
}

/**
 * Fonction de rappel pour le signal drop, dans la page /importeFromGemaSCO
 **/
function dropCSV(ev){
    ev.preventDefault();
    for (var i =0; i < ev.dataTransfer.files.length; i++){
	var file = ev.dataTransfer.files[i];
	var item = ev.dataTransfer.items[i];
	if (item.kind == "file" && item.type == "text/csv"){
	    var target = $(ev.target)
	    target.removeClass("hoveredByCSV");
	    droppedCSV(target, file);
	}
    }
}

/**
 * traitement d'un drop déjà un peu filtré
 **/
async function droppedCSV(target, file){
    var target = $(target);
    var modele = target.attr("data-model");
    if (!target.hasClass("traite") && target.hasClass("eligible") && target.find(".csvfile").text().trim() == file.name){
	var data = new TextDecoder('utf-8').decode(
	    await file.arrayBuffer());
	importeCSV(modele, data, function(data){
	    if (data.ok){
		target.addClass("traite");
		marque_eligibles();
	    }
	    showMessageCSV(data.ok, data.message);
	});
    } else {
	blinkError(target);
    }
}

/**
 * affiche un message dans la page /importeFromGemaSCO
 **/
function showMessageCSV(ok, message){
    var title = I18N.OK;
    if (!ok) title = I18N.ERREUR;
    var p = $("<p>").text(message);
    $("#message").html("").append(p);
    $("#message").dialog({
	title: title,
    })
}

/**
 * Met temporairement le fond en rouge (voir le fichier CSS)
 **/
function blinkError(target){
    target.addClass("erreur");
    setTimeout(function(){target.removeClass("erreur");}, 500);
}

function dragenterCSV(ev){
    for (var i =0; i < ev.dataTransfer.items.length; i++){
	var item = ev.dataTransfer.items[i];
	if (item.kind == "file" && item.type == "text/csv"){
	    $(ev.target).addClass("hoveredByCSV");
	}
    }
}

function dragleaveCSV(ev){
    for (var i =0; i < ev.dataTransfer.items.length; i++){
	var item = ev.dataTransfer.items[i];
	if (item.kind == "file" && item.type == "text/csv"){
	    $(ev.target).removeClass("hoveredByCSV");
	}
    }
}

/**
 * provoque l'effacement des données d'un modèle
 * @param modele le nom du modèle, ou une liste de noms de modèle
 **/
function RAZmodele(modele){
    $.post("/RAZmodele", {
	csrfmiddlewaretoken: getCookie('csrftoken'),
	modele: JSON.stringify(modele),
    }, function(data){
	data.modeles.forEach(function(modele){
	    var div = $(".model[data-model='"+modele+"']");
	    div.removeClass("traite");
	});
	marque_eligibles()
    });
}

/**
 * importe les données d'un modèle à partir de données CSV
 * @param modele le nom du modèle
 * @param csv les données csv
 * param cb une fonction de rappel avec un paramètre data
 **/
function importeCSV(modele, csv, cb){
    $.post(
	"/importeCSV", {
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	    modele: modele,
	    csv: csv,
	},
	cb
    );
}

/***************** pour l'url /cartes ******************/

function cartes_init(){
    $("#classes").selectable({
	selected: totalMembres,
	unselected: totalMembres,
    });
}

/**
 * Met à jour le nombre de cartes imprimables après chaque nouvelle
 * sélection/désélection et montre le bouton d'impression quand il est utile.
 **/
function totalMembres(event, ui){
    var total = 0;
    $("#classes li").each(
	function(n,elt){
	    elt= $(elt);
	    if (elt.hasClass("ui-selected")){
		total += parseInt(elt.attr("data-n"));
	    }
	}
    )
    $("#total").text(total);
    if (total>0){
	$("#imprimables").fadeIn();
	$("#verso").fadeOut();
    } else {
	$("#verso").fadeIn();
	$("#imprimables").fadeOut();
    }
}

/**
 * Prépare l'impression des cartes de membre pour les classes sélectionnées
 **/
function imprime_cartes(){
    var form = $("#print_form");
    var i = $("#classes_chosen");
    var chosen = [];
    $(".ui-selected").each(
	function(i, elt){
	    chosen.push($(elt).text());
	}
    );
    i.val(JSON.stringify(chosen));
    form.submit();
}

function imprime_cartes_verso(){
    location="/cartes_membres_print_verso";
}

/**
 * Imprime la fiche de suivi pour l'élève qui est en cours d'édition, dans
 * la sous page générée par le modèle eleve_modif.html
 **/
function imprime_fiche_suivi(){
    var nom = $("#id_Nom_de_famille").val();
    var prenom = $("#id_Prenom").val();
    var numero = $("#numero").val();
    fiches_suivi_print_go("eleve="+numero);
}

/**
 * Prépare la fiche de suivi pour l'élève qui est en cours d'édition,
 * à partir de la sous page générée par le modèle eleve_modif.html
 **/
function prepare_fiche_suivi(){
    var url = "/prepare_fiche_suivi"
    var data = {
	nom: $("#id_Nom_de_famille").val(),
	prenom: $("#id_Prenom").val(),
	numero: $("#numero").val(),
	csrfmiddlewaretoken: getCookie('csrftoken'),
    }
    var form = '';
        $.each( data, function( key, value ) {
            form += '<input type="hidden" name="'+key+'" value="'+value+'">';
        });
        $('<form action="'+url+'" method="POST">'+form+'</form>').appendTo('body').submit();
}

/* ---------------- fonctions pour siecle.html ----------------- */

/**
 * initialisation de la page
 **/
function siecle_init(){
    // on inhibe les évènements drop et dragover
    window.addEventListener("dragover",function(e){
	e.preventDefault();
    },false);
    window.addEventListener("drop",function(e){
	e.preventDefault();
    },false);
}

/**
 * survol par un tirer-glisser (pour les fichiers siecle)
 **/
function dragenterSiecle(ev){
    for (var i =0; i < ev.dataTransfer.items.length; i++){
	var item = ev.dataTransfer.items[i];
	if (item.kind == "file" && item.type == "text/csv"){
	    $(ev.target).addClass("hoveredByCSV");
	}
    }
}
function dragleaveSiecle(ev){
    $(ev.target).removeClass("hoveredByCSV");
}

/**
 * dépose d'un fichier CSV sur la zone d'atterrissage de siecle.html
 **/
function dropSiecle(ev){
    ev.preventDefault();
    for (var i =0; i < ev.dataTransfer.files.length; i++){
	var file = ev.dataTransfer.files[i];
	var item = ev.dataTransfer.items[i];
	if (item.kind == "file" && item.type == "text/csv"){
	    var target = $(ev.target)
	    target.removeClass("hoveredByCSV");
	    $.post(
		"/dropSiecle",{
		    csrfmiddlewaretoken: getCookie('csrftoken'),
	    }).done(function(data){
		$("#message").html(data.html)
		$("#message").dialog({title: data.title});
		dropSiecle_(target, file);
	    });
	}
    }
}
/**
 * dépose d'un fichier CSV sur la zone d'atterrissage de siecle.html
 * après un peu de nettoyage
 **/
async function dropSiecle_(target, file){
    var target = $(target);
    var data = new TextDecoder('utf-8').decode(await file.arrayBuffer());
    importeSiecle(data, function(d){
	if(d.ok){
	    target.addClass("traite");
	}
	$("#a_importer > textarea").text(d.a_afficher.join("\n"));
	$("#nb_eleves_siecle").text(d.a_afficher.length);	
	$("#message").html("<p>"+d.message+"</p>");
	$("#message").dialog({title: d.title,});
	nouveaux_sortants_modifier();
    });
}

/**
 * Modifie un élément textarea avec un objet contant un dictionnaire
 * id -> chaîne
 * les ids vont dans un attribut data-ids
 * les chaînes vont dans les lignes du textarea
 * @param textarea un élément textarea ou un sélecteur pour jQuery ; ce
 *        textarea peut avoir un cousin "h2 > span" par son parent "div", auquel
 *        cas on y inscrit le nombre d'éléments du dictionnaire dict
 * @ param dict le dictionnaire
 **/
function ta_id_string(textarea, dict){
    textarea = $(textarea)
    var ids=[]
    var strings = []
    for (var id in dict){
	ids.push(id)
	strings.push(dict[id])
    }
    strings.sort()
    textarea.text(strings.join("\n"))
    textarea.data("ids", ids.join(","))
    var span = textarea.parent("div").find("h2 > span")
    if (span) span.text(Object.keys(dict).length)
}

/**
 * Peuple la liste des élèves nouveaux
 **/
async function nouveaux_sortants_modifier(){
    $.post(
	"/message_nouveaux_sortants",
	{
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	}
    ).done(function(data){
	$("#message").html(data.html);
	/* et on place l'icône d'attente */
	var img = $("<img>").attr({
	    src: "/static/img/timer1.gif",
	    alt: data.alt_image,
	});
	$("#message").append(img);
	$("#message").dialog({title: data.title, hide: "fadeOut"});
    });
    $.postAnimation(
	"/nouveaux_sortants_modifier", {
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	},
	function(data){
	    $("#nb_doublons_siecle").text(data.liste_doublons_siecle.length);
	    $("#liste_doublons_siecle").text(
		data.liste_doublons_siecle.join(", "))
	    $("#doublons_siecle").fadeIn()
	    ta_id_string ("#nouveaux > textarea", data.nouveaux);
	    ta_id_string ("#sortants > textarea", data.sortants);
	    ta_id_string ("#restants > textarea", data.a_modifier);
	    $("#nb_a_modifier_automatiquement").text(
		Object.keys(data.a_modifier_automatiquement).length);
	    if (data.liste_modifier_manuellement.length > 0){
		$("#modifier_manuellement").
		    text(data.liste_modifier_manuellement).
		    dialog({title: "Il faudra faire ça à la main !"});
	    }
	    $("#message").dialog("close");
	    if (data.quasi_homonymes.length > 0){
		montre_quasi_homonymes(
		    data.quasi_homonymes, data.donnees_siecle);
	    }
	    metAjourBoutonsSiecle(data);
	}
    );
    
}

/**
 * Fait apparaître un dialogue avec une liste de quasi-homonymes, et pour
 * chacun un bouton d'action
 * @param qh un objet avec les quasi-homonymes
 * @param donnees_siecle les données issues de SIÈCLE qui vont avec
 **/
function montre_quasi_homonymes(qh, donnees_siecle){
    $.post(
	"/montre_quasi_homonymes", {
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	}
    ).done(function(data){
	$("#message").html(data.html);
	var t = $("#message table");
	for (var nom1 in qh){
	    var buttonCode ="<button data-nom='"+donnees_siecle[nom1].nom+"' data-prenom='"+donnees_siecle[nom1].prenom+"' data-nomprenom='"+donnees_siecle[nom1].nomprenom+"' data-sans-accent='"+donnees_siecle[nom1].sans_accent+"' data-autre='"+donnees_siecle[nom1].autre+"' onclick='unifier_siecle(this)'><img alt='"+data.unifier+"' title='"+data.unifier+"'/></button>";
	    t.append($("<tr><td>"+nom1+"</td><td>"+qh[nom1]+"</td><td>"+buttonCode+"</td></tr>"))
	}
	$("#message").dialog({
	    title: data.title,
	    width: 600,
	});
    });
}

/**
 * Unification de noms venus de SIÈCLE avec des élèves existants dans la base
 * de données de SLM
 * @param elt un élément qui dispose des attributs data-nom, data-prenom,
 *            data-nomprenom, data-sans-accent et data-autre
 **/
function unifier_siecle(elt){
    /* Après l'unification, l'import SIÈCLE est "sali", on ôte les boutons */
    $("#bouton_nouveaux").fadeOut();
    $("#bouton_sortants").fadeOut();
    $("#bouton_restants").fadeOut();
    var button = $(elt);
    var nom = button.attr("data-nom")
    var prenom = button.attr("data-prenom")
    var nomprenom = button.attr("data-nomprenom")
    var sans_accent = button.attr("data-sans-accent")
    var autre = button.attr("data-autre")
    $.post(
	'/unifier_siecle',
	{
	    nom: nom,
	    prenom: prenom,
	    nomprenom: nomprenom,
	    sans_accent: sans_accent,
	    autre: autre,
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	},
	function(data){
	    button.removeAttr("onclick");
	    button.addClass("unifie");
	    button.html(data.html);
	}
    ).fail(
	function(data){
	    alert(I18N.erreurs);
	}
    );
}

/**
 * met à jour (montre ou cache) les boutons qui surmontent les listes
 * d'élèves nouveaux, sortants ou à modifier
 *
 * Avant de montrer les boutons d'action on vérifie l'absence de doublons
 * dans Siècle et dans SLM, par l'examen de data.liste_doublons_siecle et
 * de $("ul.doublon")
 *
 * @param data un objet avec les attributs nouveaux, sortants, a_modifier
 *             qui sont des disctionnaires id ==> chaîne pour un elève,
 *             et liste_doublons_siecle, qui est une liste d'élèves.
 **/
function metAjourBoutonsSiecle(data){
    if (data.liste_doublons_siecle.length == 0 &&
	$("ul.doublon").length == 0){
	$("#bouton_sortants").fadeIn();
	if (Object.keys(data.sortants).length <= 500){
	    /* certains boutons ne sortent que quand on a purgé la plupart  */
	    /* des élèves sortants (ceux qui n'ont plus de livres à rendre) */
	    $("#bouton_print_sortants").fadeIn();
	    $("#bouton_restants").fadeIn();
	    $("#bouton_nouveaux").fadeIn();
	}
    }
}

/**
 * Démarre l'importation d'un fichier SIÈCLE
 * @param csv une instance de TextDecoder nourrie d'un fichier CSV
 * @param cb une fonction de rappel
 **/
function importeSiecle(csv, cb){
    /* on commence a vider les textareas */
    $("#a_importer > textarea").text("");
    $("#nouveaux > textarea").text("");
    $("#sortants > textarea").text("");
    $("#restants > textarea").text("");
    /* puis on appelle la fonction importeSiecle dans ajaxviews.py */
    $.postAnimation(
	"/importeSiecle", {
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	    csv: csv,
	},
	cb
    );
}



/**
 * Traite les doublon dans la base de données de SLM : propose la suppression
 * après affichage des données de prêts relatives aux élèves en double
 **/
function traiteDoublonSlm(nom, prenom){
    var details = $("li.doublon div");
    details.each(function(i, elt){
	var div = $(elt);
	var sans_accent = div.attr("data-sans-accent");
	$.post(
	    "/doublonsSLM", {
		csrfmiddlewaretoken: getCookie('csrftoken'),
		sans_accent: sans_accent,
	    },
	    function(data){
		if (data.distincts.length){
		    div.html("");
		    var ul = $("<ul class='doublon'>")
		    div.append(ul);
		    data.distincts.forEach(
			function(val,index){
			    var li=$("<li>");
			    li.text(val);
			    ul.append(li);
			}
		    )
		} else {
		    var ul = $("<ul class='doublon'></ul>");
		    for (sans_accent in data.doublons) {
			isEmpty = false;
			var li = $("<li>");
			ul.append(li);
			li.text(data[sans_accent].li);
			var button = $("<button>").attr({
			    onclick: "effaceDoublon(this, "  + data[sans_accent].key + ")",
			});
			var img = $("<img>").attr({
			    src: "/static/img/edit-delete.svg",
			    alt: "effacement",
			    title: data[sans_accent].effacer
				.replace("{k}", data[sans_accent].key),
			}).css({
			    width: '16px',
			    margin: '0 5px',
			});
			button.append(img);
			li.append(button);
		    }
		    div.html("");
		    div.append(ul);
		}
	    }
	);
    });
}

/**
 * Supprime effectivement un doublon de la base de données, après avoir
 * neutralisé l'affichage des autres possibilitées d'effacement pour le
 * même doublon.
 * @param button le bouton qui a déclenché l'évènement
 * @param key la clé primaire de l'élève à supprimer de la base
 **/
function effaceDoublon(button, key){
    var li = $(button).parents("li.doublon");
    li.addClass("grise");
    $.post(
	"/effaceEleve",
	{
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	    key: key,
	},
	function(data){
	    $("#message").html(data.html);
	    $("#message").dialog({
		title: data.title,
	    });
	}
    );
}

/**
 * Sortie des élèves qui sont dans la base de SLM mais ne sont plus dans
 * le fichier Siècle importé. On conservera cependant les élèves qui ont
 * des prêts non rendus
 * @param button le bouton qui a déclenché l'évènement
 **/
function sortieParSiecle(button){
    var div = $(button).parents("div.liste_eleves");
    var ids = div.find("textarea").data("ids")
    div.addClass("grise");
    $.post(
	"sortieParSiecle_msg", {
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	}
    ).done(function(data){
	$("#message").html(data.html)
	/* et on place l'icône d'attente */
	var img = $("<img>").attr({
	    src: "/static/img/timer1.gif",
	    alt: data.alt_img,
	});
	$("#message").append(img);
	$("#message").dialog({title : data.title,});
    });
    $.post(
	"/sortieParSiecle",
	{
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	    ids: ids,
	},
	function(data){
	    $("#message").html(data.html);
	    $("#message").dialog({
		title : data.title,
		hide: "fadeOut",
	    });
	    setTimeout(
		function(){
		    $("#message").dialog("close");
		},
		2000
	    );
	    ta_id_string("#sortants > textarea", data.sortants)
	    $("bouton_print_sortants").fadeIn();
	}
    );
}

/**
 * lance une requête pour télécharger un tableau des élèves pas à jour
 * de leurs prêts et sortants du lycée
 **/
function tableauSortantsPasAjour(){
    var f = $("<form>").attr({
	action: "/tableauSortantsPasAjour",
	method: "post"})
    var i = $("<input>").attr({
	name: "csrfmiddlewaretoken",
	value: getCookie('csrftoken'),
	type: "hidden",})
    f.append(i);
    var ids = $("<input>").attr({
	name: "ids",
	value: $("#sortants").find("textarea").data("ids"),
	type: "hidden",})
    f.append(ids)
    $("body").append(f);
    f.submit();
}

/**
 * Modification des élèves dont les données sont différentes, 
 * selon l'import SIÈCLE.
 **/
function modificationParSiecle(){
    var ids = $("#restants textarea").data("ids")
    $.post(
	"/modificationParSiecle_msg", {
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	}
    ).done(function(data){
	$("#message").html(data.html)
	/* et on place l'icône d'attente */
	var img = $("<img>").attr({
	    src: "/static/img/timer1.gif",
	    alt: data.alt_img,
	});
	$("#message").append(img);
	$("#message").dialog({title : data.title,});
    });
    $.post(
	"/modificationParSiecle",
	{
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	    ids: ids,
	},
	function(data){
	    $("#message").html(data.html);
	    $("#message").dialog({
		title : data.title,
		hide: "fadeOut",
	    });
	    setTimeout(
		function(){
		    $("#message").dialog("close");
		},
		2000
	    );
	    ta_id_string("#restants > textarea", data.restants)
	    $("#nb_a_modifier_automatiquement").text(
		Object.keys(data.a_modifier_automatiquement).length);
	    if (data.a_modifier_automatiquement.length == 0){
		$("#restants").addClass("grise");	
	    }
	}
    );
}

/**
 * Inscription de nouveaux élèves, importés depuis Siècle
 * @param elt le bouton qui a été cliqué
 **/
function entreeParSiecle(elt){
    elt = $(elt)
    $.post(
	"/entreeParSiecle_msg", {
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	}
    ).done(function(data){
	$("#message").html(data.html)
	/* et on place l'icône d'attente */
	var img = $("<img>").attr({
	    src: "/static/img/timer1.gif",
	    alt: data.alt_img,
	});
	$("#message").append(img);
	$("#message").dialog({title : data.title,});
    });
    var ids = elt.parent("div").find("textarea").data("ids")
    $.post(
	"/entreeParSiecle",
	{
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	    ids: ids,
	},
	function(data){
	    $("#message").html(data.html);
	    $("#message").dialog({
		title : data.title,
		hide: "fadeOut",
	    });
	    setTimeout(
		function(){
		    $("#message").dialog("close");
		},
		2000
	    );
	    ta_id_string("#nouveaux > textarea",data.nouveaux);
	    if (Object.keys(data.nouveaux).length == 0){
		$("#nouveaux").addClass("grise");	
	    }
	}
    );
}

/**
 * Signale qu'on met fin à une caution, demande confirmation
 * @nomprenom désigne un élève
 **/
function casse_caution(nomprenom){
    var no_caution = $("#id_no_caution").val();
    if (no_caution == 0){
	    var div = $("<div>");
	    $("body").append(div);
	    var p = $("<p>");
	p.text($("#cc").data("impossible"));
	    div.append(p);
	div.dialog({title: $("#cc").data("caution"),});
	return
    }
    $("#confirme-caution").dialog({
	title: I18N.Confirm,
	buttons: [
	    {
		text: I18N.Non,
		click: function() {
		    $( this ).dialog( "close" );
		    return;
		}
	    },
	    {
		text: I18N.Oui,
		click: function() {
		    $( this ).dialog( "close" );
		    do_casse_caution(nomprenom);
		}
	    },
	],
    });
}

/**
 * Signale qu'on met fin à une caution, casse la caution sans confirmation
 * @nomprenom désigne un élève
 **/
function do_casse_caution(nomprenom){
    $.post(
	"/fin_caution",
	{
	    nomprenom: nomprenom,
	    rembourse: $("#montant").val(),
	    csrfmiddlewaretoken: getCookie('csrftoken'),	    
	}
    ).done(
	function(data){
	    var div = $("<div>");
	    $("body").append(div);
	    var p = $("<p>");
	    p.text(data.msg);
	    div.append(p);
	    div.dialog({title: data.title,});
	}
    );
}

/**
 * Envoie un évènement clavier de validation dans un champ de saisie
 * @param selecteur pour accéder au champ de saisie
 **/
function valide_champ(selecteur){
    var event = new KeyboardEvent('keydown',{keyCode: 13});
    document.querySelector(selecteur).dispatchEvent(event);
}

/**
 * Initialisations pour la page /cautions
 **/
function cautions_init(){
    /* On examine s'il y a une valeur dans le champ caché numero */
    if ($("#numero").val()){
	$("#donnees-eleve").show();
	$("#boutons0").hide();
    } else {
	$("#donnees-eleve").hide();
    }
    
    var nomprenomInput = $("#nomprenom");
    nomprenomInput.autocomplete({
	minLength: 3,
	source: function(request, response){
	    $.ajax({
		url: "/correspondance_nom",
		type: "post",
		data: {
		    term: nomprenomInput.val(),
		    csrfmiddlewaretoken: getCookie('csrftoken')
		},
		dataType: "json",
		success: function(data) {
		    response($.map( data, function( item ) {
			return {
			    label: item,
			    value: item
			}
		    }));
		}
	    });
	},
	select: function (e, ui) {
            if (ui.item.value) {
		affiche_caution_eleve(ui.item.value)
            }
	}
    });

    nomprenomInput.on("keydown", function(e){
	if (e.keyCode == 13){
	    e.preventDefault();
	    affiche_caution_eleve(nomprenomInput.val())
	}
    });
    
    var classeInput = $("#classe");
    classeInput.autocomplete({
	minLength: 2,
	source: function(request, response){
	    $.ajax({
		url: "/correspondance_classe",
		type: "post",
		data: {
		    term: classeInput.val(),
		    csrfmiddlewaretoken: getCookie('csrftoken')
		},
		dataType: "json",
		success: function(data) {
		    response($.map( data, function( item ) {
			return {
			    label: item,
			    value: item
			}
		    }));
		}
	    });
	},
	select: function (e, ui) {
            if (ui.item.value) {
		cautions_par_classe(ui.item.value)
            }
	}
    });
	
    classeInput.on("keydown", function(e){
	if (e.keyCode == 13){
	    e.preventDefault();
	    cautions_par_classe(classeInput.val())
	}
    });
    
}

/**
 * affiche des actions pour la classe cl
 * @param cl un nom de classe
 **/
function cautions_par_classe(cl){
    var divActions = $("#cautions_classe");
    $.post(
	"cautions_classe",
	{
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	    classe: cl,
	},
	function(data){
	    var cc = $("#cautions_classe");
	    cc.html("");
	    var t = $("<table class='tableau_prets' id='tableau_cautions'>"); cc.append(t);
	    var cap = $("<caption>"); t.append(cap);
	    cap.text(data.caption);
	    var tr = $(data.head); t.append(tr);
	    data.lignes.forEach(function(l){
		t.append($(l));
	    });
	}
    );
}

/**
 * Valide (ou pas) une nouvelle caution entrée dans la page /cautions, dans
 * le contexte d'une classe ; met à jour le tableau des actions pour la classe
 * @param elt l'élément HTML qui a été cliqué
 **/
function valide_caution(elt){
    var tr = $(elt).parents("tr").first();
    var nomprenom = tr.find("td").first().text();
    var no_caution = parseInt(tr.find("input[name='no_caution']").val());
    if ( !isNaN(no_caution) ){
	$.post(
	    "/maj_caution",
	    {
		csrfmiddlewaretoken: getCookie('csrftoken'),
		no_caution: no_caution,
		nomprenom: nomprenom,
	    },
	    function(data){
		if (data.ok){
		    /* provoque une remise à jour du tableau */
		    valide_champ("#classe");
		} else {
		    $("#messages").html("<p>" + data.msg + "</p>");
		    $("#messages").dialog({title: data.erreur});
		}
	    }
	);
    }
}

/**
 * affiche les données de caution pour un élève donné
 **/
function affiche_caution_eleve(nomprenom){
    $.post(
	"/affiche_caution_eleve",
	{
	    nomprenom: nomprenom,
	    csrfmiddlewaretoken: getCookie('csrftoken')
	},
	function (data){
	    if (data.nomprenom == ""){
		$("#donnees-eleve").fadeOut();
		$("#boutons0").fadeIn();
	    }
	    if (data.ok) {
		$("#donnees-eleve").html(data.html);
		$("#boutons0").fadeOut();
		$("#donnees-eleve").fadeIn();
	    }
	}
    );
}

/**
 * Annulation d'une caution (passage à l'état « sans caution »)
 * @param sans_accent désigne un élève
 **/
function annule_caution(sans_accent){
    $.post(
	"/annule_caution_msg", {
	    sans_accent: sans_accent,
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	}
    ).done(function(data){
	$("#messages").html(data.html)
	$("#messages").dialog({
	    title: data.title,
	    buttons: [
		{
		    text: I18N.Non,
		    click: function() {
			$( this ).dialog( "close" );
			return;
		    }
		},
		{
		    text: I18N.Oui,
		    click: function() {
			$( this ).dialog( "close" );
			$.post(
			    "/annule_caution",
			    {
				sans_accent: sans_accent,
				csrfmiddlewaretoken: getCookie('csrftoken')
			    },
			    function (data){
				if (data.ok){
				    valide_champ("#nomprenom");
				} else {
				    alert(I18N.Bof);
				}
			    }
			);
			return;
		    }
		},	    
	    ],
	});
    });
}

/**
 * active le dialogue pour rendre une caution, ou pour la gérer si celle-ci
 * était déjà rendue
 * param data une structure de données qui fournit le code html du dialogue
 **/
function dialogue_caution(data){
    $("#messages").html(data.html);
    $("#messages").dialog({
	title: data.title + $("#nomprenom").val(),
	width: 600,
	buttons: [
	    {
		text: data.valider,
		click: function(){
		    $.post(
			"/fin_caution",
			{
			    nomprenom: $("#nomprenom").val(),
			    numero: $("#numero_de_la_caution").val(),
			    rembourse: $("#somme").val(),
			    ok1: $("#ok1").prop("checked"),
			    ok2: $("#ok2").prop("checked"),
			    inconnu: $("#inconnu").prop("checked"),
			    reussite: $("#reussite").prop("checked"),
			    perte: $("#perte").prop("checked"),
			    quitte: $("#quitte").prop("checked"),
			    sc: $("#sc").prop("checked"),
			    autre: $("#autre").prop("checked"),
			    comment: $("#comment").val(),
			    annule_rendu_numero:
			      $("#annule_rendu_numero").val(),
			    csrfmiddlewaretoken: getCookie('csrftoken')
			},
			function(data1){
			    if (data1.ok){
				valide_champ("#nomprenom");
				if (! data1.est_valide){
				    alert(data1.deja_rendu);
				}
			    }
			    else {
				alert(I18N.Bof);
			    }			    
			}
		    );
		    $("#messages").dialog( "close" );
		}
	    },
	],
    });
}

/**
 * Procédure pour rendre une caution, ou gérer la caution si elle est
 * déjà rendue
 * @param sans_accent désigne un élève
 **/
function rend_caution(sans_accent){
    var liste_manuels = [];
    $("td.id_manuel").each(function(index, elt){
	liste_manuels.push($(elt).text());
    });
    liste_manuels = JSON.stringify(liste_manuels);
    $.post(
	"/rend_caution",
	{
	    sans_accent: sans_accent,
	    liste_manuels: liste_manuels,
	    no_caution: $("#no_caution").text(),
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	},
	dialogue_caution
    );
}

/**
 * Édite les commentaires d'une caution
 * @param sans_accent désigne un élève
 * @param commentaire valeur initiale du commentaire
 **/
function edite_caution(sans_accent, commentaire){
    commentaire = commentaire.replace(/<br>/g,"\n");
    $.post(
	"/commente_caution_msg", {
	    commentaire: commentaire,
	    sans_accent: sans_accent,
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	}
    ).done(function(data){
	$("#messages").html(data.html);
	$("#messages").dialog({
	    title: data.title,
	    width: 800,
	    buttons: [
		{
		    text: I18N.Valider,
		    click: function(){
			$( this ).dialog( "close" );
			$.post(
			    "/commente_caution",
			    {
				sans_accent: sans_accent,
				val: $("#comment").val(),
				csrfmiddlewaretoken: getCookie('csrftoken')
			    },
			    function(data){
				if (data.ok){
				    valide_champ("#nomprenom");
				} else {
				    alert(I18N.Bof);
				}			    
			    }
			);
		    },
		}
	    ],
	});
    });
}

/**
 * Rétablit un ancien numéro de caution, d'après le contexte de la
 * page /cautions
 **/
function ancienne_caution(){
    $.post(
	"/ancienne_caution_msg", {
	    numero: $("#no_caution").val(),
	    date: $("#date").val(),
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	}
    ).done(function(data){
	$("#messages").html(data.html);
	$("#messages").dialog({
	    title: data.title,
	    buttons: [
		{
		    text: I18N.OK,
		    click: function(){
			$( this ).dialog( "close" );
			$.post(
			    "/ancienne_caution",
			    {
				nomprenom: $("#nomprenom").val(),
				no_caution: $("#no_caution").val(),
				date: $("#date").val(),
				csrfmiddlewaretoken: getCookie('csrftoken')
			    },
			    function(data){
				if (data.ok){
				    valide_champ("#nomprenom");
				} else {
				    alert(I18N.Bof);
				}			    
			    }
			);
		    }
		}
	    ],
	});
    });
}

/**
 * Exporte la liste des élèves sans livres à un format facile à retravailler
 * prend en compte le champ #classe_sans_prets
 **/
function print_eleves_sans_livres(){
     var form = $("<form>").attr({
	action: "/print_eleves_sans_livres",
	method: "post",
    }).css({
	display: "none",
    });
    var input1 = $("<input>").attr({
	type: "text",
	name: "csrfmiddlewaretoken",
    }).val(getCookie('csrftoken'));
    var input2 = $("<input>").attr({
	name: "classe",
    }).val($("#classe_sans_prets").val());
    var classe = $("#classe_sans_prets").val()
    form.append(input1).append(input2);
    $("body").append(form);
    form.submit();
    form.remove();
}

/**
 * Exporte la liste des élèves avec livres à un format facile à retravailler
 * prend en compte le champ #classe_pretes
 **/
function print_eleves_avec_livres(){
     var form = $("<form>").attr({
	action: "/print_eleves_avec_livres",
	method: "post",
    }).css({
	display: "none",
    });
    var input1 = $("<input>").attr({
	type: "text",
	name: "csrfmiddlewaretoken",
    }).val(getCookie('csrftoken'));
    var input2 = $("<input>").attr({
	name: "classe",
    }).val($("#classe_pretes").val());
    var classe = $("#classe_sans_prets").val()
    form.append(input1).append(input2);
    $("body").append(form);
    form.submit();
    form.remove();
}

/**
 * Initialisation de la page revue_du_stock
 **/
function revue_du_stock_init(){
    var codeInput = $("#code");
    codeInput.focus();
    codeInput.on("keydown", function(e){
	if (e.keyCode == 13){
	    e.preventDefault();
	    var code = codeInput.val();
	    revue(code);
	    codeInput.val("");
	    codeInput.attr("placeholder", code);
	}
    });
}

/**
 * Fonction de rappel pour servir quand on entre le code d'un livre dans
 * la page revue_du_stock
 **/
function revue(code){
    $.post(
	"revue", {
	    code: code,
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	},
	function(data){
	    $("#reponse").html(data.html);
	    if (data.sound.length) {
		document.querySelector("#"+data.sound).play();
	    }
	}
    );
}

/**
 * Fonction de rappel pour mettre un livre au pilon
 * @param ident numéro d'inventaire d'un livre
 **/
function au_pilon(ident){
    $.post(
	"/au_pilon_msg", {
	    ident: ident,
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	}
    ).done(function(data){
	var msg = $("#message");
	msg.html(data.html);
	msg.dialog({
	    title: data.title,
	    buttons:[
		{
		    text: I18N.OK,
		    click: function(){
			$( this ).dialog( "close" );
			$.post(
			    "/au_pilon",
			    {
				ident: ident,
				csrfmiddlewaretoken: getCookie('csrftoken')
			    },
			    function(data){
				if (data.ok){
				    alert(data.msg.replace("{i}", ident));
				    location.reload() 
				} else {
				    alert(I18N.Bof);
				}
			    }
			);
		    }
		},
		{
		    text: I18N.Non,
		    click: function(){
			$( this ).dialog( "close" );
		    }
		},
	    ]
	});
    });
}

/**
 * Initialisations pour la page /boutique
 **/
function boutique_init(){
    var nomprenomInput = $("#nomprenom");
    nomprenomInput.autocomplete({
	minLength: 3,
	source: function(request, response){
	    $.ajax({
		url: "/correspondance_nom",
		type: "post",
		data: {
		    term: nomprenomInput.val(),
		    csrfmiddlewaretoken: getCookie('csrftoken')
		},
		dataType: "json",
		success: function(data) {
		    response($.map( data, function( item ) {
			return {
			    label: item,
			    value: item
			}
		    }));
		}
	    });
	},
	select: function (e, ui) {
            if (ui.item.value) {
		$("#vers-moisson").fadeIn();
		choix_livres(ui.item.value);
            }
	}
    });

    nomprenomInput.on("keydown", function(e){
	if (e.keyCode == 13){
	    e.preventDefault();
	    choix_livres(nomprenomInput.val());
	}
    });

    if (nomprenomInput.val().length){
	choix_livres(nomprenomInput.val());
    }
}

/**
 * Renvoie vrai si un évènement souris est dans un élément jQuery
 * @param ev un évènement de souris
 * @param el un élément sélectionné par jQuery
 * @return un booléen disant si le curseur de soursis est "dedans"
 **/
function mouseIsInside(ev, el){
    var offset = el.offset()
    var x = ev.pageX - offset.left;
    var y = ev.pageY - offset.top;
    return x >= 0 && y >= 0 && x < el.width() && y < el.height();
}

/**
 * Renvoie vrai si un évènement souris est plus haut qu'un élément jQuery
 * @param ev un évènement de souris
 * @param el un élément sélectionné par jQuery
 * @return un booléen disant si le curseur de soursis est "plus haut"
 **/
function mouseIsHigher(ev, el){
    var offset = el.offset();
    var y = ev.pageY - offset.top;
    return y < el.height()/2;
}

/**
 * mise en place des livres du tronc commun et de autres livres
 **/
function choix_livres(nomprenom){
    $.post(
	"/mes_livres_en_boutique",
	{
	    nomprenom: nomprenom,
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	}
    ).done(
	function(data){
	    $("#classe-livres").fadeIn();
	    $("#nom-classe").text(data.classe);
	    $("#options").html(data.matieres.join("<br>"));
	    var lc = $("#livres-choisis");
	    var al = $("#autres-livres");
	    function place_livres(collection){
		collection.html("");
		return function(livre, index){
		    var l = $(livre);
		    collection.append(l);
		    if (l.find(".container").hasClass("fixe")) {
			makeImmobile(l);
		    } else {
			makeDraggable(l, lc, al, collection);
		    }
		}
	    }
	    // mise en place des livres choisis
	    data.livres_tc.forEach(place_livres(lc));
	    // mise en place des autres livres
	    data.livres_autres.forEach(place_livres(al));
	}
    );
}

/**
 * Rend un livre mobilisable
 * @param l  objet jQuery pour le livre
 * @param lc objet jQuery pour les livres-choisis
 * @param al objet jQuery pour les autres-livres
 * @param collection un des points de départ du drag, lc ou al
 **/
function makeDraggable(l, lc, al, collection){
    var separateur = $("#separation");
    l.draggable({
	helper: "clone",
	containment: $("#choix-livres"),
	axis: "y",
	start: function(event, ui){
	    $(event.target).addClass("semi-transparent");
	    lc.addClass("sensitive");
	    al.addClass("sensitive");
	},
	stop: function(event, ui){
	    $(event.target).removeClass("semi-transparent");
	    lc.removeClass("sensitive");
	    al.removeClass("sensitive");
	    var higher = mouseIsHigher(event, separateur);
	    if (collection == lc && ! higher){
		var new_l = $(l.clone());
		l.remove();
		al.prepend(new_l);
		makeDraggable(new_l, lc, al, al);
	    }
	    if (collection == al && higher){
		var new_l = $(l.clone());
		l.remove();
		lc.append(new_l);
		makeDraggable(new_l, lc, al, lc);
	    }
	},
    })
}

/**
 * Rend un livre mobilisableimmobile, mais qui rale quand on veut le bouger
 * @param l  objet jQuery pour le livre
 **/
function makeImmobile(l){
    var separateur = $("#separation");
    var l_initial;
    l.draggable({
	helper: "clone",
	containment: $(l).parent(),
	axis: "y",
	start: function(event, ui){
	    l_initial = $(event.target);
	    l_initial.addClass("semi-transparent");
	    document.querySelector("#soundiconko").play();
	},
	stop: function(event, ui){
	    $.post(
		"/makeImmobile_msg",  {
		    csrfmiddlewaretoken: getCookie('csrftoken'),
		}
	    ).done(function (data){
		alert(data.inutile_de_bouger);
		l_initial.removeClass("semi-transparent");
	    });
	},
    })
}

/**
 * Initialisation pour la page "/correlation"
 **/
function correlation_init(){
    $.post("/correlation_options_livres", {
	    csrfmiddlewaretoken: getCookie('csrftoken'),
    },function(data){
	$("#correlation").html(data.html_table);
	$("#python-code").html(data.source).fadeIn();
	var table = $("#correlation table");
	var th = $("#correlation thead th");
	var ncol = th.length;
	var cg = $("<colgroup>");
	var col = $("<col>");
	col.css("width", "100px");
	cg.append(col);
	for(n=1; n < ncol; n++){
	    col = $("<col>");
	    col.css("max-width", "80px");
	    cg.append(col);
	}
	table.prepend(cg);
	var td = $("#correlation td");
	td.each(function(i, elt){
	    var cell = $(elt);
	    var val = parseFloat(cell.text());
	    var hue = 60 * (val + 1); // -1 rouge, 0 jaune et 1 vert
	    var color = "hsl(" + hue + "deg 90% 80%)";
	    cell.css({
		background: color,
		width: "80px",
	    });
	});
	th.each(function(i, elt){
	    var cell = $(elt);
	    var text = cell.text();
	    cell.html("<p>"+text+"</p>");
	    cell.css({
		background: "lightyellow",
		position: "relative",
		height: "220px",
		"writing-mode": "bt-rl",
		"text-align": "left",
		"vertical-align": "top",
	    });
	    cell.find("p").css({
		position: "absolute",
		"transform-origin": "top left",
		transform: "rotate(-90deg) translateX(-100%)",
	    })
	});
    });
}

/**
 * Vérification qu'on est bien prêt à passer à la moisson de livres
 **/
function moisson(){
    $("#msg").dialog({
	modal: true,
	title: I18N.Confirm,
	buttons: [
	    {
		text: I18N.Non,
		      click: function() {
			  $( this ).dialog( "close" );
		      }
	    },
	    {
		text: I18N.Oui,
		icon: "ui-icon-heart",
		      click: function() {
			  $( this ).dialog( "close" );
			  moisson_go();
		      }
	    },
	],
    });
}

/**
 * Passage à la moisson de livres ; les livres à choisir sont dans le div
 * #choix-livres
 **/
function moisson_go(){
    var livres = $("#livres-choisis .livre");
    var abreges = [];
    var txt = []
    livres.each( function(i, l){
	abreges.push($(l).find(".abrege").data("abg"));
	txt.push($(l).find(".abrege").data("titre"));
    });
    var form = $("<form>").attr({
	action: "/moisson",
	method: "post",
    }).css({
	display: "none",
    });
    var input1 = $("<input>").attr({
	type: "text",
	name: "csrfmiddlewaretoken",
    }).val(getCookie('csrftoken'));
    var input2 = $("<input>").attr({
	name: "livres",
    }).val(abreges.join(","));
    var input3 = $("<input>").attr({
	name: "txt",
    }).val(txt.join(","));
    form.append(input1).append(input2).append(input3);
    $("body").append(form);
    form.submit();
    form.remove();
}

/**
 * montre ou cache le bouton "pris"
 * @param etat true pour montrer sinon false
 * @param abg (facultatif) est l'abrégé du livre détecté si etat == true
 **/
function boutonPris(etat, abg){
    var images = $("#pres-du-pouce img");
    var nonpris = $(images[0]), ouipris = $(images[1]);
    if (etat) {
	nonpris.css({display: "none",});
	ouipris.css({display: "inline-block",});
	ouipris.on("click", function(){
	    prendre_un_livre(abg);
	    
	});
    } else {
	nonpris.css({display: "inline-block",});
	ouipris.css({display: "none",});
    }
}

/**
 * dans la page de /moisson, quand on appuie sur le bouton qui
 * déclare qu'on a pris un livre
 * @param abrege l'abrégé de ce livre
 **/
function prendre_un_livre(abrege){
    dejapris.push(abrege);
    $.post(
	"/prendre_un_livre_msg", {
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	}
    ).done(function(data){
	$("#qr-reader-results").html(data.html);
	var nbpris = $("ul li input:checked")
	$("#nbpris").text(nbpris.length)
    });
    $(".qr-reader").attr("class", "qr-reader kk");
    boutonPris(false);
    var li = $("#coches li[data-abg='"+abrege+"']");
    li.find("input").prop("checked", true);
    li.addClass("checked");
    li.clone().prependTo("#coches");
    li.remove();
    showBoutiqueBoutons();
    document.querySelector("#soundiconpris").play();
    // si tous les livres sont cochés, on affiche l'invitation à
    // passer à l'enregistrement.
    var inputs = $("#coches li");
    if (inputs.length == inputs.find(":checked").length){
	$("#enregistre").fadeIn();
	document.querySelector("#soundiconreussi").play();
	html5Qrcode.stop(); // arrêt du scan de qr-codes
	$("#qr-reader-results").fadeOut();
    }
}

/**
 * Montre le groupe de boutons de la boutique ou cache le groupe
 * @param mode ; si mode == false alors les boutons sont cachés.
 **/
function showBoutiqueBoutons(mode){
    if(mode === false){
	/* on cache les boutons */
	$("#coches").hide();
	var cb = $("#coches input");
	var checked = $("#coches input:checked");
	var p = parseInt(checked.length/cb.length*100);
	$("#progress-moisson").show().find("progress").val(p);
    } else {
	/* on montre les boutons, mais pas longtemps */
	$("#coches").show();
	$("#progress-moisson").hide();
	setTimeout(function(){
	    showBoutiqueBoutons(false);
	}, 2000);
    }
}

var html5Qrcode; // variable globale qui contrôlera le scan des qr-codes

/**
 * fonction a lancer après que le DOM de la page /moisson soit initialisé
 **/
function moisson_ready(){
    tous_manuels.forEach(function(elt){
	elt.ids.forEach(function(id){
	    tousCodes["coop-" + id] = elt.abrege;
	});
    });
    a_chercher.forEach(function(elt){
	elt.ids.forEach(function(id){
	    codes["coop-" + id] = elt.abrege;
	});
    });
    $("li input").prop("checked", false);
    showBoutiqueBoutons();

    /**
     * fonction de rappel qui suit un appui sur le bouton "Pris".
     * renvoie un contexte avec les boutons pertinents cochés/non cochés
     * et le nom de l'abrégé
     **/
    function repondre(abg, statut){
	
	var ul = $("#coches li")
	var contexte = {
	    coches: [],
	}
	ul.each( function(i, li){
	    li = $(li)
	    contexte.coches.push({
		titre: li.text().trim(),
		abg: li.data("abg"),
		dejapris: li.find("input").is(":checked"),
	    })
	})
	$.post(
	    "/repondre_abrege", {
		abg: abg,
		contexte: JSON.stringify(contexte),
		csrfmiddlewaretoken: getCookie('csrftoken'),
	    }
	).done(function(data){
	    var response = data.response
	    $("#qr-reader-results").html(response[statut]);
	});
	if (statut == "oui"){
	    $(".qr-reader").attr("class", "qr-reader ok");
	    document.querySelector("#soundiconok").play();
	    boutonPris(true, abg);
	} else {
	    boutonPris(false);
	    if (statut == "deja" || statut == "laisser") {
		$(".qr-reader").attr("class", "qr-reader kk");
	    } else {
		$(".qr-reader").attr("class", "qr-reader ko");
	    }
	    document.querySelector("#soundiconko").play();
	}
    }


    /**
     * fonction de rappel pour choisir la caméra ; quand le choix est
     * fait ça appelle la fonction startCamera
     * @param data un tableau d'objets avec des attributs id et label
     **/
    function choixCamera(data){
	var choixCamIdOrConstraint; // façon de choisir la caméra
	if (data.length == 1) choixCamIdOrConstraint = data[0].id
	else choixCamIdOrConstraint = { facingMode: { exact: "environment"} }
	startCamera(choixCamIdOrConstraint)
    }

    html5Qrcode = new Html5Qrcode("qr-reader");
    Html5Qrcode.getCameras().then(choixCamera);

    /**
     * fonction de rappel pour un qt-code trouvé
     **/
    function qrCodeTrouve(decodedText){
	var abg = I18N.code_inconnu;
	if (decodedText in codes){
	    abg = codes[decodedText];
	    if (dejapris.includes(abg)){
		statut="deja";
	    } else {
		statut = "oui";
	    }
	} else {
	    if (decodedText in tousCodes){
		abg = tousCodes[decodedText];
		statut = "laisser";
	    } else {
		statut = "non";
	    }
	}
	repondre(abg, statut);
    }


    function startCamera(idOrConstraint){
	var promise = html5Qrcode.start(
	    idOrConstraint,
	    {fps: 10, qrbox: 200},
	    qrCodeTrouve).catch(
		(err) => {console.log(err);});
    }

}

/**
 * Initialisation de la page /constantes_modif. Le lien vers l'image
 * est attaché à une ancre textuelle ; on remplace ça par une image
 **/
function constantes_modif_init(){
    var a = $("#Logo-clear_id").prev();
    var url = a.attr("href");
    var img = $("<img alt='"+a.text()+"' src='"+url+"' class='img-small'/>");
    a.text("");
    a.append(img);
}

/**
 * Initialisation de la page de déclaration de livres en supplément
 **/
function livre_supplement_init(){
    var titreInput = $("#titre");
    var nomprenomInput = $("#nomprenom");
    titreInput.autocomplete({
	minLength: 3,
	source: function(request, response){
	    $.post(
		"/correspondance_livre",
		{
		    term: titreInput.val(),
		    csrfmiddlewaretoken: getCookie('csrftoken')
		},
		function(data) {
		    response($.map( data, function( item ) {
			return {
			    label: item.title,
			    value: item.id,
			}
		    }));
		}
	    )},
	select: function (e, ui) {
	    if (ui.item.value) {
		/* remet le label dans l'input puis traite ce label */
		setTimeout(function(){
		    titreInput.val(ui.item.label);
		    plus_livre_supplement(ui.item.label);
		});
	    }
	}
    });
    nomprenomInput.autocomplete({
	minLength: 3,
	source: function(request, response){
	    $.ajax({
		url: "/correspondance_nom",
		type: "post",
		data: {
		    term: nomprenomInput.val(),
		    csrfmiddlewaretoken: getCookie('csrftoken')
		},
		dataType: "json",
		success: function(data) {
		    response($.map( data, function( item ) {
			return {
			    label: item,
			    value: item
			}
		    }));
		}
	    })
	},
	select: function (e, ui) {
            if (ui.item.value) {
		/* remet le label dans l'input puis traite ce label */
		setTimeout(
		    function(){
			nomprenomInput.val(ui.item.label);
			plus_nomprenom_supplement(ui.item.label)
		    }
		)}
	    },
    });
}
    
/**
 * ajoute un widget de livre à prêter en supplément
 * @param titre le titre du livre
 **/
function plus_livre_supplement(titre){
    $.post(
	"/plus_livre_supplement",
	{
	    titre: titre,
	    csrfmiddlewaretoken: getCookie('csrftoken')
	},
    ).done( function(data){
	var dejala = $("#les-livres").find("div[data-id='"+data.id+"']");
	if (dejala.length == 0)	$("#les-livres").find("div.plus").append(
	    $(data.html));
	$("#les-livres").fadeIn();
	setTimeout(function(){
	    $("#titre").val("");
	}, 1000);
    });
}

/**
 * supprime un bouton de livre supplémentaire
 * @param elt le bouton qui a été cliqué
 **/
function moins_livre_supplement(elt){
    elt=$(elt);
    var div = elt.parent();
    div.remove();
    var divs = $("#les-livres").find("div.bouton-suppr");
    if (divs.length == 0) $("#les-livres").fadeOut();
}

/**
 * ajoute un widget d'élève à qui prêter un supplément de livres
 * @param nomprenom le titre du livrenom et le prénom de l'élève
 **/
function plus_nomprenom_supplement(nomprenom){
    $.post(
	"/plus_nomprenom_supplement",
	{
	    nomprenom: nomprenom,
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	},
    ).done( function(data){
	var dejala = $("#les-eleves").find("div[data-id='"+data.id+"']");
	if (dejala.length == 0)	{
	    $("#les-eleves").find("div.plus").append(
		$(data.html));
	    plus_classe_supplement(data.classe);
	}
	$("#les-eleves").fadeIn();
	setTimeout(function(){
	    $("#nomprenom").val("");
	}, 1000);
    });
}

/**
 * Met en place un bouton pour pouvoir ajouter tous les élèves d'une classe
 * @param classe un nom de classe
 */
function plus_classe_supplement(classe){
    $.post("/plus_classe_supplement",{
	classe: classe,
	csrfmiddlewaretoken: getCookie('csrftoken'),
    }
    ).done(function(data){
	var dejala = $("#les-eleves").find("div[data-classe='"+classe+"']");
	if (dejala.length == 0)	{
	    $("#les-eleves").find("div.plus").prepend(
		$(data.html));
	}
    });
}

/**
 * supprime un bouton d'élève supplémentaire
 * @param elt le bouton qui a été cliqué
 **/
function moins_nomprenom_supplement(elt){
    elt=$(elt);
    var div = elt.parent();
    div.remove();
    var divs = $("#les-eleves").find("div.bouton-suppr");
    if (divs.length == 0) $("#les-eleves").fadeOut();
}

/**
 * traite les données issues de /livre_supplement et validées par un prof
 **/
function livre_supplement_go(){
    var msg = [];
    var ok = true;
    var prof = $("#prof").val();
    if (prof.length < 4) {
	msg.push($("#lsg").data("noprof").replace("{p}", prof));
	ok = false;
    }
    var livres = [];
    $("#les-livres").find("div.bouton-suppr").each(function(index,div){
	livres.push($(div).data("id"))
    })
    if (livres.length == 0) {
	msg.push($("#lsg").data("nobook"));
	ok = false;
    }
    var eleves = [];
    $("#les-eleves").find("div.bouton-suppr").each(function(index,div){
	eleves.push($(div).data("id"))
    })
    if (eleves.length == 0) {
	msg.push($("#lsg").data("nostudent"));
	ok = false;
    }
    var message = $("#message");
    if (!ok){
	
	message.html("");
	message.append($("<h3>").text($("#lsg").data("erreur")));
	var ul = $("<ul>");
	msg.forEach((txt) => ul.append($("<li>").text(txt)));
	message.append(ul);
	message.dialog({
	    show: { effect: "fold", duration: 1000 },
	    modal: true,
	});
    } else {
	message.html("");
	message.append($("<h3>").text($("#lsg").data("oktitle")));
	message.append($("<p>").text($("#lsg").data("prets")
				     .replace("{lp}", livres.length)
				     .replace("{le}", eleves.length)
	    ));
	message.dialog({
	    show: { effect: "fold", duration: 1000 },
	    modal: true,
	});
	setTimeout(function(){
	    message.dialog("close");
	    var form = $("<form method='post' action='/livre_supplement_go'>");
	    $("body").append(form);
	    form.append($("<input type='hiddden' name='csrfmiddlewaretoken'>").val(getCookie('csrftoken')));
	    form.append($("<input type='text' name='data'>").val(
		JSON.stringify({
		    prof: prof,
		    livres: livres,
		    eleves: eleves,
		})
	    ));
	    form.append(
		$("<textarea name='commentaire' rows='3' cols='50'>").val(
		    $("#commentaire").find("textarea").val()
		));
	    form.submit();
	},3000);
    }
}

/**
 * Validation ou effacement de demande de livre supplémentaire
 * @param elt l'élément <button> qui a été cliqué
 **/
function valide_livre_supplement(elt){
    elt = $(elt);
    var id = elt.data("id");
    $.post( '/findLivreSupplement',{
	csrfmiddlewaretoken: getCookie('csrftoken'),
	id: id,
    }
    ).done(function(data){
	var div = $("#message");
	var prof = div.find("span.prof");
	var comment = div.find("textarea.comment");
	var livres = div.find("dd.livres");
	var eleves = div.find("dd.eleves");
	prof.text(data.prof);
	if (data.commentaire) comment.text(data.commentaire);
	else comment.text(data.nocomment);
	livres.text(data.livres);
	eleves.text(data.eleves);
	div.dialog({
	    modal: true,
	    width: 450,
	    buttons: [
		{
		    text: I18N.Echappement,
		    click: function() {
			$( this ).dialog( "close" );
		    }
		},
		{
		    text: I18N.Valider,
		    icon: "ui-icon-heart",
		    click: function() {
			$( this ).dialog( "close" );
			deleteDemandeBouton(data.id);
			valide_livre_supplement_go(data.id, true);
		    }
		},
		{
		    text: I18N.Supprimer,
		    icon: "ui-icon-heart",
		    click: function() {
			$( this ).dialog( "close" );
			deleteDemandeBouton(data.id);
			valide_livre_supplement_go(data.id, false);
		    }
		},
	    ],
	});
    });
}

/**
 * Supprime un bouton de demande de livre supplémentaire
 * @param id l'identifiant de la demande
 **/
function deleteDemandeBouton(id){
    $("button[data-id='"+id+"']").parent().remove();
}

/**
 * Valide ou invalide une demande de livres
 * @param id l'identifiant d'une demande
 * @param mode booléen, vrai pour valider, faux pour invalider
 **/
function valide_livre_supplement_go(id, mode){
    $.post("/valide_livre_supplement_go",{
	id: id,
	mode: mode,
	csrfmiddlewaretoken: getCookie('csrftoken'),	
    });
}

/**
 * Ajoute tous les élèves d'une classe, pour un prêt de livre supplémentaire
 * @param elt désigne le <div> qui a été cliqué
 **/
function plus_classe_supplement_go(elt){
    elt= $(elt);
    $.post("/plus_classe_supplement_go",{
	classe: elt.data("classe"),
	csrfmiddlewaretoken: getCookie('csrftoken'),	
    }).done(function(data){
	data.eleves.forEach(function(np){
	    plus_nomprenom_supplement(np);
	});
    });
}

/**
 * Détermine si deux rectangles se recouvrent au moins partiellement
 * @param r1 un objet {x : float, y: float, width: float, height: float}
 * @param r2 un objet {x : float, y: float, width: float, height: float}
 * @param ratio (facultatif), 0.5 par défaut ; proportion minimale du
 *              plus petit rectangle inclus dans l'autre
 * @return true si l'aire de l'intersection est plus que le ratio
 *              multiplié par l'aire la plus petite
 **/
function rectOverlap(r1, r2, ratio){
    if (ratio === undefined) ratio = 0.5;
    var a1 = r1.width * r1.height;
    var a2 = r2.width * r2.height;
    var a = a1;
    if (a1 > a2){
	var r = r2;
	r2=r1;
	r1 = r;
	a = a2;
    }
    /* le rectangle r1 est maintenant le plus petit, son aire est a   */
    /* on applique une recette publiée à                              */
    /* https://math.stackexchange.com/questions/99565/                */
    /* simplest-way-to-calculate-the-intersect-area-of-two-rectangles */
    var x_overlap = Math.max(
	0,
	Math.min(r1.x + r1.width, r2.x + r2.width) - //plus à gauche bord droit
	    Math.max(r1.x, r2.x));                   //plus à droite bord gauche
    var y_overlap = Math.max(
	0,
	Math.min(r1.y + r1.height, r2.y + r2.height) - //plus haut bord bas
	    Math.max(r1.y, r2.y));                     //plus bas bord haut
    var overlapArea = x_overlap * y_overlap;           // aire commune
    return overlapArea / a > ratio;
}

/**
 * Initialisation du rectangle de 80 px de large pour les changements de langue
 **/
function init_langs(){
    $.post("/langs",{
	csrfmiddlewaretoken: getCookie('csrftoken'),	
    }).done(
	function(data){
	    $("#langs").html(data.html);
	}
    );
}

/**
 * fonction de rappel pour changer et recharger la page courante
 * @param l la langue désirée
 **/
function change_language(l){
    $.post("/change_language",{
	csrfmiddlewaretoken: getCookie('csrftoken'),
	lang: l,
    }).done(
	function(data){
	    location.reload();
	}
    );
    
}

/**
 * fait apparaître dans un dialogue les livres nécessaires pour une classe
 **/
function livres_de_classe(classe){
    $.post( "/livres_de_classe", {
	classe: classe,
	csrfmiddlewaretoken: getCookie('csrftoken'),
    }).done(function(data){
	$("#messages").html(data.html).dialog();
    });
}

/**
 * pour dé-perdre un livre
 * @param that l'élément button qui a été cliqué
 **/
function livre_retrouve(that){
    that = $(that);
    $("#message").html(that.data("msg")).dialog(
	{
	    title: "id = " + that.data("id"),
	    buttons: [
		{
		    text: I18N.Echappement,
		    click: function() {
			$( this ).dialog( "close" );
		    }
		},
		{
		    text: I18N.Valider,
		    icon: "ui-icon-heart",
		    click: function() {
			$( this ).dialog( "close" );
			livre_retrouve_go(that.data("id"));
		    }
		},
	    ],
	});
}

function livre_retrouve_go(id){
    $.post( "/livre_retrouve_go", {
	id: id,
	csrfmiddlewaretoken: getCookie('csrftoken'),
    }).done(
	function(data){
	    $("li.livre-perdu").hide();
	}
    );
}

/**
 * Action de postage de courriers pour une liste d'élèves définie à
 * /choix_eleves
 **/
function action_courrier(){
    var noms = [];
    $("#choix .nom").each(function(_, div){
	noms.push($(div).text().replace(/\(.*\)/, "").trim());
    });
    var form = $("<form>").attr({
	action: "/action_courrier",
	method: "post",
    }).css({
	display: "none",
    });
    var input1 = $("<input>").attr({
	type: "text",
	name: "csrfmiddlewaretoken",
    }).val(getCookie('csrftoken'));
    var input2 = $("<input>").attr({
	name: "noms",
    }).val(JSON.stringify(noms));
    form.append(input1).append(input2);
    $("body").append(form);
    form.submit();
    form.remove();
}

  /**
   * Lance la génération d'étiquettes d'adresses
   * avec $("#noms").val() pour les noms et en prenant en
   * compte les cases rouges du tableau (de la page /action_courrier)
   **/
  function action_courrier_go(){
      var tabous = [];
      var trs = $("table.etiquettes tr");
      trs.each(function(l, tr){
	  var tds = $(tr).find("td");
	  tds.each(function(c, td){
	      if($(td).hasClass("tabou")){
		  tabous.push({row: 1+l, col: 1+c});
	      }
	  });
      });
      var form = $("<form>").attr({
	  action: "/action_courrier_go",
	  method: "post",
      }).css({
	  display: "none",
      });
      var input1 = $("<input>").attr({
	  type: "text",
	  name: "csrfmiddlewaretoken",
      }).val(getCookie('csrftoken'));
      var input2 = $("<input>").attr({
	  name: "tabous",
      }).val(JSON.stringify(tabous));
      var input3 = $("<input>").attr({
	  name: "noms",
      }).val($("#noms").val());
      form.append(input1).append(input2).append(input3);
      $("body").append(form);
      form.submit();
      form.remove();
  }

/**
 * Crée à la volée un dialogue garde-fou avant de lancer une fonction
 * @param title un titre pour le dialogue
 * @param message un message pour le dialogue
 * @param fun la fonction à appeler éventuellement
 * @param obj un objet passé comme argument
 * @param fun_esc la fonction à appeler éventuellement si on échappe
 * @param obj_esc un objet passé comme argument
 **/
function garde_fou(title, message, fun, obj, fun_esc, obj_esc){
    var d = $("<div id='garde-fou'>" + message +" ?</div>")
    $("body").append(d);
    d.dialog(
	{
	    title: title,
	    buttons: [
		{
		    text: I18N.Echappement,
		    click: function() {
			$( this ).dialog( "close" );
			d.remove;
			if (fun_esc) {window[fun_esc](obj_esc)}
		    }
		},
		{
		    text: I18N.Valider,
		    icon: "ui-icon-heart",
		    click: function() {
			$( this ).dialog( "close" );
			d.remove;
			if (fun) {window[fun](obj)};
		    }
		},
	    ],
	    modal: true,
	    width: 450
	}
    )
}

/**
 * Teste un sélecteur proposé dans la page /digraphe_select à l'aide
 * de code html fabriqué sur le gabarit update_digraphs.html
 * prend un code Python éventuellement douteux et lance une évaluation
 * de celui-ci ; si c'est bon renvoie une sélection d'objets et si ça
 * échoue, renvoie des messages d'erreur
 *
 * de plus, si c'est bon ça peuple les divs .autres-boutons
 * d'une série de raccourci pour injecter des mots dans le constructeur
 * de digraphe
 *
 * @param dd contient l'id d'un élément <dd>
 * @param mode vaut "l" pour une cause ou "r" pour un effet
 * @param garde optionnel, vrai par défaut pour déclencher le garde_fou
 **/
function test_selector(dd, mode, garde){
    var dd = $("#"+dd)
    var autres = dd.find(".autres-boutons")
    var expr = dd.find("input.select").val()
    var valeur = dd.find("input.val").val()
    var label = dd.find("input.label").val()
    garde = garde ?? true
    $.post("/test_selector", {
	csrfmiddlewaretoken: getCookie('csrftoken'),
	expr: expr,
	valeur: valeur,
	label: label,
	mode: mode,
    }, function(data){
	if (garde) garde_fou("Evaluation", data.message)
	autres.html(data.autres_boutons_html)
    })
}

/**
 * Affiche une liste de livres lob = list of books
 **/
function lob(){
    var nomprenom = $("#nomprenom").val()
    dejapris=[]
    // codes dans le tableau du haut
    $("#nouveaux_livres td.id_manuel").each( function (i, td){
	td = $(td)
	dejapris.push(td.text().trim())
    })
    // codes dans le tableau du bas
    $("table.tableau_prets td.id_manuel").each( function (i, td){
        td = $(td)
        dejapris.push(td.text().trim())
    })

    $.post(
	"lob",
	{
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	    nomprenom: nomprenom,
	    dejapris: JSON.stringify(dejapris)
	},
	function(data){
	    $("#lob .books").html(data.html)
	})
}


/**
 * Insère du texte visible sur un bouton dans un champ textarea
 * @param button le bouton émetteur du signal
 * @param dest le sélecteur d'un élément textarea
 * @param mode qui vaut "" ou "l" pour une cause ou "r" pour un effet
 **/
function new_text(button, dest, mode){
    var button=$(button)
    var ta = $(dest)
    var dot = $(dest).val()
    if (! dot) dot = 'digraph mon_graphe {\n# end nodes\n# end edges\n}'
    var ins = '"' + mode  + button.data("valeur") + '"' // pour l'arête
    var node = '"' + mode + button.data("valeur") + '" [label="' + button.data("label")  + '"]\n'
    if (dot.indexOf(node) < 0){
	// on insère un noeud s'il n'est pas déjà présent
	var pos = dot.indexOf('\n# end nodes') + 1
	if (pos >= 1) {
	    dot = dot.substr(0, pos) + node + dot.substr(pos)
	}
    }
    if (mode=="l") {
	ins += " -> "
	pos = dot.indexOf('\n# end edges') + 1
	if (pos >= 1) {
	    dot = dot.substr(0, pos) + ins + dot.substr(pos)
	}
    }
    if (mode=="r") {
	ins += "\n"
	pos = dot.indexOf('-> # end edges') + 3
	if (pos >= 3) {
	    dot = dot.substr(0, pos) + ins + dot.substr(pos)
	}
    }
    ta.val(dot)
}

/**
 * Création de nouveaux enregistrements pour un digraphe
 **/
function new_digraph(){
    var name = $("#new-name").val()
    var dot = $("#new-text").val()
    $.post(
	"new_digraph",{
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	    name: name.trim(),
	    dot: dot,
	    left_selector: $("#left input.select").val(),
	    left_val: $("#left input.val").val(),
	    left_text: $("#left input.label").val(),
	    right_selector: $("#right input.select").val(),
            right_val: $("#right input.val").val(),
	    right_text: $("#right input.label").val(),
	}, function(data){
	    garde_fou("Enregistrement des données", data.message)
	})
	
}

/**
 * copie le dot_code du TEXTAREA frère dans le textarea d'édition
 * @param button le bouton cliqué
 **/
function copy_digraph(button){
    var dot = $(button).parent().find("textarea").val()
    $("#new-text").val(dot)
}

/**
 * Supprime une instance de Digraphe_select sans garde-fous au préalable
 * @param name l'attribut name de l'instance à effacer
 **/
var del_digraphe_select_go = function(name){
    $.post(
	"del_digraphe_select",{
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	    name: name,
	}, function (data){
	    garde_fou("Suppression effectuée", name + "a été supprimé")
	    location.reload()
	})
}

/**
 * Supprime une instance de Digraphe_select
 **/
function del_digraphe_select(){
    var name = $("#dgs").data("name")
    garde_fou(
	"Suppression d'un sélecteur de digraphes",
	"Êtes-vous sûr de vouloir supprimer " + name,
	"del_digraphe_select_go",
	name
    )
}

/**
 * Renomme une instance de Digraphe_select sans garde-fous au préalable
 * @param obj un objet avec les attributs name1, name2
 **/
var mv_digraphe_select_go = function(obj){
    $.post(
	"mv_digraphe_select",{
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	    name1: obj.name1,
	    name2: obj.name2,
	}, function (data){
	    garde_fou("Renommage", obj.name1 + " a été renommé en " + obj.name2)
	    location.reload()
	})
}

/**
 * Renomme une instance de Digraphe_select
 **/
function mv_digraphe_select(){
    var name1 = $("#dgs").data("name")
    var name2 = $("#name2").val()
    garde_fou(
	"Changement de nom d'un sélecteur de digraphes",
	"Êtes-vous sûr de vouloir renommer " + name1 + " en " + name2,
	"mv_digraphe_select_go",
	{ name1: name1, name2: name2}
    )
}

/**
 * Minifie/déminifie la liste de livres à prendre dans /prets
 **/
function toggle_lap(){
    $("#livres-a-prendre").toggleClass("mini")
}

/**
 * Efface une caution : celle-ci ne sera jamais utilisée, l'élève ne se
 * présentera pas
 * @param sans_accent désigne un élève
 **/
function efface_caution(sans_accent){
    $.post(
	"/efface_caution", {
	    sans_accent: sans_accent,
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	}
    ).done(function(data){
	$("#messages").html(data.html)
	if (data.ok){
	    //dialogue avec boutons et actions ensuite
	    $("#messages").dialog({
		modal: true,
		buttons: [
		    {
			text: I18N.Echappement,
			click: function() {
			    $( this ).dialog( "close" );
			}
		    },
		    {
			text: I18N.Valider,
			icon: "ui-icon-heart",
			click: function() {
			    $( this ).dialog( "close" );
			    efface_caution_go()
			}
		    },
		],
	    })
	} else {
	    // simple dialogue sans boutons
	    $("#messages").dialog()
	}
    })
}

/**
 * Efface une caution pour de bon ; les données à examiner sont à deux places
 * dans la fenêtre : dans #nomprenom et dans #n
 **/
function efface_caution_go(){
    $.post(
	"/efface_caution_go",
	{
	    nomprenom: $("#nomprenom").val(),
	    n: $("#n").val(),
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	},
	function (data){
	    if (data.ok){
		// on réaffiche la zone de données de la page
		affiche_caution_eleve($("#nomprenom").val())
	    } else {
		$("#messages").html(data.html)
		$("#messages").dialog()
	    }
	}
    )
}

var loc_reload = function(){
    location.reload()
}

/**
 * Fonction de rappel pour enregistrer un sélecteur de digraphe
 * depuis la page /digraphe_select (bouton d'enregistrement)
 * @param btn le bouton d'enregistrement
 **/
function save_digraphe_select(btn){
    btn = $(btn)
    var name = $("#name0").val()
    var left = $("#left")
    var right = $("#right")
    $.post(
	"/save_digraphe_select",
	{
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	    name: name,
	    left_selector: left.find("input.select").val(),
	    left_val:      left.find("input.val").val(),
	    left_text:     left.find("input.label").val(),
	    left_ordering: "",
	    right_selector: right.find("input.select").val(),
	    right_val:      right.find("input.val").val(),
	    right_text:     right.find("input.label").val(),
	    right_ordering: "",
	},
	function(data){
	    // signale que la sauvegarde est OK, ou pas.
	    garde_fou(
		"Enregistrement", data.html,
		"loc_reload", null,
		"loc_reload", null
	    )
	})
}

/**
 * Recomptage des livres selon les options de la population d'élèves actuelle
 * @param ident un identifiant de matériel
 * @param btn un bouton
 **/
function recompte_livres(ident, btn){
    btn = $(btn)
    $.post("/recompte_livres",{
	csrfmiddlewaretoken: getCookie('csrftoken'),
	ident: ident,
    }, function(data){
	if(data.ok == "OK"){
	    // un recomptage a eu lieu, on peut
	    // remettre à jour la page
	    btn.find(".compte").text(data.compte)
	    btn.find(".date").text(data.date)
	}
    })
}

/**
 * lance un routine de comptage des manuels nécessaires pour la cohorte
 * actuelle d'élèves, compte tenu de leurs besoins.
 **/
function recompte_pour_maintenant(){
    $.post("recompte_pour_maintenant",{
	csrfmiddlewaretoken: getCookie('csrftoken'),
    }, function(data){
	if (data.ok) location.reload()
    })
}

/**
 * pour produire un document avec les élèves devant recevoir un livre
 * @param ident l'id de ce livre
 **/
function pour_qui_ce_livre(ident){
    $.post("/pqcl",{
	csrfmiddlewaretoken: getCookie('csrftoken'),
	ident: ident,
    })

}

/**
 * mise en place de l'afficheur de stock dans la page manuels_statut
 **/
function manuels_statut_init(){
    $.post(
	"/en_stock_more",
	{
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	    mode: "en-stock",
	},
	function(data){
	    data.manuels.forEach(m => {
		var manuel = $("<div class='flexmini'></div>")
		manuel.text(
		    "id = " + m[0] + ", " + m[1]
		)
		$("#en-stock").append(manuel)
	    })
	}
    )
}

/**
 * affiche un dialogue pour régler les problèmes de confusions possibles
 * liés à un nom_prenom d'élève
 * @param nom_prenom la propriété sans_accent d'une instance d'Eleves
 **/
function fixEleveConfus(nom_prenom){
    var div = $("#message")
    $.post(
	"fixEleveConfus",
	{
	    csrfmiddlewaretoken: getCookie('csrftoken'),
	    sans_accent: nom_prenom,
	},
	function(data){
	    if (data.ok){
		div.html(data.html)
		div.dialog({
		    modal:true,
		    width: 1000,
		    title: data.title,
		})
	    }
	}
    )
    
}

/**
 * fonction de rappel après qu'un choix a été validé dans le dialogue des noms
 * d'élèves confus
 **/
function validConfus() {
    var msgdiv = $("#message")
    var radios = msgdiv.find("div.choix input")
    console.log("GRRRR radios =", radios)
}

/**
 * fonction de rappel après qu'un choix a été modifié dans le dialogue des noms
 * d'élèves confus
 * @param elt l'élément qui a changé
 **/
function changeConfus(elt) {
    var elt = $(elt)
    console.log("GRRR changement :", elt)
    $("div.if-checked").hide() // cache tous les champs complémentaire
    elt.parent().find("div.if-checked").show() // révèle ceux qu'il faut
}

/**
 * fonction de rappel pour montrer les détails d'une caution, dans
 * la div $("#messages")
 * @param no_caution un numéro de caution
 **/
function montre_caution(no_caution){
    $.post("montre_caution", {
	no_caution, no_caution,
	csrfmiddlewaretoken: getCookie('csrftoken'),
    }, function (data){
	if (data.ok){
	    $("#messages").html(data.html).dialog({
		title: data.title,
		modal: true,
		buttons: [
		    {
			text: I18N.Echappement,
			click: function() {
			    $( this ).dialog( "close" );
			}
		    },
		],
	    })
	}
    })
}
	  
/**
 * Fonction de rappel qui est activée dans la page élèves quand le
 * champ nomprenom contient un élève inconnu (variable `valide` False)
 **/
function eleve_inconnu(title){
  $("#nouvel-eleve").dialog({
      title: title,
      width: 800,
      buttons: [
  {
      text: "Ok",
      icon: "ui-icon-heart",
      click: function() {
	  $( this ).dialog( "close" );
	  $("#neo").submit()
      }
  },
  {
      text: "Esc",
      click: function() {
	  $( this ).dialog( "close" );
      }
  },
      ],
  })
}
