/**
 * @file Ce fichier contient la classe de l'API du LDAP qui gère les opérations sur les groupes. Elle est destinée à être exportée pour être utilisée par les resolvers.
 * @author hawkspar
 * @memberof LDAP
 */

import { ldapConfig, categories } from '../internal/config';
import {Basics} from '../internal/basics';
import {Tools} from '../internal/tools';

//------------------------------------------------------------------------------------------------------------------------
// Type à exporter
//------------------------------------------------------------------------------------------------------------------------

/**
 * @memberof LDAP
 * @class groupData
 * @var {string} gid - Identifiant du groupe
 * @var {string} name - Nom du groupe (souvent son nom mais pas nécessairement)
 * @var {"association"|"free"|"formation"|"promo"|"cours"|"sport"} category - Statut du groupe ; association, section sportive... (actuellement juste 'association' ou 'free')
 * @var {string[]} parents - Liste des groupes directement parents de celui-ci
 * @var {string[]} admins - Liste des admins du groupe
 * @var {string[]} admins2 - Liste des admins du groupe, mais avec une synthaxe différente
 * @var {string[]} speakers - Liste des porte-parole du groupe
 * @var {string[]} members - Liste des membres du groupe
 * @var {string[]} followers - Liste des sympathisants du groupe
 * @var {string?} logo - Logo du groupe (en bytestring)
 * @var {string?} urlLogo - Lien vers le logo du groupe
 * @var {string?} description - Description du groupe (script Markdown)
 * @var {string?} email - Mail pour joindre le groupe
 * @var {string?} site - Site web du groupe (URL)
 * @var {string?} adress - Local du groupe
 * @var {string?} idNumber - Identifiant numérique unique du groupe
 */
export class groupData {
    gid: string;
	name: string;
	category: "association"|"free"|"formation"|"promo"|"cours"|"sport";
    parents: string[] = [];
    admins: string[] = [];
    admins2: string[] = [];
    speakers: string[] = [];
    members: string[] = [];
    followers: string[] = [];
	logo?: string;
	urlLogo?: string;
    description?: string;
    email?: string;
    site?: string;
    adress?: string;
    idNumber?: string
}

//------------------------------------------------------------------------------------------------------------------------
// Classes à exporter TBT
//------------------------------------------------------------------------------------------------------------------------

export class Group {
    /**
     * @memberof LDAP
     * @class Group
     * @classdesc Cette classe est une des deux classes exportables permettant de faire des opérations sur les groupes.
     * @summary Constructeur vide.
     */
    constructor() {}
     
    /**
     * @memberof LDAP
     * @summary Fonction qui renvoit toutes les infos relatives à un groupe particulier.
     * @desc Cette fonction utilise {@link Tools.peek} avec l'interface {@link groupData}. Elle ne fait que consulter le groupe sans le changer, et en extrayant les uid des membres.
     * @arg {string} gid - Identifiant du groupe
     * @return {Promise(groupData)} Informations recueillies ; renvoie une liste de dictionnaire avec le profil complet du groupe au format {@link groupData}.
     * @static
     * @async
     */
    static async peek(gid: string) : Promise<groupData> {
        try {
            let data = await Tools.peek<groupData>("group", gid, groupData);
            // Extraction des uid de parents
            data["parents"] = data["parents"].map(dn => dn.split(',')[0].split('=')[1]);
            return data;
        }
        catch(err) { throw "Erreur lors d'une recherche d'informations sur un groupe."; }
    }

    /**
     * @memberof LDAP
     * @summary Fonction qui retrouve le groupe qui ressemblent à l'input et qui correspond au type fourni. Etape 0 vers un vrai TOL (Trombino On Line) pour les groupes.
     * @desc Cette fonction utilise {@link Tools.search}.
     * @arg {groupData} input - String entré par l'utilisateur qui ressemble au nom du groupe.
     * @return {Promise(string[])} Liste des gid dont le nom ressemble à l'input.
     * @static
     * @async
    */
    static async search(data: groupData) : Promise<string[]> {
        try         { return Tools.search("group", data); }
        catch(err)  { throw "Erreur lors de la recherche approximative d'un groupe."; }
    }

    /**
     * @memberof LDAP
     * @summary Fonction qui permet de rajouter un administrateur à un groupe.
     * @desc Cette fonction fait essentiellement appel à {@link Tools.add}. Le nouvel administrateur ne devient pas membre ou porte-parole du groupe pour autant !
     * @arg {string} uid - Identifiant du membre futur admin
     * @arg {string} gid - Identifiant du groupe
     * @return {boolean} `true` si la modification s'est bien déroulée, false sinon
     * @async
     * @static
     */
    static async addAdmin(uid: string, gid: string): Promise<boolean> { return Tools.add(uid, gid, "admins"); }

