/* Chrysalide - Outil d'analyse de fichiers binaires
 * browser.c - accès à des définitions Kaitai depuis ROST
 *
 * 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 Chrysalide.  If not, see <http://www.gnu.org/licenses/>.
 */


#include "browser.h"


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


#include <analysis/scan/exprs/literal.h>


#include "browser-int.h"
#include "../records/bits.h"
#include "../records/delayed.h"
#include "../records/item.h"
#include "../records/list.h"



/* ---------------------- PARCOURS DE CORRESPONDANCES ETABLIES ---------------------- */


/* Initialise la classe des parcours de correspondances Kaitai. */
static void g_kaitai_browser_class_init(GKaitaiBrowserClass *);

/* Initialise un parcours de correspondances Kaitai. */
static void g_kaitai_browser_init(GKaitaiBrowser *);

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

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



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


/* Indique le nom associé à une expression d'évaluation. */
static char *g_kaitai_browser_get_name(const GKaitaiBrowser *);

/* Lance une résolution d'élément à solliciter. */
static bool g_kaitai_browser_resolve(GKaitaiBrowser *, const char *, GScanContext *, GScanScope *, GScanRegisteredItem **);

/* Réduit une expression à une forme plus simple. */
static bool g_kaitai_browser_reduce(GKaitaiBrowser *, GScanContext *, GScanScope *, GScanExpression **);

/* Effectue une extraction d'élément à partir d'une série. */
static GObject *g_kaitai_browser_extract_at(GKaitaiBrowser *, const GScanExpression *, GScanContext *, GScanScope *);



/* ---------------------------------------------------------------------------------- */
/*                        PARCOURS DE CORRESPONDANCES ETABLIES                        */
/* ---------------------------------------------------------------------------------- */


/* Indique le type défini pour un parcours de correspondances Kaitai pour ROST. */
G_DEFINE_TYPE(GKaitaiBrowser, g_kaitai_browser, G_TYPE_SCAN_REGISTERED_ITEM);


/******************************************************************************
*                                                                             *
*  Paramètres  : klass = classe à initialiser.                                *
*                                                                             *
*  Description : Initialise la classe des parcours de correspondances Kaitai. *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_kaitai_browser_class_init(GKaitaiBrowserClass *klass)
{
    GObjectClass *object;                   /* Autre version de la classe  */
    GScanRegisteredItemClass *registered;   /* Version de classe parente   */

    object = G_OBJECT_CLASS(klass);

    object->dispose = (GObjectFinalizeFunc/* ! */)g_kaitai_browser_dispose;
    object->finalize = (GObjectFinalizeFunc)g_kaitai_browser_finalize;

    registered = G_SCAN_REGISTERED_ITEM_CLASS(klass);

    registered->get_name = (get_registered_item_name_fc)g_kaitai_browser_get_name;
    registered->resolve = (resolve_registered_item_fc)g_kaitai_browser_resolve;
    registered->reduce = (reduce_registered_item_fc)g_kaitai_browser_reduce;
    registered->extract = (extract_registered_item_at)g_kaitai_browser_extract_at;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : browser = instance à initialiser.                            *
*                                                                             *
*  Description : Initialise un parcours de correspondances Kaitai.            *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_kaitai_browser_init(GKaitaiBrowser *browser)
{
    browser->path = NULL;
    browser->record = NULL;

}


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

static void g_kaitai_browser_dispose(GKaitaiBrowser *browser)
{
    g_clear_object(&browser->record);

    G_OBJECT_CLASS(g_kaitai_browser_parent_class)->dispose(G_OBJECT(browser));

}


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

static void g_kaitai_browser_finalize(GKaitaiBrowser *browser)
{
    if (browser->path != NULL)
        free(browser->path);

    G_OBJECT_CLASS(g_kaitai_browser_parent_class)->finalize(G_OBJECT(browser));

}


/******************************************************************************
*                                                                             *
*  Paramètres  : path   = chemin vers l'enregistrement fourni.                *
*                record = correspondance racine à considérer.                 *
*                                                                             *
*  Description : Crée un nouveau parcours de correspondances Kaitai.          *
*                                                                             *
*  Retour      : Instance mise en place ou NULL en cas d'échec.               *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GKaitaiBrowser *g_kaitai_browser_new(const char *path, GMatchRecord *record)
{
    GKaitaiBrowser *result;                 /* Structure à retourner       */

    result = g_object_new(G_TYPE_KAITAI_BROWSER, NULL);

    if (!g_kaitai_browser_create(result, path, record))
        g_clear_object(&result);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : browser = encadrement d'un parcours de correspondances.      *
*                path    = chemin vers l'enregistrement fourni.               *
*                record  = correspondance racine à considérer.                *
*                                                                             *
*  Description : Met en place un nouveau parcours de correspondances Kaitai.  *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool g_kaitai_browser_create(GKaitaiBrowser *browser, const char *path, GMatchRecord *record)
{
    bool result;                            /* Bilan à retourner           */

    result = true;

    if (path != NULL)
        browser->path = strdup(path);

    browser->record = record;
    g_object_ref(G_OBJECT(browser->record));

    return result;

}



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


