/* Chrysalide - Outil d'analyse de fichiers binaires
 * component.c - représentation des composants extraits de l'ABI C++ Itanium
 *
 * Copyright (C) 2013 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 "component.h"


#include <malloc.h>
#include <string.h>


#include "component-int.h"
#include "../../../common/extstr.h"
#include "../../../common/fnv1a.h"




/* Procédure à appliquer sur un composant visité */
typedef void (* visit_comp_fc) (itanium_component *);



#define reset_comp_hash(c) c->hash = 0

/* Visite les composants en présence. */
static void visit_comp(itanium_component *, visit_comp_fc);



/******************************************************************************
*                                                                             *
*  Paramètres  : comp    = composant à traiter.                               *
*                visitor = fonction à appliquer sur les composants présents.  *
*                                                                             *
*  Description : Visite les composants en présence.                           *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void visit_comp(itanium_component *comp, visit_comp_fc visitor)
{
    itanium_component *sub;                 /* Sous-partie de composant    */

    switch (comp->type)
    {
        case ICT_NESTED_NAME:
        case ICT_TEMPLATE_NAME_ARGS:
            visit_comp(comp->binary.left, visitor);
            visit_comp(comp->binary.right, visitor);
            break;

        case ICT_PREFIX_UNARY:
        case ICT_TPREFIX_UNARY:
            visit_comp(comp->unary, visitor);
            break;

        case ICT_PREFIX_BINARY:
        case ICT_TPREFIX_BINARY:
            visit_comp(comp->binary.left, visitor);
            visit_comp(comp->binary.right, visitor);
            break;

        case ICT_FUNCTION_THUNK:
            visit_comp(comp->binary.left, visitor);
            visit_comp(comp->binary.right, visitor);
            break;

        case ICT_FUNCTION_COVARIANT_THUNK:
            visit_comp(comp->ternary.first, visitor);
            visit_comp(comp->ternary.second, visitor);
            visit_comp(comp->ternary.third, visitor);
            break;

        case ICT_POINTER_TO:
            visit_comp(comp->unary, visitor);
            break;

        case ICT_REFERENCE_TO:
            visit_comp(comp->unary, visitor);
            break;

        case ICT_RVALUE_REFERENCE_TO:
            visit_comp(comp->unary, visitor);
            break;

        case ICT_COMPLEX_PAIR:
            visit_comp(comp->unary, visitor);
            break;

        case ICT_IMAGINARY:
            visit_comp(comp->unary, visitor);
            break;

        case ICT_FUNCTION_ENCODING:

            /* Retour ? */

            sub = NULL;//IT_BINARY_COMP(IT_BINARY_COMP(comp).right).left;

            if (sub != NULL)
                visit_comp(sub, visitor);

            /* Nom de la fonction */
            visit_comp(comp->binary.left, visitor);

            visit_comp(comp->binary.right, visitor);

            break;

        case ICT_TEMPLATE_ARGS:
            visit_comp(comp->unary, visitor);
            break;

        case ICT_TYPES_LIST:

            visit_comp(comp->binary.left, visitor);

            if (comp->binary.right != NULL)
                visit_comp(comp->binary.right, visitor);

            break;

        default:
            break;

    }

    visitor(comp);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : comp = composant à mettre à jour.                            *
