#include "utils.h"
#include "ctinfo.h"
#include "free.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char* get_dim_name(char* array_name, int dim_number)
{
    size_t required_size = snprintf(NULL, 0, "__%s_%d", array_name, dim_number) + 1;
    char*  result        = malloc(required_size);
    size_t res           = snprintf(result, required_size, "__%s_%d", array_name, dim_number);
    if (res >= required_size)
    {
        free(result);
        CTIabort("Unable to sprintf dimension variable name.");
    }
    return result;
}

/* Returns a newly malloced string containing the name of an array initializer variable.
 * This might sound confusing when it comes to the difference between this function and the next.
 * If you write the code
 * int[2] a = func();
 * this function returns the variable name that func() is assigned to.
 * The next function returns the index variable name. */
char* get_array_while_name(char* array_name)
{
    size_t required_size = snprintf(NULL, 0, "___%s_while", array_name) + 1;
    char*  result        = malloc(required_size);
    size_t res           = snprintf(result, required_size, "___%s_while", array_name);
    if (res >= required_size)
    {
        free(result);
        CTIabort("Unable to sprintf array while variable name.");
    }
    return result;
}

/* Returns a newly malloced string containing the name of an array's while initializer loop's induction variable. */
char* get_array_while_var(char* array_name)
{
    size_t required_size = snprintf(NULL, 0, "___%s_while_var", array_name) + 1;
    char*  result        = malloc(required_size);
    size_t res           = snprintf(result, required_size, "___%s_while_var", array_name);
    if (res >= required_size)
    {
        free(result);
        CTIabort("Unable to sprintf array while variable name.");
    }
    return result;
}

int count_exprs(node* exprs)
{
    int n = 0;
    for (node* e = exprs; e != NULL; e = EXPRS_NEXT(e), n++)
        ;
    return n;
}

int count_ids(node* ids)
{
    int n = 0;
    for (node* i = ids; i != NULL; i = IDS_NEXT(i), n++)
        ;
    return n;
}


/* Takes a possibly nested arrexpr and retuns a flattened version with the same internal ordering. */

node* flatten_arrexpr(node* arrexpr, node* prev, node* next, node** tail)
{
    if (!arrexpr) return NULL;

    node* cur_exprs;
    node* first_exprs = NULL;
    node* prev_exprs  = prev;
    node* cur_expr;
    node* next_exprs;
    // node* prev_expr;

    for (cur_exprs = ARREXPR_EXPRS(arrexpr); cur_exprs != NULL; cur_exprs = next_exprs)
    {
        cur_expr   = EXPRS_EXPR(cur_exprs);
        next_exprs = EXPRS_NEXT(cur_exprs);
        if (NODE_TYPE(cur_expr) == N_arrexpr)
        {
            node* new_tail;
            node* exprs = flatten_arrexpr(cur_expr, prev_exprs, EXPRS_NEXT(cur_exprs), &new_tail);
            if (prev_exprs) EXPRS_NEXT(prev_exprs) = exprs;
            prev_exprs = new_tail;
            if (!first_exprs) first_exprs = exprs;
            EXPRS_NEXT(cur_exprs) = NULL;
            EXPRS_EXPR(cur_exprs) = NULL;
            FREEdoFreeTree(cur_exprs);

            // prev_expr = EXPRS_EXPR(prev_exprs);
        }
        else
        {
            if (!first_exprs) first_exprs = cur_exprs;
            // prev_expr = cur_expr;
            if (prev_exprs) EXPRS_NEXT(prev_exprs) = cur_exprs;
            prev_exprs = cur_exprs;
        }
    }

    ARREXPR_EXPRS(arrexpr) = NULL;
    FREEdoFreeTree(arrexpr);

    if (tail)
        *tail = prev_exprs;
    else
        first_exprs = TBmakeArrexpr(first_exprs);

    EXPRS_NEXT(prev_exprs) = next;
    return first_exprs;
}


void _helper_get_dims_expr(node* n, struct st_entry_arr* arr_ent)
{
    VAR_STLINK(n)     = st_lookup(arr_ent->containing_table, VAR_NAME(n), LOOKUP_LOCAL, NULL);
    VAR_RELFRAMENO(n) = 0;
}

/* Returns an expression equal to the product of all dimension expressions.  */

node* get_dims_expr(struct st_entry_arr* arr_ent)
{
    struct st_entry* cur_ent   = arr_ent->dim_vars[0];
    node*            prev_root = TBmakeVar(strdup(cur_ent->name), NULL);

    _helper_get_dims_expr(prev_root, arr_ent);

    for (int i = 1; i < arr_ent->n_dims; i++)
    {
        cur_ent       = arr_ent->dim_vars[i];
        node* new_var = TBmakeVar(strdup(cur_ent->name), NULL);

        _helper_get_dims_expr(new_var, arr_ent);

        prev_root                 = TBmakeBinop(BO_mul, prev_root, new_var);
        BINOP_TYPE(prev_root)     = TY_int;
        BINOP_EXPRTYPE(prev_root) = TY_int;
    }

    return prev_root;
}