/* OpenIDA - Outil d'analyse de fichiers binaires
 * switch.c - décodage des aiguillages multiples du flot d'exécution
 *
 * Copyright (C) 2013 Cyrille Bagard
 *
 *  This file is part of OpenIDA.
 *
 *  OpenIDA 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.
 *
 *  OpenIDA 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 "switch.h"


#include <malloc.h>
#include <stdlib.h>


#include "../instruction-int.h"



/* Détails d'un cas */
typedef struct _case_info
{
    vmpa_t addr;                            /* Adresse des blocs associés  */

    GDecExpression **values;                /* Valeur d'embranchement      */
    size_t values_count;                    /* Quantité de cas rassemblés  */

    GDecInstruction *instrs;                /* Instructions du cas         */

} case_info;


/* Définition d'un aiguillage multiple du flux d'exécution (instance) */
struct _GSwitchInstruction
{
    GDecInstruction parent;                 /* A laisser en premier        */

    GDecExpression *value;                  /* Valeur décidant du flot     */

    case_info *cases;                       /* Embranchements présents     */
    size_t cases_count;                     /* Nombre de cas de sélection  */

    GDecInstruction *def_case;              /* Instructions par défaut     */

};


/* Définition d'un aiguillage multiple du flux d'exécution (classe) */
struct _GSwitchInstructionClass
{
    GDecInstructionClass parent;            /* A laisser en premier        */

};



/* Initialise la classe des aiguillages de flux d'exécution. */
static void g_switch_instruction_class_init(GSwitchInstructionClass *);

/* Initialise une instance d'aiguillage du flux d'exécution. */
static void g_switch_instruction_init(GSwitchInstruction *);

/* Visite un ensemble hiérarchique d'instructions décompilées. */
static bool g_switch_instruction_visit(GSwitchInstruction *, dec_instr_visitor_cb, DecInstrVisitFlags, void *);

/* Remplace une instruction décompilée par une autre. */
static bool g_switch_instruction_replace(GSwitchInstruction *, GDecInstruction *, GDecInstruction *);

/* Imprime pour l'écran un version humaine d'une instruction. */
static GBufferLine *g_switch_instruction_print(const GSwitchInstruction *, GCodeBuffer *, GBufferLine *, GLangOutput *);



/* Indique le type défini pour un aiguillage du flux d'exécution. */
G_DEFINE_TYPE(GSwitchInstruction, g_switch_instruction, G_TYPE_DEC_INSTRUCTION);


/******************************************************************************
*                                                                             *
*  Paramètres  : klass = classe à initialiser.                                *
*                                                                             *
*  Description : Initialise la classe des aiguillages de flux d'exécution.    *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_switch_instruction_class_init(GSwitchInstructionClass *klass)
{

}


/******************************************************************************
*                                                                             *
*  Paramètres  : instr = instance à initialiser.                              *
*                                                                             *
*  Description : Initialise une instance d'aiguillage du flux d'exécution.    *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_switch_instruction_init(GSwitchInstruction *instr)
{
    GDecInstruction *base;                  /* Autre version de l'objet    */

    base = G_DEC_INSTRUCTION(instr);

    base->visit = (dec_instr_visit_fc)g_switch_instruction_visit;
    base->replace = (dec_instr_replace_fc)g_switch_instruction_replace;
    base->print = (dec_instr_print_fc)g_switch_instruction_print;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : value = valeur déterminant la voie à suivre.                 *