    /**
     * @memberof LDAP
     * @summary Fonction qui permet de supprimer un administrateur.
     * @desc Cette fonction fait essentiellement appel à {@link Tools.remove}.
     * Elle ne remonte pas les échelons, car cela permettrait à un admin d'un petit groupe de supprimer un admin d'un grand. 
     * @arg {string} uid - Identifiant de l'admin à dégrader, supposé membre
     * @arg {string} gid - Identifiant du groupe
     * @return {boolean} `true` si la modification s'est bien déroulée, false sinon
     * @async
     * @static
     */
    static async remAdmin(uid: string, gid: string): Promise<boolean> { return Tools.remove(uid, gid, "admins"); }

    /**
     * @memberof LDAP
     * @summary Fonction qui permet de rajouter un porte-parole à un groupe.
     * @desc Cette fonction fait essentiellement appel à {@link Tools.add}. Elle ne rajoute pas l'utilisateur au groupe.
     * @arg {string} uid - Identifiant du membre futur porte-parole
     * @arg {string} gid - Identifiant du groupe
     * @return {boolean} `true` si la modification s'est bien déroulée, false sinon
     * @async
     * @static
     */
    static async addSpeaker(uid: string, gid: string): Promise<boolean> { return Tools.add(uid, gid, "speakers"); }

    /**
     * @memberof LDAP
     * @summary Fonction qui permet de rétrograder un membre du stade de porte-parole d'un groupe au stade d'utilisateur.
     * @desc Cette fonction fait essentiellement appel à {@link Tools.remove}. Elle dégrade aussi d'un éventuel statut d'administrateur.
     * @arg {string} uid - Identifiant de l'admin à dégrader (pas supposé valide)
     * @arg {string} gid - Identifiant du groupe
     * @return {boolean} `true` si la modification s'est bien déroulée, false sinon
     * @async
     * @static
     */
    static async remSpeaker(uid: string, gid: string): Promise<boolean> { return Tools.remove(uid, gid, "speakers"); }

    /**
     * @memberof LDAP
     * @summary Fonction qui permet d'ajouter un utilisateur à un groupe.
     * @desc Cette fonction fait essentiellement appel à {@link Tools.add}.
     * @arg {string} uid - Identifiant de l'utilisateur à ajouter
     * @arg {string} gid - Identifiant du groupe
     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
     * @async
     * @static
     */
    static async addMember(uid: string, gid: string) : Promise<boolean> { return Tools.add(uid, gid, "members"); }

    /**
     * @memberof LDAP
     * @summary Fonction qui permet de supprimer un membre existant d'un groupe.
     * @desc Cette fonction fait essentiellement appel à {@link Tools.add}.
     * Cette fonction supprime tous les droits de l'utilisateur sur le groupe, mais aussi sur les groupes sources si son statut de membre était hérité.
     * @arg {string} uid - Identifiant de l'ex-membre (pas supposé valide)
     * @arg {string} gid - Identifiant du groupe
     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
     * @async
     * @static
     */
    static async remMember(uid: string, gid: string): Promise<boolean> { return Tools.remove(uid, gid, "members"); }

    /**
     * @memberof LDAP
     * @summary Fonction qui permet d'ajouter un sympathisant à un groupe.
     * @desc Cette fonction fait essentiellement appel à {@link Tools.add}.
     * @arg {string} uid - Identifiant de l'utilisateur à ajouter
     * @arg {string} gid - Identifiant du groupe
     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
     * @async
     * @static
     */
    static async addFollower(uid: string, gid: string) : Promise<boolean> { return Tools.add(uid, gid, "followers"); }

    /**
     * @memberof LDAP
     * @summary Fonction qui permet de supprimer un sympathisant d'un groupe.
     * @desc Cette fonction fait essentiellement appel à {@link Tools.add}.
     * @arg {string} uid - Identifiant de l'ex-sympathisant (pas supposé valide)
     * @arg {string} gid - Identifiant du groupe
     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
     * @async
     * @static
     */
    static async remFollower(uid: string, gid: string): Promise<boolean> { return Tools.remove(uid, gid, "followers"); }

    /**
     * @memberof LDAP
     * @summary Fonction intermédiaire qui reformatte un fichier User en LDAP-friendly pour tous les champs éditables
     * @desc Cette fonction n'est pas asynchrone !
     * @arg {userData} data - Dictionnaire de données utilisateurs
     * @arg {dic} vals - Dictionnaire passé par référence pour être enrichi des données utilisateurs reformattées
     */
    static reformat(data: groupData, vals : any) : void {
        // Ecriture de toutes les valeurs directement inscrites dans le LDAP
        for (let key_att of ["name","logo","urlLogo","description","site","email","category","adress"]) {
            if (data[key_att] != undefined) vals[ldapConfig.group[key_att]]=data[key_att]
        };

        // Elimination des accents
        vals[ldapConfig.user['nameEn']] = data['name'].normalize('NFD').replace(/[\u0300-\u036f]/g, "");
    }