*                                                                             *
*  Description : Incrémente le nombre d'utilisation du composant.             *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void itd_ref_comp(itanium_component *comp)
{
    void visit_for_ref(itanium_component *comp)
    {
        comp->refcount++;

    }

    visit_comp(comp, visit_for_ref);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : comp = composant à mettre à jour.                            *
*                                                                             *
*  Description : Décrémente le nombre d'utilisation du composant.             *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void itd_unref_comp(itanium_component *comp)
{
    return;

    void visit_for_unref(itanium_component *comp)
    {
        if (--comp->refcount == 0)
        {
            if (comp->type == ICT_TYPE)
                g_object_unref(G_OBJECT(comp->dtype));

            g_itanium_dcontext_mark_component_as_free(comp->context, comp);

        }

    }

    visit_comp(comp, visit_for_unref);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : comp = composant à manipuler.                                *
*                                                                             *
*  Description : Détermine ou fournit l'empreinte d'un composant.             *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

fnv64_t itd_hash_comp(itanium_component *comp)
{
    char *desc;                             /* Description du composant    */

    if (comp->hash == 0)
    {
        desc = itd_translate_component(comp->context, comp, NULL);
        comp->hash = fnv_64a_hash(desc);
        free(desc);
    }

    return comp->hash;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : context = contexte de décodage à utiliser.                   *
*                                                                             *
*  Description : Construit un composant dans un contexte Itanium.             *
*                                                                             *
*  Retour      : Composant extrait ou NULL en cas d'échec.                    *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

itanium_component *itd_make_empty(GItaniumDContext *context)
{
    itanium_component *result;              /* Composant à renvoyer        */

    result = g_itanium_dcontext_get_empty_component(context);

    result->type = ICT_EMPTY;

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : context = contexte de décodage à utiliser.                   *
*                str     = chaîne de caractères à conserver.                  *
*                len     = taille de l'identifiant à retrouver.               *
*                                                                             *
*  Description : Construit un composant dans un contexte Itanium.             *
*                                                                             *
*  Retour      : Composant extrait ou NULL en cas d'échec.                    *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

itanium_component *itd_make_name(GItaniumDContext *context, const char *str, size_t len)
{
    itanium_component *result;              /* Composant à renvoyer        */

    result = g_itanium_dcontext_get_empty_component(context);

    result->type = ICT_NAME;
    result->s_name.str = str;
    result->s_name.len = len;

    return result;

}




/******************************************************************************
*                                                                             *
*  Paramètres  : context = contexte de décodage à utiliser.                   *
*                info    = information de base sur l'opérateur manipulé.      *
*                                                                             *
*  Description : Construit un composant dans un contexte Itanium.             *
*                                                                             *
*  Retour      : Composant extrait ou NULL en cas d'échec.                    *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

itanium_component *itd_make_operator(GItaniumDContext *context, const itanium_operator_info *info)
{
    itanium_component *result;              /* Composant à renvoyer        */

    result = g_itanium_dcontext_get_empty_component(context);

    result->type = ICT_OPERATOR_NAME;
    result->operator.otype = IOT_SIMPLE;
    result->operator.info = *info;

    return result;

}




/******************************************************************************
*                                                                             *
*  Paramètres  : context = contexte de décodage à utiliser.                   *
*                type    = type exacte de décallage.                          *
*                offset  = décallage extrait de l'encodage.                   *
*                                                                             *
*  Description : Construit un composant dans un contexte Itanium.             *
*                                                                             *
*  Retour      : Composant extrait ou NULL en cas d'échec.                    *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

itanium_component *itd_make_offset(GItaniumDContext *context, ItaniumComponentType type, ssize_t offset)
{
    itanium_component *result;              /* Composant à renvoyer        */

    result = g_itanium_dcontext_get_empty_component(context);

    result->type = type;
    result->offset = offset;

    return result;

}




/******************************************************************************
*                                                                             *
*  Paramètres  : context = contexte de décodage à utiliser.                   *
*                dtype   = instance de type en place à conserver.             *
*                                                                             *
*  Description : Construit un composant dans un contexte Itanium.             *
*                                                                             *
*  Retour      : Composant extrait ou NULL en cas d'échec.                    *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

itanium_component *itd_make_type(GItaniumDContext *context, GDataType *dtype)
{
    itanium_component *result;              /* Composant à renvoyer        */

    result = g_itanium_dcontext_get_empty_component(context);

    result->type = ICT_TYPE;
    result->dtype = dtype;

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : context = contexte de décodage à utiliser.                   *
*                type    = type du composant à mettre en place.               *
*                left    = premier composant à associer.                      *
*                right   = second composant à associer.                       *
*                                                                             *
*  Description : Construit un composant dans un contexte Itanium.             *
*                                                                             *
*  Retour      : Composant extrait ou NULL en cas d'échec.                    *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

itanium_component *itd_make_binary(GItaniumDContext *context, ItaniumComponentType type, itanium_component *left, itanium_component *right)
{
    itanium_component *result;              /* Composant à renvoyer        */

    result = g_itanium_dcontext_get_empty_component(context);

    result->type = type;
    result->binary.left = left;
    result->binary.right = right;

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : context = contexte de décodage à utiliser.                   *
*                type    = type du composant à mettre en place.               *
*                left    = second composant à associer.                       *
*                                                                             *
*  Description : Construit un composant dans un contexte Itanium.             *
*                                                                             *
*  Retour      : Composant extrait ou NULL en cas d'échec.                    *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

itanium_component *itd_append_right_to_binary(GItaniumDContext *context, ItaniumComponentType type, itanium_component *parent, itanium_component *left)
{
    itanium_component *result;              /* Composant à renvoyer        */
    itanium_component *iter;                /* Boucle de parcours          */

    result = g_itanium_dcontext_get_empty_component(context);

    result->type = type;
    result->binary.left = left;

    if (parent != NULL)
    {
        for (iter = parent; iter->binary.right != NULL; iter = iter->binary.right)
            reset_comp_hash(iter);
        iter->binary.right = result;
    }

    return (parent != NULL ? parent : result);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : context = contexte de décodage à utiliser.                   *
*                type    = type du composant à mettre en place.               *
*                unary   = sous-composant à associer.                         *
*                                                                             *
*  Description : Construit un composant dans un contexte Itanium.             *
*                                                                             *
*  Retour      : Composant extrait ou NULL en cas d'échec.                    *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

itanium_component *itd_make_unary(GItaniumDContext *context, ItaniumComponentType type, itanium_component *unary)
{
    itanium_component *result;              /* Composant à renvoyer        */

    result = g_itanium_dcontext_get_empty_component(context);

    result->type = type;
    result->unary = unary;

    return result;

}





/******************************************************************************
*                                                                             *
*  Paramètres  : comp = composant à mettre à jour.                            *
*                type = type à redéfinir pour le composant.                   *
*                                                                             *
*  Description : Modifie légèrement le type d'un composant donné.             *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void itd_set_type(itanium_component *comp, ItaniumComponentType type)
{
    comp->type = type;

    reset_comp_hash(comp);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : comp = composant à consulter.                                *
*                                                                             *
*  Description : Fournit le type d'un composant issu d'un contexte Itanium.   *
*                                                                             *
*  Retour      : Type enregistré.                                             *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

ItaniumComponentType itd_get_component_type(const itanium_component *comp)
{
    return comp->type;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : context = contexte de décodage à utiliser.                   *
*                comp    = second composant à associer.                       *
*                base    = éventuelle base à compléter ou NULL si aucune.     *
*                                                                             *
*  Description : Traduit les composants de contexte Itanium.                  *
*                                                                             *
*  Retour      : Traduction en format humainement lisible effectuée.          *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

char *itd_translate_component(GItaniumDContext *context, const itanium_component *comp, char *base)
{
    char *result;                           /* Chaîne à retourner          */
    char *name;                             /* Désignation à copier        */
    const itanium_component *sub;           /* Sous-partie de composant    */

    //if (base != NULL)
    //printf(".... %s\n", base);

    switch (comp->type)
    {
        case ICT_EMPTY:
            result = base;
            break;

        case ICT_NAME:
        case ICT_STD_SUBST:
            result = strnadd(base, comp->s_name.str, comp->s_name.len);
            break;

        case ICT_NESTED_NAME:
            result = itd_translate_component(context, comp->binary.left, base);
            if (comp->binary.right->type != ICT_TEMPLATE_ARGS)
                result = stradd(result, "::");
            result = itd_translate_component(context, comp->binary.right, result);
            break;

        case ICT_TEMPLATE_NAME_ARGS:
            result = itd_translate_component(context, comp->binary.left, base);
            result = itd_translate_component(context, comp->binary.right, result);
            break;

        case ICT_PREFIX_UNARY:
        case ICT_TPREFIX_UNARY:
            result = stradd(base, "::");
            result = itd_translate_component(context, comp->unary, result);
            break;

        case ICT_PREFIX_BINARY:
        case ICT_TPREFIX_BINARY:
            result = itd_translate_component(context, comp->binary.left, base);
            result = stradd(result, "::");
            result = itd_translate_component(context, comp->binary.right, result);
            break;


        case ICT_OPERATOR_NAME:
            switch (comp->operator.otype)
            {
                case IOT_SIMPLE:
                    result = stradd(base, comp->operator.info.name);
                    break;
                case IOT_CAST:
                    result = stradd(base, "TODO_CAST");
                    break;
                case IOT_VENDOR:
                    result = stradd(base, "TODO_VENDOR");
                    break;
                default:
                    result = NULL;
                    break;
            }
            break;


        case ICT_FUNCTION_THUNK:
            result = itd_translate_component(context, comp->binary.right, base);
            break;

        case ICT_FUNCTION_COVARIANT_THUNK:
            result = itd_translate_component(context, comp->ternary.third, base);
            break;


        case ICT_CONSTRUCTOR:
            result = stradd(base, "<ctor>");
            break;

        case ICT_DESSTRUCTOR:
            result = stradd(base, "<dtor>");
            break;

        case ICT_TYPE:

            name = g_data_type_to_string(comp->dtype);

            result = stradd(base, name);

            free(name);

            break;

        case ICT_POINTER_TO:
            result = itd_translate_component(context, comp->unary, base);
            result = stradd(result, " *");
            break;

        case ICT_REFERENCE_TO:
            result = itd_translate_component(context, comp->unary, base);
            result = stradd(result, " &");
            break;

        case ICT_RVALUE_REFERENCE_TO:
            result = itd_translate_component(context, comp->unary, base);
            result = stradd(result, " &");
            break;

        case ICT_COMPLEX_PAIR:
            result = stradd(base, "<?>");
            result = itd_translate_component(context, comp->unary, result);
            break;

        case ICT_IMAGINARY:
            result = stradd(base, "<?>");
            result = itd_translate_component(context, comp->unary, result);
            break;


        case ICT_FUNCTION_ENCODING:

            result = base;

            /* Retour ? */

            sub = NULL;//IT_BINARY_COMP(IT_BINARY_COMP(comp).right).left;

            if (sub != NULL)
                result = itd_translate_component(context, sub, result);
            else
                result = stradd(result, "???");

            result = stradd(result, " ");

            /* Nom de la fonction */
            result = itd_translate_component(context, comp->binary.left, result);

            result = stradd(result, "(");

            result = itd_translate_component(context, comp->binary.right, result);

            result = stradd(result, ")");


            break;

        case ICT_TEMPLATE_ARGS:
            result = stradd(base, "<");
            result = itd_translate_component(context, comp->unary, result);
            result = stradd(result, ">");
            break;

        case ICT_TYPES_LIST:

            result = itd_translate_component(context, comp->binary.left, base);

            if (comp->binary.right != NULL)
            {
                result = stradd(result, ", ");
                result = itd_translate_component(context, comp->binary.right, result);
            }


            //sub = IT_BINARY_COMP(IT_BINARY_COMP(comp).right).right;


            break;


        default: /* ICT_* */
            result = base;
            break;

    }



    return result;


}