/******************************************************************************
*                                                                             *
*  Paramètres  : item = élément d'appel à consulter.                          *
*                                                                             *
*  Description : Indique le nom associé à une expression d'évaluation.        *
*                                                                             *
*  Retour      : Désignation humaine de l'expression d'évaluation.            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static char *g_kaitai_browser_get_name(const GKaitaiBrowser *item)
{
    char *result;                           /* Désignation à retourner     */
    int ret;                                /* Statut de construction      */

    if (item->path == NULL)
        result = strdup("kaitai://");

    else
    {
        ret = asprintf(&result, "kaitai://%s", item->path);
        assert(ret > 0);

        if (ret <= 0)
            result = strdup("kaitai://???");

    }

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : item   = élément d'appel à consulter.                        *
*                target = désignation de l'objet d'appel à identifier.        *
*                ctx    = contexte de suivi de l'analyse courante.            *
*                scope  = portée courante des variables locales.              *
*                out    = zone d'enregistrement de la résolution opérée. [OUT]*
*                                                                             *
*  Description : Lance une résolution d'élément à solliciter.                 *
*                                                                             *
*  Retour      : Bilan de l'opération : false en cas d'erreur irrécupérable.  *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_kaitai_browser_resolve(GKaitaiBrowser *item, const char *target, GScanContext *ctx, GScanScope *scope, GScanRegisteredItem **out)
{
    bool result;                            /* Bilan à retourner           */
    GMatchRecord *found;                    /* Correspondance trouvée      */
    char *path;
    int ret;                                /* Statut de construction      */

    found = g_match_record_find_by_name(item->record, target, strlen(target), 1);
    result = (found != NULL);

    if (result)
    {
        ret = asprintf(&path, "%s.%s", item->path, target);
        assert(ret > 0);

        if (ret <= 0)
            path = strdup("!?");

        *out = G_SCAN_REGISTERED_ITEM(g_kaitai_browser_new(path, found));

        free(path);
        g_object_unref(G_OBJECT(found));

    }

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : item  = élément d'appel à 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 bool g_kaitai_browser_reduce(GKaitaiBrowser *item, GScanContext *ctx, GScanScope *scope, GScanExpression **out)
{
    bool result;                            /* Bilan à retourner           */
    size_t count;                           /* Décompte total à considérer */
    resolved_value_t value;                 /* Valeur brute à transformer  */

    if (G_IS_RECORD_LIST(item->record))
    {
        count = g_record_list_count_records(G_RECORD_LIST(item->record));

        *out = g_scan_literal_expression_new(LVT_BOOLEAN, (bool []){ count > 0 });

        result = true;

    }

    else
    {
        if (G_IS_RECORD_BIT_FIELD(item->record))
            result = g_record_bit_field_get_value(G_RECORD_BIT_FIELD(item->record), &value);

        else if (G_IS_RECORD_DELAYED(item->record))
            result = g_record_delayed_compute_and_aggregate_value(G_RECORD_DELAYED(item->record), &value);

        else if (G_IS_RECORD_ITEM(item->record))
            result = g_record_item_get_value(G_RECORD_ITEM(item->record), &value);

        else
            result = false;

        if (result)
        {
            switch (value.type)
            {
                case GVT_UNSIGNED_INTEGER:
                    *out = g_scan_literal_expression_new(LVT_UNSIGNED_INTEGER, &value.unsigned_integer);
                    break;

                case GVT_SIGNED_INTEGER:
                    *out = g_scan_literal_expression_new(LVT_SIGNED_INTEGER, &value.signed_integer);
                    break;

                case GVT_FLOAT:
                    /* TODO */
                    break;

                case GVT_BOOLEAN:
                    *out = g_scan_literal_expression_new(LVT_BOOLEAN, &value.status);
                    break;

                case GVT_BYTES:
                    *out = g_scan_literal_expression_new(LVT_STRING, &value.bytes);
                    break;

                default:
                    break;

            }

            EXIT_RESOLVED_VALUE(value);

        }

    }

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : item  = élément d'appel à consulter.                         *
*                index = indice de l'élément à cibler.                        *
*                ctx   = contexte de suivi de l'analyse courante.             *
*                scope = portée courante des variables locales.               *
*                                                                             *
*  Description : Effectue une extraction d'élément à partir d'une série.      *
*                                                                             *
*  Retour      : Elément de série obtenu ou NULL si erreur irrécupérable.     *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static GObject *g_kaitai_browser_extract_at(GKaitaiBrowser *item, const GScanExpression *index, GScanContext *ctx, GScanScope *scope)
{
    GObject *result;                        /* Elément récupéré à renvoyer */
    GScanLiteralExpression *literal;        /* Accès direct à l'indice     */
    LiteralValueType vtype;                 /* Type de valeur portée       */
    unsigned long long at;                  /* Valeur concrète du point    */
    bool status;                            /* Bilan d'obtention d'indice  */
    GRecordList *list;                      /* Accès direct à la liste     */
    size_t count;                           /* Décompte total à considérer */
    GMatchRecord *found;                    /* Correspondance trouvée      */
    char *path;
    int ret;                                /* Statut de construction      */

    result = NULL;

    /* Validations préliminaires */

    if (!G_IS_RECORD_LIST(item->record)) goto exit;
    if (!G_IS_SCAN_LITERAL_EXPRESSION(index)) goto exit;

    literal = G_SCAN_LITERAL_EXPRESSION(index);

    vtype = g_scan_literal_expression_get_value_type(literal);
    if (vtype != LVT_UNSIGNED_INTEGER) goto exit;

    status = g_scan_literal_expression_get_unsigned_integer_value(literal, &at);
    if (!status) goto exit;

    list = G_RECORD_LIST(item->record);

    count = g_record_list_count_records(list);
    if (at >= count) goto exit;

    /* Récupération de l'élément visé */

    found = g_record_list_get_record(list, at);
    if (found == NULL) goto exit;

    ret = asprintf(&path, "%s[%llu]", item->path, at);
    assert(ret > 0);

    if (ret <= 0)
        path = strdup("!?");

    result = G_OBJECT(g_kaitai_browser_new(path, found));

    free(path);
    g_object_unref(G_OBJECT(found));

 exit:

    return result;

}