/* Chrysalide - Outil d'analyse de fichiers binaires
 * manager.c - collecte et gestion des instances partagées
 *
 * Copyright (C) 2016 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 Foobar.  If not, see <http://www.gnu.org/licenses/>.
 */


#include "manager.h"


#include <assert.h>
#include <malloc.h>
#ifdef DEBUG_DUMP_STATS
#   include <stdio.h>
#endif
#include <string.h>


#include "../../common/sort.h"



/* Gestionnaire d'instances de type identique partagées (instance) */
struct _GShareManager
{
    GObject parent;                         /* A laisser en premier        */

    GSharedInstance **instances;            /* Instances partagées         */
    size_t count;                           /* Quantité de ces instances   */
    GMutex access;                          /* Accès à la table            */

    GType managed;                          /* Type d'instances gérées     */

};

/* Gestionnaire d'instances de type identique partagées (classe) */
struct _GShareManagerClass
{
    GObjectClass parent;                    /* A laisser en premier        */

};


/* Procède à l'initialisation d'une classe de suivi de largeurs. */
static void g_share_manager_class_init(GShareManagerClass *);

/* Procède à l'initialisation d'un suivi de largeurs de lignes. */
static void g_share_manager_init(GShareManager *);

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

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



/* Détermine le type du gestionnaire d'instances partagées. */
G_DEFINE_TYPE(GShareManager, g_share_manager, G_TYPE_OBJECT);


/******************************************************************************
*                                                                             *
*  Paramètres  : class = classe de composant GTK à initialiser.               *
*                                                                             *
*  Description : Procède à l'initialisation d'une classe de suivi de largeurs.*
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_share_manager_class_init(GShareManagerClass *class)
{
    GObjectClass *object;                   /* Autre version de la classe  */

    object = G_OBJECT_CLASS(class);

    object->dispose = (GObjectFinalizeFunc/* ! */)g_share_manager_dispose;
    object->finalize = (GObjectFinalizeFunc)g_share_manager_finalize;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : manager = composant GLib à initialiser.                      *
*                                                                             *
*  Description : Procède à l'initialisation d'un suivi de largeurs de lignes. *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_share_manager_init(GShareManager *manager)
{
    manager->instances = NULL;
    manager->count = 0;

    g_mutex_init(&manager->access);

}


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

static void g_share_manager_dispose(GShareManager *manager)
{
    size_t i;                               /* Boucle de parcours          */

    for (i = 0; i < manager->count; i++)
        g_object_unref(G_OBJECT(manager->instances[i]));

    g_mutex_clear(&manager->access);

    G_OBJECT_CLASS(g_share_manager_parent_class)->dispose(G_OBJECT(manager));

}


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

static void g_share_manager_finalize(GShareManager *manager)
{
    if (manager->instances != NULL)
        free(manager->instances);

    G_OBJECT_CLASS(g_share_manager_parent_class)->finalize(G_OBJECT(manager));

}


/******************************************************************************
*                                                                             *
*  Paramètres  : type = type d'instances encadrées ici.                       *
*                                                                             *
*  Description : Crée un nouveau gestionnaire d'instances partagées.          *
*                                                                             *
*  Retour      : Composant GLib créé.                                         *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GShareManager *g_share_manager_new(GType type)
{
    GShareManager *result;                  /* Gestionnaire à renvoyer     */

    result = g_object_new(G_TYPE_SHARE_MANAGER, NULL);

    result->managed = type;

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : manager  = gestionnaire d'instance à consulter.              *
*                template = informations à retrouver intégralement.           *
*                                                                             *
*  Description : Retrouve ou crée une instance partagée.                      *
*                                                                             *
*  Retour      : Instance existante déjà partagée ou nouvellement créée.      *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GSharedInstance *g_share_manager_get(GShareManager *manager, GSharedInstance *template)
{
    GSharedInstance *result;                /* Trouvaille à retourner      */
    size_t index;                           /* Indice d'une instance idéale*/
    bool found;                             /* Existence de cette instance */
    bool status;                            /* Conclusion d'initialisation */

    g_mutex_lock(&manager->access);

    found = bsearch_index(&template, manager->instances, manager->count, sizeof(GSharedInstance *),
                          (__compar_fn_t)g_shared_instance_quickly_compare, &index);

    if (!found)
    {
        result = g_object_new(manager->managed, NULL);

        status = g_shared_instance_init(result, template);

        if (!status)
        {
            g_object_unref(result);
            result = NULL;
        }

        else
        {
            g_shared_instance_inc_references(result);

            manager->instances = (GSharedInstance **)_qinsert(manager->instances, &manager->count,
                                                              sizeof(GSharedInstance *), &result, index);

        }

    }

    else
        result = manager->instances[index];

    if (result != NULL)
    {
        g_object_ref(G_OBJECT(result));
        g_shared_instance_inc_references(result);
    }

    g_mutex_unlock(&manager->access);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : manager   = gestionnaire d'instance à consulter.             *
*                old       = ancienne instance partagée à faire évoluer.      *
*                template  = informations à retrouver intégralement.          *
*                container = propriétaire de l'ancienne version à contacter.  *
*                                                                             *
*  Description : Met à jour une instance partagée.                            *
*                                                                             *
*  Retour      : Instance existante déjà partagée ou nouvellement créée.      *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GSharedInstance *g_share_manager_update(GShareManager *manager, GSharedInstance *old, GSharedInstance *template, GShareContainer *container)
{
    GSharedInstance *result;                /* Nouvelle instance à renvoyer*/

    result = g_share_manager_get(manager, template);

    if (container != NULL)
        g_share_container_replace(container, old, result);

    g_share_manager_put(manager, old);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : manager = gestionnaire d'instance à consulter.               *
*                shared  = instance partagée à libérer.                       *
*                                                                             *
*  Description : Abandonne un usage d'une instance partagée.                  *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_share_manager_put(GShareManager *manager, GSharedInstance *shared)
{
    g_mutex_lock(&manager->access);

    g_shared_instance_dec_references(shared);

    if (g_shared_instance_get_references(shared) == 1)
    {
        g_object_unref(G_OBJECT(shared));

        manager->instances = (GSharedInstance **)qdelete(manager->instances, &manager->count,
                                                         sizeof(GSharedInstance *),
                                                         (__compar_fn_t)g_shared_instance_quickly_compare,
                                                         &shared);

    }

    g_mutex_unlock(&manager->access);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : manager = gestionnaire d'instance à consulter.               *
*                                                                             *
*  Description : Imprime des statistiques d'utilisation du gestionnaire.      *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/
#ifdef DEBUG_DUMP_STATS
void g_share_manager_dump_stats(GShareManager *manager)
{
    unsigned int counter;                   /* Quantité nécessaire         */
    size_t i;                               /* Boucle de parcours          */
    GTypeQuery query;                       /* Informations sur un type    */

    counter = 0;

    g_mutex_lock(&manager->access);

    for (i = 0; i < manager->count; i++)
        counter += g_shared_instance_get_references(manager->instances[i]);

    g_mutex_unlock(&manager->access);

    g_type_query(manager->managed, &query);

    printf("%s: current = %zu / %zu - needed = %u / %u (size=%u, saved=%zu)\n",
           query.type_name,
           manager->count, manager->count * query.instance_size,
           counter, counter * query.instance_size,
           query.instance_size,
           (counter - manager->count) * query.instance_size);

}
#endif