*                                                                             *
*  Description : Exprime un aiguillage multiple du flux selon une valeur.     *
*                                                                             *
*  Retour      : Instruction mise en place.                                   *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GDecInstruction *g_switch_instruction_new(GDecExpression *value)
{
    GSwitchInstruction *result;              /* Expression à retourner      */

    result = g_object_new(G_TYPE_SWITCH_INSTRUCTION, NULL);

    result->value = value;

    return G_DEC_INSTRUCTION(result);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : instr    = première instruction à venir visiter.             *
*                callback = procédure à appeler à chaque instruction visitée. *
*                flags    = moments des appels à réaliser en retour.          *
*                data     = données quelconques associées au visiteur.        *
*                                                                             *
*  Description : Visite un ensemble hiérarchique d'instructions décompilées.  *
*                                                                             *
*  Retour      : true si le parcours a été jusqu'à son terme, false sinon.    *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_switch_instruction_visit(GSwitchInstruction *instr, dec_instr_visitor_cb callback, DecInstrVisitFlags flags, void *data)
{
    bool result;                            /* Bilan à retourner           */
    size_t i;                               /* Boucle de parcours          */

    result = true;

    for (i = 0; i < instr->cases_count && result; i++)
        result = _g_dec_instruction_visit(instr->cases[i].instrs, G_DEC_INSTRUCTION(instr),
                                          callback, flags, data);

    if (result && instr->def_case != NULL)
        result = _g_dec_instruction_visit(instr->def_case, G_DEC_INSTRUCTION(instr),
                                          callback, flags, data);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : instr = première instruction à venir ausculter.              *
*                old   = instruction décompilée à venir remplacer.            *
*                new   = instruction décompilée à utiliser dorénavant.        *
*                                                                             *
*  Description : Remplace une instruction décompilée par une autre.           *
*                                                                             *
*  Retour      : true si un remplacement a été effectué, false sinon.         *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_switch_instruction_replace(GSwitchInstruction *instr, GDecInstruction *old, GDecInstruction *new)
{
    bool result;                            /* Bilan à retourner           */
    size_t i;                               /* Boucle de parcours          */

    result = false;

    for (i = 0; i < instr->cases_count; i++)
        result |= g_dec_instruction_replace(instr->cases[i].instrs, old, new);

    if (instr->def_case != NULL)
        result |= g_dec_instruction_replace(instr->def_case, old, new);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : instr  = instruction à transcrire en version humaine.        *
*                buffer = tampon où doit se réaliser l'insertion.             *
*                line   = ligne d'impression prête à emploi ou NULL.          *
*                output = langage de programmation de sortie.                 *
*                                                                             *
*  Description : Imprime pour l'écran un version humaine d'une instruction.   *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static GBufferLine *g_switch_instruction_print(const GSwitchInstruction *instr, GCodeBuffer *buffer, GBufferLine *line, GLangOutput *output)
{
    GBufferLine *result;                    /* Ligne à retourner           */
    size_t i;                               /* Boucle de parcours #1       */
    size_t j;                               /* Boucle de parcours #2       */

    g_buffer_line_insert_text(line, BLC_ASSEMBLY_HEAD, "switch", 9, RTT_KEY_WORD);

    g_buffer_line_insert_text(line, BLC_ASSEMBLY_HEAD, " ", 1, RTT_RAW);
    g_buffer_line_insert_text(line, BLC_ASSEMBLY_HEAD, "(", 1, RTT_PUNCT);

    result = g_dec_instruction_print(G_DEC_INSTRUCTION(instr->value), buffer, line, output);

    g_buffer_line_insert_text(result, BLC_ASSEMBLY_HEAD, ")", 1, RTT_PUNCT);

    g_buffer_line_insert_text(result, BLC_ASSEMBLY_HEAD, " ", 1, RTT_RAW);
    g_buffer_line_insert_text(result, BLC_ASSEMBLY_HEAD, "{", 1, RTT_HOOK);

    g_code_buffer_inc_indentation(buffer);

    /* Cas d'aiguillage définis */

    for (i = 0; i < instr->cases_count; i++)
    {
        for (j = 0; j < instr->cases[i].values_count; j++)
        {
            result = g_code_buffer_append_new_line_fixme(buffer);

            g_buffer_line_insert_text(result, BLC_ASSEMBLY_HEAD, "case", 4, RTT_KEY_WORD);
            g_buffer_line_insert_text(result, BLC_ASSEMBLY_HEAD, " ", 1, RTT_RAW);

            result = g_dec_instruction_print(G_DEC_INSTRUCTION(instr->cases[i].values[j])
                                             , buffer, result, output);

            g_buffer_line_insert_text(result, BLC_ASSEMBLY_HEAD, ":", 1, RTT_PUNCT);

        }

        result = g_dec_instruction_print(instr->cases[i].instrs, buffer, result, output);

    }

    /* Cas par défaut */

    if (instr->def_case != NULL)
    {
        result = g_code_buffer_append_new_line_fixme(buffer);

        g_buffer_line_insert_text(result, BLC_ASSEMBLY_HEAD, "default", 7, RTT_KEY_WORD);
        g_buffer_line_insert_text(result, BLC_ASSEMBLY_HEAD, ":", 1, RTT_PUNCT);

        result = g_dec_instruction_print(instr->def_case, buffer, result, output);

    }

    /* Clôture */

    g_code_buffer_dec_indentation(buffer);

    result = g_code_buffer_append_new_line_fixme(buffer);

    g_buffer_line_insert_text(result, BLC_ASSEMBLY_HEAD, "}", 1, RTT_HOOK);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : instr  = instruction à compléter avec un nouveau cas.        *
*                value  = valeur validant l'exécution des instructions.       *
*                instrs = instructions associées au cas présenté.             *
*                addr   = adresse du bloc d'instructions.                     *
*                                                                             *
*  Description : Ajoute un cas d'exécution à l'aiguillage multiple.           *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_switch_instruction_add_case(GSwitchInstruction *instr, GDecExpression *value, GDecInstruction *instrs, vmpa_t addr)
{
    case_info *found;                       /* Cas similaires déjà intégrés*/

    found = (case_info *)bsearch(&addr, instr->cases, instr->cases_count, sizeof(case_info),
                                 (__compar_fn_t)compare_vmpa);

    if (found != NULL)
    {
        found->values = (GDecExpression **)realloc(found->values,
                                                   found->values_count++ * sizeof(GDecExpression *));

        found->values[found->values_count - 1] = value;

    }
    else
    {
        instr->cases = (case_info *)realloc(instr->cases,
                                            ++instr->cases_count * sizeof(case_info));

        instr->cases[instr->cases_count - 1].addr = addr;
        instr->cases[instr->cases_count - 1].values = (GDecExpression **)malloc(sizeof(GDecExpression *));
        instr->cases[instr->cases_count - 1].values_count = 1;
        instr->cases[instr->cases_count - 1].instrs = instrs;

        instr->cases[instr->cases_count - 1].values[0] = value;

        qsort(instr->cases, instr->cases_count, sizeof(case_info), (__compar_fn_t)compare_vmpa);

    }

}


/******************************************************************************
*                                                                             *
*  Paramètres  : instr  = instruction à compléter avec un nouveau cas.        *
*                instrs = instructions associées au cas présenté.             *
*                                                                             *
*  Description : Ajoute un cas d'exécution par défaut à l'aiguillage multiple.*
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_switch_instruction_set_default_case(GSwitchInstruction *instr, GDecInstruction *instrs)
{
    if (instr->def_case != NULL)
        g_object_unref(G_OBJECT(instr->def_case));

    instr->def_case = instrs;

}