/* Chrysalide - Outil d'analyse de fichiers binaires
 * extract.c - organisation d'une extraction d'un élément d'une série interne
 *
 * Copyright (C) 2023 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 "extract.h"


#include <assert.h>
#include <string.h>


#include "extract-int.h"
#include "../../../core/global.h"



/* --------------------- INTRODUCTION D'UNE NOUVELLE EXPRESSION --------------------- */


/* Initialise la classe des extractions d'éléments internes. */
static void g_scan_pending_extraction_class_init(GScanPendingExtractionClass *);

/* Initialise une instance d'extraction d'élément interne. */
static void g_scan_pending_extraction_init(GScanPendingExtraction *);

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

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



/* --------------------- IMPLEMENTATION DES FONCTIONS DE CLASSE --------------------- */


/* Réduit une expression à une forme plus simple. */
static ScanReductionState g_scan_pending_extraction_reduce(const GScanPendingExtraction *, GScanContext *, GScanScope *, GScanExpression **);

/* Reproduit un accès en place dans une nouvelle instance. */
static void g_scan_pending_extraction_copy(GScanPendingExtraction *, const GScanPendingExtraction *);



/* ---------------------------------------------------------------------------------- */
/*                       INTRODUCTION D'UNE NOUVELLE EXPRESSION                       */
/* ---------------------------------------------------------------------------------- */


/* Indique le type défini pour une extraction d'élément de série interne. */
G_DEFINE_TYPE(GScanPendingExtraction, g_scan_pending_extraction, G_TYPE_SCAN_NAMED_ACCESS);


/******************************************************************************
*                                                                             *
*  Paramètres  : klass = classe à initialiser.                                *
*                                                                             *
*  Description : Initialise la classe des extractions d'éléments internes.    *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_scan_pending_extraction_class_init(GScanPendingExtractionClass *klass)
{
    GObjectClass *object;                   /* Autre version de la classe  */
    GScanExpressionClass *expr;             /* Version de classe parente   */
    GScanNamedAccessClass *access;          /* Autre version de la classe  */

    object = G_OBJECT_CLASS(klass);

    object->dispose = (GObjectFinalizeFunc/* ! */)g_scan_pending_extraction_dispose;
    object->finalize = (GObjectFinalizeFunc)g_scan_pending_extraction_finalize;

    expr = G_SCAN_EXPRESSION_CLASS(klass);

    expr->cmp_rich = (compare_expr_rich_fc)NULL;
    expr->reduce = (reduce_expr_fc)g_scan_pending_extraction_reduce;

    access = G_SCAN_NAMED_ACCESS_CLASS(klass);

    access->copy = (copy_scan_access_fc)g_scan_pending_extraction_copy;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : extract = instance à initialiser.                            *
*                                                                             *
*  Description : Initialise une instance d'extraction d'élément interne.      *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_scan_pending_extraction_init(GScanPendingExtraction *extract)
{
    extract->index = NULL;

}


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

static void g_scan_pending_extraction_dispose(GScanPendingExtraction *extract)
{
    g_clear_object(&extract->index);

    G_OBJECT_CLASS(g_scan_pending_extraction_parent_class)->dispose(G_OBJECT(extract));

}


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

static void g_scan_pending_extraction_finalize(GScanPendingExtraction *extract)
{
    G_OBJECT_CLASS(g_scan_pending_extraction_parent_class)->finalize(G_OBJECT(extract));

}


/******************************************************************************
*                                                                             *
*  Paramètres  : target = désignation de l'objet d'appel à identifier.        *
*                index  = indice de l'élément à extraire.                     *
*                                                                             *
*  Description : Organise l'extraction d'un élément d'une série interne.      *
*                                                                             *
*  Retour      : Fonction mise en place.                                      *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GScanExpression *g_scan_pending_extraction_new(const sized_string_t *target, GScanExpression *index)
{
    GScanExpression *result;                /* Structure à retourner       */

    result = g_object_new(G_TYPE_SCAN_PENDING_EXTRACTION, NULL);

    if (!g_scan_pending_extraction_create(G_SCAN_PENDING_EXTRACTION(result), target, index))
        g_clear_object(&result);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : extract = instance à initialiser pleinement.                 *
*                target  = désignation de l'objet d'appel à identifier.       *
*                index   = indice de l'élément à extraire.                    *
*                                                                             *
*  Description : Met en place une expression d'extraction d'élément interne.  *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool g_scan_pending_extraction_create(GScanPendingExtraction *extract, const sized_string_t *target, GScanExpression *index)
{
    bool result;                            /* Bilan à retourner           */

    result = g_scan_named_access_create(G_SCAN_NAMED_ACCESS(extract), target);
    if (!result) goto exit;

    extract->index = index;
    g_object_ref(G_OBJECT(index));

 exit:

    return result;

}



/* ---------------------------------------------------------------------------------- */
/*                       IMPLEMENTATION DES FONCTIONS DE CLASSE                       */
/* ---------------------------------------------------------------------------------- */


