/* Chrysalide - Outil d'analyse de fichiers binaires
 * cond.c - représentation des conditions
 *
 * Copyright (C) 2010-2013 Cyrille Bagard
 *
 *  This file is part of Chrysalide.
 *
 *  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 "cond.h"


#include <malloc.h>


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



/* Définition d'une condition binaire quelconque (instance) */
struct _GCondExpression
{
    GDecExpression parent;                  /* A laisser en premier        */

    CondOperatorType operator;              /* Opérateur de la liste       */

    union
    {
        GDecExpression *exp;                /* Expression analysée de base */
        struct
        {
            GCondExpression **conds;        /* Liste d'expressions         */
            size_t count;                   /* Taille de cette liste       */
        };
    };

};


/* Définition d'une condition binaire quelconque (classe) */
struct _GCondExpressionClass
{
    GDecExpressionClass parent;             /* A laisser en premier        */

};



/* Initialise la classe des conditions binaires quelconques. */
static void g_cond_expression_class_init(GCondExpressionClass *);

/* Initialise une instance de condition binaire quelconque. */
static void g_cond_expression_init(GCondExpression *);

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

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

/* Imprime pour l'écran un version humaine d'une expression. */
static GBufferLine *g_cond_expression_print(const GCondExpression *, GCodeBuffer *, GBufferLine *, GLangOutput *);

/* Réalise une négation sur une expression décompilée. */
static bool g_cond_expression_negate(GCondExpression *);



/* Indique le type défini pour une condition binaire quelconque. */
G_DEFINE_TYPE(GCondExpression, g_cond_expression, G_TYPE_DEC_EXPRESSION);


/******************************************************************************
*                                                                             *
*  Paramètres  : klass = classe à initialiser.                                *
*                                                                             *
*  Description : Initialise la classe des conditions binaires quelconques.    *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_cond_expression_class_init(GCondExpressionClass *klass)
{

}


/******************************************************************************
*                                                                             *
*  Paramètres  : cond = instance à initialiser.                               *
*                                                                             *
*  Description : Initialise une instance de condition binaire quelconque.     *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_cond_expression_init(GCondExpression *cond)
{
    GDecInstruction *instr;                 /* Autre version de l'objet    */
    GDecExpression *expr;                   /* Autre version de l'objet    */

    instr = G_DEC_INSTRUCTION(cond);

    instr->visit = (dec_instr_visit_fc)g_cond_expression_visit;
    instr->replace = (dec_instr_replace_fc)g_cond_expression_replace;
    instr->print = (dec_instr_print_fc)g_cond_expression_print;

    expr = G_DEC_EXPRESSION(cond);

    expr->negate = (dec_expr_negate_fc)g_cond_expression_negate;

    cond->operator = COT_NONE;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : exp = expression sur laquelle repose la condition.           *
