/* Chrysalide - Outil d'analyse de fichiers binaires
 * workqueue.c - gestion des travaux différés
 *
 * Copyright (C) 2009-2024 Cyrille Bagard
 *
 *  This file is part of Chrysalide.
 *
 *  Chrysalide is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Chrysalide is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Chrysalide.  If not, see <http://www.gnu.org/licenses/>.
 */


#include "workqueue.h"


#include <assert.h>
#include <malloc.h>


#include "workqueue-int.h"



/* Initialise la classe des travaux différés. */
static void g_work_queue_class_init(GWorkQueueClass *);

/* Initialise une instance de gestionnaire de travaux différés. */
static void g_work_queue_init(GWorkQueue *);

/* Supprime toutes les références externes. */
static void g_work_queue_dispose(GWorkQueue *);

/* Procède à la libération totale de la mémoire. */
static void g_work_queue_finalize(GWorkQueue *);

/* Donne l'assurance de l'existence d'un groupe de travail. */
static bool g_work_queue_ensure_group_exists(GWorkQueue *, wgroup_id_t, guint);

/* Fournit le groupe de travail correspondant à un identifiant. */
static GWorkGroup *g_work_queue_find_group_for_id(GWorkQueue *, wgroup_id_t);



/* Indique le type défini pour le gestionnaire des travaux différés. */
G_DEFINE_TYPE(GWorkQueue, g_work_queue, G_TYPE_OBJECT);


/******************************************************************************
*                                                                             *
*  Paramètres  : klass = classe à initialiser.                                *
*                                                                             *
*  Description : Initialise la classe des travaux différés.                   *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_work_queue_class_init(GWorkQueueClass *klass)
{
    GObjectClass *object;                   /* Autre version de la classe  */

    object = G_OBJECT_CLASS(klass);

    object->dispose = (GObjectFinalizeFunc/* ! */)g_work_queue_dispose;
    object->finalize = (GObjectFinalizeFunc)g_work_queue_finalize;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : queue = instance à initialiser.                              *