/******************************************************************************
*                                                                             *
*  Paramètres  : expr  = expression à consulter.                              *
*                ctx   = contexte de suivi de l'analyse courante.             *
*                scope = portée courante des variables locales.               *
*                out   = zone d'enregistrement de la réduction opérée. [OUT]  *
*                                                                             *
*  Description : Réduit une expression à une forme plus simple.               *
*                                                                             *
*  Retour      : Bilan de l'opération : false en cas d'erreur irrécupérable.  *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static ScanReductionState g_scan_pending_extraction_reduce(const GScanPendingExtraction *expr, GScanContext *ctx, GScanScope *scope, GScanExpression **out)
{
    ScanReductionState result;              /* Etat synthétisé à retourner */
    GScanNamedAccess *access;               /* Autre vision de l'expression*/
    GScanRegisteredItem *resolved;          /* Cible concrète obtenue      */
    GScanExpression *new;                   /* Nouvelle réduction obtenue  */
    GObject *final;                         /* Expression ou élément ?     */
    GScanExpression *new_next;              /* Nouvelle version du suivant */

    access = G_SCAN_NAMED_ACCESS(expr);

    resolved = _g_scan_named_access_prepare_reduction(access, ctx, scope);

    if (resolved == NULL)
        result = SRS_UNRESOLVABLE;

    else
    {
        /* Actualisation nécessaire des arguments ? */

        result = g_scan_expression_reduce(expr->index, ctx, scope, &new);

        /* Suite des traitements */

        if (result == SRS_WAIT_FOR_SCAN)
        {
            /**
             * Si changement il y a eu...
             */
            if (new != expr->index)
            {
                *out = g_scan_pending_extraction_new(NULL, new);

                /**
                 * Fonctionnement équivalent de :
                 *    g_scan_named_access_set_base(G_SCAN_NAMED_ACCESS(*out), resolved);
                 */
                G_SCAN_NAMED_ACCESS(*out)->resolved = resolved;
                g_object_ref(G_OBJECT(resolved));

                if (G_SCAN_NAMED_ACCESS(expr)->next != NULL)
                    g_scan_named_access_attach_next(G_SCAN_NAMED_ACCESS(*out), G_SCAN_NAMED_ACCESS(expr)->next);

            }

        }

        else if (result == SRS_REDUCED)
        {
            final = g_scan_registered_item_extract_at(resolved, new, ctx, scope);

            if (final != NULL)
            {
                /**
                 * Si le produit de l'appel à la fonction est une expression d'évaluation
                 * classique, alors ce produit constitue la réduction finale de la chaîne.
                 *
                 * Ce cas de figure ne se rencontre normalement qu'en bout de chaîne.
                 */
                if (!G_IS_SCAN_REGISTERED_ITEM(final))
                {
                    if (access->next != NULL)
                        result = SRS_UNRESOLVABLE;

                    else
                    {
                        *out = G_SCAN_EXPRESSION(final);
                        g_object_ref(G_OBJECT(final));

                        result = SRS_REDUCED;

                    }

                }
                else
                {
                    if (access->next != NULL)
                    {
                        new_next = g_scan_named_access_duplicate(access->next, G_SCAN_REGISTERED_ITEM(final));

                        result = g_scan_expression_reduce(new_next, ctx, scope, out);

                        g_object_unref(G_OBJECT(new_next));

                    }

                    /**
                     * Le cas ci-après est typique de l'extension Kaitai : field[n]
                     * renvoie vers une instance GScanRegisteredItem (GKaitaiBrowser).
                     *
                     * Il n'y a donc pas d'expression en jeu, et l'élément est le dernier
                     * de la liste.
                     */
                    else
                    {
                        if (g_scan_registered_item_reduce(G_SCAN_REGISTERED_ITEM(final), ctx, scope, out))
                            result = SRS_REDUCED;
                        else
                            result = SRS_UNRESOLVABLE;

                    }

                }

            }

            else
                result = SRS_UNRESOLVABLE;

            g_clear_object(&final);

        }

        /* Libération locale des arguments reconstruits */

        g_clear_object(&new);

        g_object_unref(G_OBJECT(resolved));

    }

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : dest = emplacement d'enregistrement à constituer. [OUT]      *
*                src  = expression source à copier.                           *
*                                                                             *
*  Description : Reproduit un accès en place dans une nouvelle instance.      *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_scan_pending_extraction_copy(GScanPendingExtraction *dest, const GScanPendingExtraction *src)
{
    GScanNamedAccessClass *class;           /* Classe parente à solliciter */

    class = G_SCAN_NAMED_ACCESS_CLASS(g_scan_pending_extraction_parent_class);

    class->copy(G_SCAN_NAMED_ACCESS(dest), G_SCAN_NAMED_ACCESS(src));

    dest->index = src->index;
    g_object_ref(G_OBJECT(src->index));

}