*                                                                             *
*  Description : Exprime une condition binaire quelconque.                    *
*                                                                             *
*  Retour      : Expression mise en place.                                    *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GDecExpression *g_cond_expression_new(GDecExpression *exp)
{
    GCondExpression *result;              /* Expression à retourner      */

    result = g_object_new(G_TYPE_COND_EXPRESSION, NULL);

    result->exp = exp;

    return G_DEC_EXPRESSION(result);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : expr     = 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_cond_expression_visit(GCondExpression *expr, dec_instr_visitor_cb callback, DecInstrVisitFlags flags, void *data)
{
    bool result;                            /* Bilan à retourner           */
    size_t i;                               /* Boucle de parcours          */

    if (expr->operator == COT_NONE)
        result = _g_dec_instruction_visit(G_DEC_INSTRUCTION(expr->exp), G_DEC_INSTRUCTION(expr),
                                          callback, flags, data);

    else
    {
        result = true;

        for (i = 0; i < expr->count && result; i++)
            result = _g_dec_instruction_visit(G_DEC_INSTRUCTION(expr->conds[i]),
                                              G_DEC_INSTRUCTION(expr),
                                              callback, flags, data);

    }

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : expr = 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_cond_expression_replace(GCondExpression *expr, GDecInstruction *old, GDecInstruction *new)
{
    bool result;                            /* Bilan à retourner           */
    size_t i;                               /* Boucle de parcours          */

    if (expr->operator == COT_NONE)
    {
        if (expr->exp == G_DEC_EXPRESSION(old))
        {
            g_object_unref(G_OBJECT(expr->exp));
            g_object_ref(G_OBJECT(new));
            expr->exp = G_DEC_EXPRESSION(new);

            result = true;

        }
        else
            result = g_dec_instruction_replace(G_DEC_INSTRUCTION(expr->exp), old, new);

    }
    else
    {
        result = false;

        for (i = 0; i < expr->count; i++)
        {
            if (expr->conds[i] == G_COND_EXPRESSION(old))
            {
                g_object_unref(G_OBJECT(expr->conds[i]));
                g_object_ref(G_OBJECT(new));
                expr->conds[i] = G_COND_EXPRESSION(new);

                result = true;

            }
            else
                result |= g_dec_instruction_replace(G_DEC_INSTRUCTION(expr->conds[i]), old, new);

        }

    }

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : expr   = expression à 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 expression.    *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static GBufferLine *g_cond_expression_print(const GCondExpression *expr, GCodeBuffer *buffer, GBufferLine *line, GLangOutput *output)
{
    GBufferLine *result;                    /* Ligne à retourner           */
    size_t i;                               /* Boucle de parcours          */

    result = g_lang_output_encapsulate_condition(output, buffer, line, true);

    if (expr->operator == COT_NONE)
        result = g_dec_instruction_print(G_DEC_INSTRUCTION(expr->exp),
                                         buffer, result, output);

    else
        for (i = 0; i < expr->count; i++)
        {
            if (i > 0)
                g_lang_output_write_cond_operator(output, result, expr->operator);

            result = g_dec_instruction_print(G_DEC_INSTRUCTION(expr->conds[i]),
                                             buffer, result, output);

        }

    result = g_lang_output_encapsulate_condition(output, buffer, result, false);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : cond = instance à traiter.                                   *
*                                                                             *
*  Description : Réalise une négation sur une expression décompilée.          *
*                                                                             *
*  Retour      : true si la négation a été gérée, false sinon.                *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_cond_expression_negate(GCondExpression *cond)
{
    size_t i;                               /* Boucle de parcours          */

    if (cond->operator == COT_NONE)
        g_dec_expression_negate(cond->exp);

    else
    {
        cond->operator = (cond->operator == COT_AND ? COT_OR : COT_AND);

        for (i = 0; i < cond->count; i++)
            g_dec_expression_negate(G_DEC_EXPRESSION(cond->conds[i]));

    }

    return true;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : cond = instance à consulter.                                 *
*                                                                             *
*  Description : Fournit l'opération logique entre les comparaisons.          *
*                                                                             *
*  Retour      : Opération liant les différentes parties, ou COT_NONE.        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

CondOperatorType g_cond_expression_get_operator(const GCondExpression *cond)
{
    return cond->operator;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : cond = instance à consulter.                                 *
*                                                                             *
*  Description : Fournit l'expression d'une condition.                        *
*                                                                             *
*  Retour      : Expression sur laquelle repose la condition.                 *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GDecExpression *g_cond_expression_get_expression(const GCondExpression *cond)
{
    return cond->exp;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : cond = instance à modifier.                                  *
*                exp  = expression sur laquelle repose la condition.          *
*                                                                             *
*  Description : Définit l'expression d'une condition.                        *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_cond_expression_set_expression(GCondExpression *cond, GDecExpression *exp)
{
    if (cond->exp != NULL)
        g_object_unref(G_OBJECT(cond->exp));

    cond->exp = exp;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : cond  = instance à compléter.                                *
*                extra = condition à rajouter.                                *
*                op    = lien avec le reste de la condition.                  *
*                                                                             *
*  Description : Etend l'expression d'une condition.                          *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_cond_expression_add_condition(GCondExpression *cond, GCondExpression *extra, CondOperatorType op)
{
    GCondExpression *existing;              /* Expr. première revalorisée  */

    if (cond->operator == COT_NONE)
    {
        existing = G_COND_EXPRESSION(g_cond_expression_new(cond->exp));
        goto gceac_replace_existing;
    }
    else
    {
        if (cond->operator == op)
        {
            cond->conds = (GCondExpression **)calloc(++cond->count, sizeof(GCondExpression));
            cond->conds[cond->count - 1] = extra;
        }
        else
        {
            existing = G_COND_EXPRESSION(g_cond_expression_new(NULL));

            existing->operator = cond->operator;
            existing->conds = cond->conds;
            existing->count = cond->count;

            goto gceac_replace_existing;

        }

    }

 gceac_done:

    return;

 gceac_replace_existing:

    cond->operator = op;

    cond->conds = (GCondExpression **)calloc(2, sizeof(GCondExpression));
    cond->conds[0] = existing;
    cond->conds[1] = extra;

    cond->count = 2;

    goto gceac_done;

}