    /**
     * @memberof LDAP
     * @summary Fonction qui créé un nouveau groupe dans le LDAP.
     * @desc Cette fonction fait une utilisation massive d'eval pour anonymiser son code ; c'est mal et cela suppose que beaucoup de soins ont été pris lors de
     * l'escape de ses paramètres. Appelle {@link LDAP.add} et {@link LDAP.change}, mais aussi {@link Tools.add}
     * pour gérer les groupes du nouvel utilisateur. Cettte application permet de rajouter des utilisateurs à toutes les catégories du groupe.
     * @arg {groupData} data - Dictionnaire des informations utilisateurs (voir détail des champs dans ldapConfig.json)
     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
     * @async
     * @static
     */
    static async create(data: groupData) : Promise<boolean> {
        // Calcul d'un dictionnaire d'ajout
        let vals = {};

        // gid de base généré à partir du nom standardisé, pas à partir de l'entrée 'gid' !
        try {
            Tools.generateGid(data['name']).then(id => {
                vals[ldapConfig.group.gid]      = id;
                vals[ldapConfig.group['name']]  = id;
            });
        }
        catch(err) {
            throw "Erreur lors de la génération d'un hruid pour créer un nouveau groupe.";
        }

        let gid: string = vals[ldapConfig.group.gid];

        // Réécriture données groupes en mode LDAP-friendly
        Group.reformat(data, vals);

        // Appel à la fonction de base
        if (!await Basics.add("group", vals)) throw "Erreur lors de la création d'une nouvelle feuille dans l'arbre des groupes.";

        // Certains champs nécessitent de petits calculs
        
        // IMPORTANT
        if (!await Basics.change("group", gid, "add", { [ldapConfig.group['idNumber']]: await Tools.generateId(ldapConfig.user['idNumber'], "group") })) {
            throw "Erreur lors de l'ajout de l'identifiant numérique du nouveau groupe.";
        }

        ["posixGroup", "brGroup"].forEach(cst => {
            let vals3 = { [ldapConfig.group['classes']] : cst };
            Basics.change("group", gid, "add", vals3).then(res => {
                if (!res) throw "Erreur lors de l'ajout des valeurs constantes du nouveau groupe.";
            });
        });
        // Ajout groupes parents et fils
        data["parents"].forEach(gid => {
            Basics.change("group", gid, "add", { [ldapConfig.group["parents"]]: gid }).then(res => {
                if (!res) throw "Erreur de l'ajout d'un groupe associé au nouveau groupe.";
            });
        });
        // Utilisation des fonctions adaptées pour assurer la cohérence de l'ensemble
        for (let cat of categories) {
            for (let uid of data[cat]) {
                if (!await Tools.add(uid, gid, cat)) throw "Erreur de l'ajout d'un membre au nouveau groupe.";
            }
        }
        return true;
    }

    /**
     * @memberof LDAP
     * @summary Fonction qui supprime un groupe du LDAP.
     * @desc Cette fonction commence par gérer les groupes du membre puis le supprime entièrement. A modifier une fois que le LDAP incluerait les groupes administres par une utilisateur.
     * Appelle {@link LDAP.clear} bien sûr, mais aussi {@link Tools.remove} pour gérer les groupes de l'utilisateur sortant.
     * @arg {string} gid - gid de la victime
     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
     * @async
     * @static
     */
    static async delete(gid: string): Promise<boolean> {
        try {
            // Gestion des catégories en bloc d'abord
            let profile = await Group.peek(gid);
            for (let cat of categories) profile[ldapConfig.group[cat]].forEach(uid => Tools.remove(uid, gid, cat));
            // Elimination
            if (!await Basics.clear("group",gid)) throw "Erreur lors de la suppression de la feuille dans l'arbre des groupes.";
            return true;
        }
        catch(err) {
            throw "Erreur lors de l'obtention du profil d'un groupe pour le supprimer.";
        }
    }

    /**
     * @memberof LDAP
     * @summary Fonction qui édite un groupe existant dans le LDAP. Sans influence sur ses membres ou admins. 
     * @desc Appelle {@link Group.delete} suivi de {@link Group.create} pour mener son oeuvre de destruction créatrice.
     * Cette méthode ne permet pas de rejoindre aucun groupe, mais elle permet de changer son mot de passe, image, ou même son nom.
     * @arg {groupData} data - Dictionnaire des informations du groupe.
     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
     * @async
     * @static
     */
    static async edit(data: groupData) : Promise<boolean> {
        try {
            let vals = {};
            Group.reformat(data, vals);
            // Modification par effet de bord
            return Basics.change("user", data["gid"], "replace", vals);
        }
        catch(err) {
            throw "Erreur lors de la modification d'un groupe.";
        }
    }
}