/**
 * Namespace qui regroupe toutes les classes et toutes les fonctions qui permettent l'implémentation des resolvers du schéma GraphQL
 * @namespace GraphQL
 */

/** 
 * @file Fonctions génériques de recherche dans la BDD et le LDAP réutilisables dans l'authorisation et/ou dans les resolvers
 * @author ofacklam
 * @memberof GraphQL
 */

import { User as UT, userData } from '../../ldap/export/user'
import knex from '../../../db/knex_router';

export class GroupSet extends Set<string> {
    addList(l: string[]) {
        for(let elt of l) {
            this.add(elt);
        }
    }
}
export interface GroupCollection {
    simpleGroups: GroupSet,
    metaGroups: GroupSet
}

export class Tools {

    /**
     * @memberof GraphQL
     * @class Tools
     * @summary Outils intermédiaires LDAP / BDD.
     * @classdesc Cette classe contient des fonctions intermédiaires de recherche dans le LDAP / la BDD.
     */
    constructor() { }

    /**
     * @memberof GraphQL
     * @summary Fonction qui escape l'id donné en argument
     * @arg {string} id - L'identifiant a escape.
     * @return {Promise(Group)} Renvoie l'identifiant escapé.
     * @static
     */
    static escapeID(id: string) {
        return String(id).replace(' ', '_').replace(/\W/g, '').toLowerCase(); //the REGEXP matches all non-word characters (\W) in a global search (/../g)
    }

    /**
     * @memberof GraphQL
     * @summary Fonction qui renvoit l'union de deux ensembles
     * @arg {GroupSet} A - Le premier ensemble.
     * @arg {GroupSet} B - Le deuxieme ensemble.
     * @return {Promise(GroupSet)} Renvoie un nouveau GroupSet contenant l'union de A et B.
     * @static
     */
    static union(A: GroupSet, B: GroupSet): GroupSet {
        let union = new GroupSet(A);
        for(let b of B) {
            union.add(b);
        }
        return union;
    }

    /**
     * @memberof GraphQL
     * @summary Fonction qui renvoit tous les groupes simples dont le user est membre.
     * @arg {userData} data - Données de l'utilisateur.
     * @return {Promise(GroupSet)} Renvoie un GroupSet contenant le nom des groupes simples.
     * @static
     * @async
     */
    static async memberOfSimple(data: userData): Promise<GroupSet> {
        return new GroupSet(data.groups);
    }

    /**
     * @memberof GraphQL
     * @summary Fonction qui renvoit tous les groupes simples dont le user est speaker.
     * @arg {userData} data - Données de l'utilisateur.
     * @return {Promise(GroupSet)} Renvoie un GroupSet contenant le nom des groupes simples.
     * @static
     * @async
     */
    static async speakerOfSimple(data: userData): Promise<GroupSet> {
        throw "Not implemented";
    }

    /**
     * @memberof GraphQL
     * @summary Fonction qui renvoit tous les groupes simples dont le user est administrateur.
     * @arg {userData} data - Données de l'utilisateur.
     * @return {Promise(GroupSet)} Renvoie un GroupSet contenant le nom des groupes simples.
     * @static
     * @async
     */
    static async adminOfSimple(data: userData): Promise<GroupSet> {
        return new GroupSet(data.groupsIsAdmin);
    }

    /**
     * @memberof GraphQL
     * @summary Fonction qui renvoit tous les méta-groupes dont ces groupes sont membres.
     * @arg {GroupSet} groups - Un ensemble de gid des groupes a considérer.
     * @return {Promise(GroupSet)} Renvoie un GroupSet contenant le nom des méta-groupes.
     * @static
     * @async
     */
    static async metaGroupsOfGroups(groups: GroupSet): Promise<GroupSet> {
        let metas = await knex.select('meta_group_gid').from('metagroup_memberships').whereIn('simple_group_gid', groups);
        return new GroupSet(metas.map( elt => {
            return elt.meta_group_gid;
        }));
    }

    /**
     * @memberof GraphQL
     * @summary Fonction qui renvoit tous les groupes (simples ou méta) dont le user est membre.
     * @arg {userData} data - Données de l'utilisateur.
     * @return {Promise(GroupCollection)} Renvoie une GroupCollection contenant le nom des groupes. 
     * @static
     * @async
     */
    static async memberOf(data: userData): Promise<GroupCollection> {
        let simple = await Tools.memberOfSimple(data);
        return { simpleGroups: simple, metaGroups: await Tools.metaGroupsOfGroups(simple) };
    }

