/*****************************************************************************
 *
 * Module: array_def_rewrite
 *
 * Prefix: ADR
 *
 * Description:
 *
 * This module rewrites array definitions so that their dimensions are defined separately.
 *
 * (i.e. int[m] a; -> int[] a; int m;)
 *
 *****************************************************************************/

#include "array_def_rewrite.h"
#include "dbug.h"
#include "mytypes.h"
#include "queue.h"
#include "symbol_table.h"
#include "traverse.h"
#include "tree_basic.h"
#include "types.h"
#include <string.h>

#include "copy.h"
#include "ctinfo.h"
#include "memory.h"
#include "mymacros.h"
#include "utils.h"
#include "free.h"

/*
 * Traversal functions
 */

node* ADRparam(node* arg_node, info* arg_info)
{
    DBUG_ENTER("ADRparam");

    if (!PARAM_IS_ARRAY(arg_node))
    {
        TO(PARAM_NEXT);
        DBUG_RETURN(arg_node);
    }

    /* Insert each dimension variable as a separate parameter before the array. */

    node* first_dim_par = NULL;
    node* prev_dim_par  = NULL;
    node* cur_dim_par   = NULL;

    for (node* id = PARAM_DIMS(arg_node); id != NULL; prev_dim_par = cur_dim_par, id = IDS_NEXT(id))
    {
        cur_dim_par = TBmakeParam(TY_int | TY_const, strdup(IDS_NAME(id)), NULL, NULL);

        if (prev_dim_par) PARAM_NEXT(prev_dim_par) = cur_dim_par;

        if (!first_dim_par) first_dim_par = cur_dim_par;
    }

    PARAM_NEXT(prev_dim_par) = arg_node;

    TO(PARAM_NEXT);

    DBUG_RETURN(first_dim_par);
}

node* ADRvardecl(node* arg_node, info* arg_info)
{
    DBUG_ENTER("ADRvardecl");

    if (!VARDECL_IS_ARRAY(arg_node))
    {
        TO(VARDECL_NEXT);
        DBUG_RETURN(arg_node);
    }

    char* name = VARDECL_NAME(arg_node);

    node* first_dim_decl = NULL;
    node* prev_dim_decl  = NULL;
    node* cur_dim_decl   = NULL;
    int   n_dim          = 0;

    node* dim_copy = COPYdoCopy(VARDECL_DIMS(arg_node));

    for (node* dim_exprs = VARDECL_DIMS(arg_node); dim_exprs != NULL; prev_dim_decl = cur_dim_decl)
    {
        cur_dim_decl = TBmakeVardecl(TY_int | TY_const, get_dim_name(name, n_dim),
                                     EXPRS_EXPR(dim_exprs), NULL, NULL);

        if (prev_dim_decl) VARDECL_NEXT(prev_dim_decl) = cur_dim_decl;

        n_dim++;
        if (!first_dim_decl) first_dim_decl = cur_dim_decl;

        node* old_exprs = dim_exprs;
        dim_exprs       = EXPRS_NEXT(old_exprs);
        EXPRS_NEXT(old_exprs) = NULL;
        EXPRS_EXPR(old_exprs) = NULL;
        FREEdoFreeTree(old_exprs);
    }

    VARDECL_NEXT(prev_dim_decl) = arg_node;
    VARDECL_DIMS(arg_node)      = dim_copy;

    TO(VARDECL_NEXT);

    DBUG_RETURN(first_dim_decl);
}

node* ADRdecls(node* arg_node, info* arg_info)
{
    node* decl = DECLS_DECL(arg_node);

    if (NODE_TYPE(decl) != N_globaldef || !GLOBALDEF_IS_ARRAY(decl))
    {
        TO(DECLS_DECL);
        TO(DECLS_NEXT);
        return arg_node;
    }

    node* result = NULL;

    node* prev_decls = NULL;
    node* dim_decls  = NULL;

    int n_param = 0;
    for (node* id = GLOBALDEF_DIMS(decl); id != NULL;
         n_param++, prev_decls = dim_decls)
    {
        char* generated_dim_name = get_dim_name(GLOBALDEF_NAME(decl), n_param);
        node* dim_decl, *extern_decls;

        if (GLOBALDEF_ISEXTERN(decl))
        {
            char* given_dim_name = IDS_NAME(id);

            dim_decl = TBmakeGlobaldef(TY_int | TY_const, strdup(given_dim_name),
                                       TBmakeVar(strdup(generated_dim_name), NULL), NULL);
        }

        node* extern_decl = TBmakeGlobaldef(TY_int | TY_const, generated_dim_name, NULL, NULL);
        GLOBALDEF_ISEXTERN(extern_decl)   = GLOBALDEF_ISEXTERN(decl);
        GLOBALDEF_ISEXPORTED(extern_decl) = GLOBALDEF_ISEXPORTED(decl);

        if (GLOBALDEF_ISEXTERN(decl))
        {
            dim_decls          = TBmakeDecls(dim_decl, NULL);
            extern_decls = TBmakeDecls(extern_decl, dim_decls);
            id = IDS_NEXT(id);
        }
        else
        {
            GLOBALDEF_INIT(extern_decl) = COPYdoCopy(EXPRS_EXPR(id));
            extern_decls = TBmakeDecls(extern_decl, NULL);
            dim_decls          = extern_decls;
            id = EXPRS_NEXT(id);
        }

        if (n_param == 0) result = extern_decls;

        if (prev_decls) DECLS_NEXT(prev_decls) = extern_decls;
    }

    DECLS_NEXT(dim_decls) = arg_node;
    TO(DECLS_NEXT);

    return result;
}

node* ADRdoRewrite(node* syntaxtree)
{
    DBUG_ENTER("ADRdoRewrite");

    TRAVpush(TR_adr);
    syntaxtree = TRAVdo(syntaxtree, NULL);
    TRAVpop();

    DBUG_RETURN(syntaxtree);
}
