/**
 * @module
 * Fonctions de requêtage des données d’exemple.
 */

import {termTypes} from '../model.js';
import * as data from './data.js';

/**
 * Recherche l’ensemble des maladies liées par une relation « a pour symptôme »
 * avec un ensemble de termes donné.
 *
 * @param query Ensemble de termes attendus.
 * @return Liste des maladies correspondantes.
 */
export const diseasesBySymptoms = async query =>
{
    let allMatches = [];

    if (!query.length)
    {
        // Si aucun terme dans la requête, tout correspond
        allMatches = Object.values(data.terms).map(({id}) => id);
    }
    else
    {
        // Marqueurs indiquant pour chaque terme l’ensemble des éléments de la
        // requête qu’il a pour symptôme au travers d’une relation directe ou
        // transitive
        const matchingSymptoms = {};

        // Réalise un parcours en profondeur du graphe en partant de chaque terme
        // de la requête pour marquer les résultats
        for (let queryTerm of query)
        {
            const stack = [queryTerm.id];

            while (stack.length)
            {
                const current = stack.pop();
                const neighbors = data.symptomOf.filter(
                    ([from]) => from === current
                ).map(
                    ([, to]) => to
                );

                for (let neighbor of neighbors)
                {
                    if (!(neighbor in matchingSymptoms))
                    {
                        matchingSymptoms[neighbor] = [];
                    }

                    if (!matchingSymptoms[neighbor].includes(queryTerm.id))
                    {
                        matchingSymptoms[neighbor].push(queryTerm.id);
                        stack.push(neighbor);
                    }
                }
            }
        }

        // Seuls les termes ayant été visités par tous les éléments de la
        // requête sont des résultats valides
        allMatches = Object.entries(matchingSymptoms).filter(
            ([, matches]) => matches.length === query.length
        ).map(
            ([id]) => id
        );
    }

    // On ne garde que les maladies
    return allMatches.map(
        id => data.terms[id]
    ).filter(
        term => term.types.includes(termTypes.disease)
    );
};

/**
 * Récupère tous les termes liés à un ensemble de termes par une relation « a
 * pour symptôme », directe ou transitive.
 *
 * @param terms Ensemble de termes initiaux.
 * @return Termes de `terms` ainsi que leurs voisins par la relation symptôme.
 */
export const exploreSymptoms = async terms =>
{
    // Voisins des termes de la requête
    const selected = terms.map(({id}) => id);

    // Fait un parcours en profondeur issu de chaque résultat pour obtenir
    // tous les voisins directs ou transitifs par la relation « a pour
    // symptôme »
    const stack = [...selected];

    while (stack.length)
    {
        const current = stack.pop();
        const neighbors = data.hasSymptom.filter(
            ([from]) => from === current
        ).map(
            ([, to]) => to
        );

        for (let neighbor of neighbors)
        {
            if (!selected.includes(neighbor))
            {
                selected.push(neighbor);
                stack.push(neighbor);
            }
        }
    }

    return selected.map(id => data.terms[id]);
};

/**
 * Calcule le sous-graphe issu d’un ensemble de termes.
 *
 * @param terms Ensemble de termes à mettre dans le sous-graphe.
 * @return Objet contenant les nœuds et les arêtes du graphe.
 */
export const symptomsSubgraph = async terms =>
{
    const termsIds = terms.map(({id}) => id);

    // Construction du graphe constitué des éléments sélectionnés et des
    // arêtes qui les lient
    const nodes = {};
    const edges = [];

    for (let term of terms)
    {
        nodes[term.id] = term;
    }

    // Sélection des arêtes liant les nœuds sélectionnés
    for (let [from, to] of data.hasSymptom)
    {
        if (
            termsIds.includes(from)
            && termsIds.includes(to)
        )
        {
            edges.push([from, to]);
        }
    }

    return {nodes, edges};
};