    /**
     * @memberof GraphQL
     * @summary Fonction qui renvoit tous les groupes (simples ou méta) dont le user est speaker.
     * @arg {userData} data - Données de l'utilisateur.
     * @return {Promise(GroupCollection)} Renvoie une GroupCollection contenant le nom des groupes. 
     * @static
     * @async
     */
    static async speakerOf(data: userData): Promise<GroupCollection> {
        let speaker = await Tools.speakerOfSimple(data);
        let admin = await Tools.adminOfSimple(data);
        return { simpleGroups: speaker, metaGroups: await Tools.metaGroupsOfGroups(admin) };
    }

    /**
     * @memberof GraphQL
     * @summary Fonction qui renvoit tous les groupes (simples ou méta) dont le user est administrateur.
     * @arg {userData} data - Données de l'utilisateur.
     * @return {Promise(GroupCollection)} Renvoie une GroupCollection contenant le nom des groupes.
     * @static
     * @async
     */
    static async adminOf(data: userData): Promise<GroupCollection> {
        let simple = await Tools.adminOfSimple(data);
        return { simpleGroups: simple, metaGroups: await Tools.metaGroupsOfGroups(simple) };
    }

    /**
     * @memberof GraphQL
     * @summary Fonction qui fait un parcours en profondeur de l'arbre de racine `gid` et renvoie la liste de tous les noeuds.
     * @arg {string} gid - Identifiant du groupe, supposé valide.
     * @return {Promise(string[])} Renvoie une liste contenant le nom des groupes dans cet arbre.
     * @static
     * @async
     */
    static async DFS(gid: string): Promise<string[]> {
        let res = [gid];
        let children = await knex.select('gid').from('groups').where('parent_gid', gid);

        for(let child of children) {
            let sub_tree = await Tools.DFS(child.gid);
            res = res.concat(sub_tree);
        }

        return res;
    }

    /**
     * @memberof GraphQL
     * @summary Fonction qui fait un parcours a profondeur 1 de l'arbre de racine `gid` et renvoie la liste des noeuds.
     * @arg {string} gid - Identifiant du groupe, supposé valide.
     * @return {Promise(string[])} Renvoie une liste contenant le nom des groupes a profondeur 1.
     * @static
     * @async
     */
    static async oneDownSearch(gid: string): Promise<string[]> {
        let res = [gid];
        let children = await knex.select('gid').from('groups').where('parent_gid', gid);

        for (let child of children) {
            res.push(child.gid);
        }

        return res;
    }

    /**
     * @memberof GraphQL
     * @summary Fonction qui renvoie tous les groupes dont l'utilisateur est supervisor.
     * @desc Utilise {@link Tools.oneDownSearch} pour avoir la profondeur 1 des arbres enracinés en chacun des groupes dont il est admin.
     * @arg {userData} data - Données du user.
     * @return {Promise(GroupCollection)} Renvoie une GroupCollection contenant le nom des groupes.
     * @static
     * @async
     */
    static async supervisorOf(data: userData): Promise<GroupCollection> {
        let groups = await Tools.adminOf(data);

        let simple = new GroupSet();
        let meta = new GroupSet();

        for(let g of groups.simpleGroups) {
            simple.addList(await Tools.oneDownSearch(g));
        }
        for(let g of groups.metaGroups) {
            meta.addList(await Tools.oneDownSearch(g));
        }
    
        return { simpleGroups: simple, metaGroups: meta };
    }

    /**
     * @memberof GraphQL
     * @summary Fonction qui renvoie tous les groupes dont l'utilisateur est viewer.
     * @desc Utilise {@link Tools.oneDownSearch} pour avoir la profondeur 1 des arbres enracinés en chacun des groupes dont il est membre.
     * @arg {userData} data - Données du user.
     * @return {Promise(GroupCollection)} Renvoie une GroupCollection contenant le nom des groupes.
     * @static
     * @async
     */
    static async viewerOf(data: userData): Promise<GroupCollection> {
        let groups = await Tools.memberOf(data);

        let simple = new GroupSet();
        let meta = new GroupSet();

        //Ajouter les groupes dont il est membre + les fils a profondeur 1
        for(let g of groups.simpleGroups) {
            simple.addList(await Tools.oneDownSearch(g));
        }
        for(let g of groups.metaGroups) {
            meta.addList(await Tools.oneDownSearch(g));
        }

        //Ajouter les groupes simples qui sont dans ses métagroupes
        let s = await knex.select('simple_group_gid').from('metagroup_memberships').whereIn('meta_group_gid', groups.metaGroups);
        simple.addList(s.map( elt => {
            return elt.simple_group_gid;
        }));

        return { simpleGroups: simple, metaGroups: meta };
    }
}