*                                                                             *
*  Description : Initialise une instance de gestionnaire de travaux différés. *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_work_queue_init(GWorkQueue *queue)
{
    queue->generator = 0;

    queue->groups = NULL;
    queue->groups_count = 0;
    g_mutex_init(&queue->mutex);
    g_cond_init(&queue->wait_all);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : queue = instance d'objet GLib à traiter.                     *
*                                                                             *
*  Description : Supprime toutes les références externes.                     *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_work_queue_dispose(GWorkQueue *queue)
{
    size_t i;                               /* Boucle de parcours          */

    g_mutex_lock(&queue->mutex);

    for (i = 0; i < queue->groups_count; i++)
        g_clear_object(&queue->groups[i]);

    g_mutex_unlock(&queue->mutex);

    g_mutex_clear(&queue->mutex);
    g_cond_clear(&queue->wait_all);

    G_OBJECT_CLASS(g_work_queue_parent_class)->dispose(G_OBJECT(queue));

}


/******************************************************************************
*                                                                             *
*  Paramètres  : queue = instance d'objet GLib à traiter.                     *
*                                                                             *
*  Description : Procède à la libération totale de la mémoire.                *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_work_queue_finalize(GWorkQueue *queue)
{
    if (queue->groups != NULL)
        free(queue->groups);

    G_OBJECT_CLASS(g_work_queue_parent_class)->finalize(G_OBJECT(queue));

}


/******************************************************************************
*                                                                             *
*  Paramètres  : -                                                            *
*                                                                             *
*  Description : Créé un nouveau gestionnaire de tâches parallèles.           *
*                                                                             *
*  Retour      : Gestionnaire de traitements mis en place.                    *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GWorkQueue *g_work_queue_new(void)
{
    GWorkQueue *result;                     /* Instance à retourner        */

    result = g_object_new(G_TYPE_WORK_QUEUE, NULL);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
*                id    = identifiant d'un groupe de travail.                  *
*                count = quantité de threads à allouer (0 pour un défaut).    *
*                                                                             *
*  Description : Donne l'assurance de l'existence d'un groupe de travail.     *
*                                                                             *
*  Retour      : true si un nouveau groupe a été constitué, false sinon.      *
*                                                                             *
*  Remarques   : Le verrou d'accès doit être posé par l'appelant.             *
*                                                                             *
******************************************************************************/

static bool g_work_queue_ensure_group_exists(GWorkQueue *queue, wgroup_id_t id, guint count)
{
    bool found;                             /* Bilan des recherches        */
    size_t i;                               /* Boucle de parcours          */
    GWorkGroup *group;                      /* Groupe à consulter          */

    assert(!g_mutex_trylock(&queue->mutex));

    found = false;

    for (i = 0; i < queue->groups_count && !found; i++)
    {
        group = queue->groups[i];
        found = (g_work_group_get_id(group) == id);
    }

    if (!found)
    {
        queue->groups_count++;
        queue->groups = realloc(queue->groups, queue->groups_count * sizeof(GWorkGroup *));

        group = g_work_group_new(id, count);
        queue->groups[queue->groups_count - 1] = group;

    }

    return !found;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
*                count = quantité de threads à allouer (0 pour un défaut).    *
*                                                                             *
*  Description : Constitue un nouveau groupe de travail.                      *
*                                                                             *
*  Retour      : Nouvel identifiant unique d'un nouveau groupe de travail.    *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

wgroup_id_t g_work_queue_define_group(GWorkQueue *queue, guint count)
{
    wgroup_id_t result;                     /* Valeur à retourner          */
    bool created;                           /* Bilan d'une tentative       */

    g_mutex_lock(&queue->mutex);

    do
    {
        result = ++queue->generator;

        if (result == INVALID_GROUP_ID)
            continue;

        created = g_work_queue_ensure_group_exists(queue, result, count);

    }
    while (!created);

    g_mutex_unlock(&queue->mutex);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
*                id    = identifiant d'un groupe de travail.                  *
*                                                                             *
*  Description : Dissout un groupe de travail existant.                       *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_work_queue_delete_group(GWorkQueue *queue, wgroup_id_t id)
{
    size_t i;                               /* Boucle de parcours          */
    GWorkGroup *group;                      /* Groupe de travail manipulé  */
#ifndef NDEBUG
    bool found;                             /* Repérage du groupe visé     */
#endif

#ifndef NDEBUG
    found = false;
#endif

    g_mutex_lock(&queue->mutex);

    for (i = 0; i < queue->groups_count; i++)
    {
        group = queue->groups[i];

        if (g_work_group_get_id(group) == id)
        {
            unref_object(group);

            memmove(&queue->groups[i], &queue->groups[i + 1],
                    (queue->groups_count - i - 1) * sizeof(GWorkGroup *));

            queue->groups_count--;
            queue->groups = realloc(queue->groups, queue->groups_count * sizeof(GWorkGroup *));

#ifndef NDEBUG
            found = true;
#endif

            break;

        }

    }

    assert(found);

    g_cond_broadcast(&queue->wait_all);

    g_mutex_unlock(&queue->mutex);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : queue = gestionnaire des actions à mener.                    *
*                work  = nouvelle tâche à programmer, puis effectuer.         *
*                id    = identifiant du groupe de travail d'affectation.      *
*                                                                             *
*  Description : Place une nouvelle tâche en attente.                         *
*                                                                             *
*  Retour      : Bilan, qui correspond à l'existence du groupe ciblé.         *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool g_work_queue_schedule(GWorkQueue *queue, GGenericWork *work, wgroup_id_t id)
{
    bool result;                            /* Bilan à retourner           */
    GWorkGroup *group;                      /* Groupe de travail à attendre*/

    group = g_work_queue_find_group_for_id(queue, id);
    assert(group != NULL);

    result = (group != NULL);

    if (result)
    {
        g_work_group_schedule(group, work);

        unref_object(group);

    }

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
*                id    = identifiant d'un groupe de travail.                  *
*                                                                             *
*  Description : Fournit le groupe de travail correspondant à un identifiant. *
*                                                                             *
*  Retour      : Eventuel groupe existant trouvé ou NULL si aucun.            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static GWorkGroup *g_work_queue_find_group_for_id(GWorkQueue *queue, wgroup_id_t id)
{
    GWorkGroup *result;                     /* Trouvaille à retourner      */
    size_t i;                               /* Boucle de parcours          */

    result = NULL;

    g_mutex_lock(&queue->mutex);

    for (i = 0; i < queue->groups_count; i++)
        if (g_work_group_get_id(queue->groups[i]) == id)
        {
            result = queue->groups[i];
            ref_object(result);
            break;
        }

    g_mutex_unlock(&queue->mutex);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
*                id    = identifiant d'un groupe de travail.                  *
*                                                                             *
*  Description : Détermine si un groupe est vide de toute programmation.      *
*                                                                             *
*  Retour      : Etat du groupe de travail.                                   *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool g_work_queue_is_empty(GWorkQueue *queue, wgroup_id_t id)
{
    bool result;                            /* Etat à retourner            */
    GWorkGroup *group;                      /* Groupe de travail à attendre*/

    group = g_work_queue_find_group_for_id(queue, id);

    if (group != NULL)
    {
        result = g_work_group_is_empty(group);
        unref_object(group);
    }

    else
        result = true;

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
*                id    = identifiant d'un groupe de travail.                  *
*                                                                             *
*  Description : Attend que toutes les tâches d'un groupe soient traitées.    *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_work_queue_wait_for_completion(GWorkQueue *queue, wgroup_id_t id)
{
    GWorkGroup *group;                      /* Groupe de travail à attendre*/

    group = g_work_queue_find_group_for_id(queue, id);

    if (group != NULL)
    {
        g_work_group_wait_for_completion(group);
        unref_object(group);
    }

}


/******************************************************************************
*                                                                             *
*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
*                id    = identifiant d'un groupe de travail.                  *
*                rel   = durée relative à patienter au max. en microsecondes. *
*                                                                             *
*  Description : Attend que toutes les tâches d'un groupe soient traitées.    *
*                                                                             *
*  Retour      : Bilan de l'attente : false en cas d'expiration, true sinon.  *
*                                                                             *
*  Remarques   : Cette fonction est originellement dédiée à un usage Python.  *
*                                                                             *
******************************************************************************/

bool g_work_queue_wait_timed_for_completion(GWorkQueue *queue, wgroup_id_t id, gint64 rel)
{
    bool result;                            /* Bilan d'attente à renvoyer  */
    GWorkGroup *group;                      /* Groupe de travail à attendre*/

    group = g_work_queue_find_group_for_id(queue, id);

    if (group != NULL)
    {
        result = g_work_group_wait_timed_for_completion(group, rel);
        unref_object(group);
    }
    else
        result = true;

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : queue    = gestionnaire de l'ensemble des groupes de travail.*
*                gb_ids   = identifiants de groupes globaux.                  *
*                gb_count = nombre de ces groupes globaux.                    *
*                                                                             *
*  Description : Attend que toutes les tâches de tout groupe soient traitées. *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_work_queue_wait_for_all_completions(GWorkQueue *queue, const wgroup_id_t *gb_ids, size_t gb_count)
{
    size_t i;                               /* Boucle de parcours          */

    g_mutex_lock(&queue->mutex);

 wait_again:

    /**
     * Attente d'éventuels groupes isolés.
     */

    while (queue->groups_count > gb_count)
        g_cond_wait(&queue->wait_all, &queue->mutex);

    g_mutex_unlock(&queue->mutex);

    /**
     * Attente des groupes principaux.
     */

    for (i = 0; i < gb_count; i++)
        g_work_queue_wait_for_completion(queue, gb_ids[i]);

    /**
     * Si le groupe par défaut a généré de nouveaux groupes, on recommence !
     */

    g_mutex_lock(&queue->mutex);

    if (queue->groups_count > gb_count)
        goto wait_again;

    g_mutex_unlock(&queue->mutex);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : queue    = gestionnaire de l'ensemble des groupes de travail.*
*                id       = identifiant d'un groupe de travail.               *
*                                                                             *
*  Description : Force un réveil d'une attente en cours pour la confirmer.    *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_work_queue_wake_up_waiters(GWorkQueue *queue, wgroup_id_t id)
{
    GWorkGroup *group;                      /* Groupe de travail à traiter */

    group = g_work_queue_find_group_for_id(queue, id);

    if (group != NULL)
    {
        g_work_group_wake_up_waiters(group);
        unref_object(group);
    }

}