#include <v_lib.h>
#include "eval.h"
#include "ipltrans.h"
#include "util.h"
#include "messages.h"
#include "istrings.h"
#include "efunc.h"

/*
 * There is only one object and one constructor for all types of
 * transforms, but there are several different (local) functions for
 * executing specific transforms.
 */

/*
 * If set, iterate through all elements; otherwise, call once
 */
#define IPL_FLAG_ITERATE    1

typedef struct ipl_transform_s
{
    u32_t       type;
    char_p      name;
    vhandle_t   nsl_group;
    u32_t       num_args;
    vec_p       var_pool;
    vec_p       argp;
    vp_stack_p  current_pass; /* used by callbacks */
    u8_stack_t  pass_flags;
    vp_stack_t  passes; /* each of vp_stack_p */
}   ipl_transform_t, *ipl_transform_p;

/*
 * Vector, vector pair transforms
 */
#define MAX_IDX_OFFS        (EXIT_CODE_OFFSET + 1)
#define IDX_OFFS            (EXIT_CODE_OFFSET + 2)

/*
 * Vector transform
 */
#define ELEM_OFFS           (IDX_OFFS + 1)

V_LOCAL nsl_var_entry_t ipl_vector_vars[] =
{
    {IPL_MAX_IDX_KEY,MAX_IDX_OFFS,IPL_MAX_IDX_DESC},
    {IPL_IDX_KEY,IDX_OFFS,IPL_IDX_DESC},
    {IPL_ELEM_KEY,ELEM_OFFS,IPL_ELEM_DESC},
    {NULL,0,NULL}
};

/*
 * Vector pair transform
 */
#define ELEM1_OFFS          (IDX_OFFS + 1)
#define ELEM2_OFFS          (IDX_OFFS + 2)

V_LOCAL nsl_var_entry_t ipl_vector_pair_vars[] =
{
    {IPL_MAX_IDX_KEY,MAX_IDX_OFFS,IPL_MAX_IDX_DESC},
    {IPL_IDX_KEY,IDX_OFFS,IPL_IDX_DESC},
    {IPL_ELEM1_KEY,ELEM1_OFFS,IPL_ELEM1_DESC},
    {IPL_ELEM2_KEY,ELEM2_OFFS,IPL_ELEM2_DESC},
    {NULL,0,NULL}
};

/*
 * Matrix, matrix pair, cartesian/polar, cartesian/polar pair transforms
 */
#define MAX_ROW_OFFS        (EXIT_CODE_OFFSET + 1)
#define MAX_COL_OFFS        (EXIT_CODE_OFFSET + 2)
#define ROW_OFFS            (EXIT_CODE_OFFSET + 3)
#define COL_OFFS            (EXIT_CODE_OFFSET + 4)

/*
 * Matrix transform
 */
#define CELL_OFFS           (COL_OFFS + 1)

V_LOCAL nsl_var_entry_t ipl_matrix_vars[] =
{
    {IPL_MAX_ROW_KEY,MAX_ROW_OFFS,IPL_MAX_ROW_DESC},
    {IPL_MAX_COL_KEY,MAX_COL_OFFS,IPL_MAX_COL_DESC},
    {IPL_ROW_KEY,ROW_OFFS,IPL_ROW_DESC},
    {IPL_COL_KEY,COL_OFFS,IPL_COL_DESC},
    {IPL_CELL_KEY,CELL_OFFS,IPL_CELL_DESC},
    {NULL,0,NULL}
};

/*
 * Matrix pair transform
 */
#define CELL1_OFFS          (COL_OFFS + 1)
#define CELL2_OFFS          (COL_OFFS + 2)

V_LOCAL nsl_var_entry_t ipl_matrix_pair_vars[] =
{
    {IPL_MAX_ROW_KEY,MAX_ROW_OFFS,IPL_MAX_ROW_DESC},
    {IPL_MAX_COL_KEY,MAX_COL_OFFS,IPL_MAX_COL_DESC},
    {IPL_ROW_KEY,ROW_OFFS,IPL_ROW_DESC},
    {IPL_COL_KEY,COL_OFFS,IPL_COL_DESC},
    {IPL_CELL1_KEY,CELL1_OFFS,IPL_CELL1_DESC},
    {IPL_CELL2_KEY,CELL2_OFFS,IPL_CELL2_DESC},
    {NULL,0,NULL}
};

/*
 * Cartesian transform of RVMFT
 */
#define NEG_FREQ_OFFS       (COL_OFFS + 1)
#define RE_OFFS             (COL_OFFS + 2)
#define IM_OFFS             (COL_OFFS + 3)

V_LOCAL nsl_var_entry_t ipl_cartesian_vars[] =
{
    {IPL_MAX_ROW_KEY,MAX_ROW_OFFS,IPL_MAX_ROW_DESC},
    {IPL_MAX_COL_KEY,MAX_COL_OFFS,IPL_MAX_COL_DESC},
    {IPL_ROW_KEY,ROW_OFFS,IPL_ROW_DESC},
    {IPL_COL_KEY,COL_OFFS,IPL_COL_DESC},
    {IPL_NEG_FREQ_KEY,NEG_FREQ_OFFS,IPL_NEG_FREQ_DESC},
    {IPL_RE_KEY,RE_OFFS,IPL_RE_DESC},
    {IPL_IM_KEY,IM_OFFS,IPL_IM_DESC},
    {NULL,0,NULL}
};

/*
 * Polar transform of RVMFT
 */

/*
#define NEG_FREQ_OFFS       (COL_OFFS + 1)
*/
#define MAG_OFFS            (COL_OFFS + 2)
#define PHASE_OFFS          (COL_OFFS + 3)

V_LOCAL nsl_var_entry_t ipl_polar_vars[] =
{
    {IPL_MAX_ROW_KEY,MAX_ROW_OFFS,IPL_MAX_ROW_DESC},
    {IPL_MAX_COL_KEY,MAX_COL_OFFS,IPL_MAX_COL_DESC},
    {IPL_ROW_KEY,ROW_OFFS,IPL_ROW_DESC},
    {IPL_COL_KEY,COL_OFFS,IPL_COL_DESC},
    {IPL_NEG_FREQ_KEY,NEG_FREQ_OFFS,IPL_NEG_FREQ_DESC},
    {IPL_MAG_KEY,MAG_OFFS,IPL_MAG_DESC},
    {IPL_PHASE_KEY,PHASE_OFFS,IPL_PHASE_DESC},
    {NULL,0,NULL}
};

/*
 * Cartesian transform of RVMFT pair
 */

/*
#define NEG_FREQ_OFFS       (COL_OFFS + 1)
*/
#define RE1_OFFS            (COL_OFFS + 2)
#define IM1_OFFS            (COL_OFFS + 3)
#define RE2_OFFS            (COL_OFFS + 4)
#define IM2_OFFS            (COL_OFFS + 5)

V_LOCAL nsl_var_entry_t ipl_cartesian_pair_vars[] =
{
    {IPL_MAX_ROW_KEY,MAX_ROW_OFFS,IPL_MAX_ROW_DESC},
    {IPL_MAX_COL_KEY,MAX_COL_OFFS,IPL_MAX_COL_DESC},
    {IPL_ROW_KEY,ROW_OFFS,IPL_ROW_DESC},
    {IPL_COL_KEY,COL_OFFS,IPL_COL_DESC},
    {IPL_NEG_FREQ_KEY,NEG_FREQ_OFFS,IPL_NEG_FREQ_DESC},
    {IPL_RE1_KEY,RE1_OFFS,IPL_RE1_DESC},
    {IPL_IM1_KEY,IM1_OFFS,IPL_IM1_DESC},
    {IPL_RE2_KEY,RE2_OFFS,IPL_RE2_DESC},
    {IPL_IM2_KEY,IM2_OFFS,IPL_IM2_DESC},
    {NULL,0,NULL}
};

/*
 * Polar transform of RVMFT pair
 */

/*
#define NEG_FREQ_OFFS       (COL_OFFS + 1)
*/
#define MAG1_OFFS           (COL_OFFS + 2)
#define PHASE1_OFFS         (COL_OFFS + 3)
#define MAG2_OFFS           (COL_OFFS + 4)
#define PHASE2_OFFS         (COL_OFFS + 5)

V_LOCAL nsl_var_entry_t ipl_polar_pair_vars[] =
{
    {IPL_MAX_ROW_KEY,MAX_ROW_OFFS,IPL_MAX_ROW_DESC},
    {IPL_MAX_COL_KEY,MAX_COL_OFFS,IPL_MAX_COL_DESC},
    {IPL_ROW_KEY,ROW_OFFS,IPL_ROW_DESC},
    {IPL_COL_KEY,COL_OFFS,IPL_COL_DESC},
    {IPL_NEG_FREQ_KEY,NEG_FREQ_OFFS,IPL_NEG_FREQ_DESC},
    {IPL_MAG1_KEY,MAG1_OFFS,IPL_MAG1_DESC},
    {IPL_PHASE1_KEY,PHASE1_OFFS,IPL_PHASE1_DESC},
    {IPL_MAG2_KEY,MAG2_OFFS,IPL_MAG2_DESC},
    {IPL_PHASE2_KEY,PHASE2_OFFS,IPL_PHASE2_DESC},
    {NULL,0,NULL}
};

nsl_const_entry_t ipl_builtin_consts[] =
{
    {IPL_PI_KEY,M_PI,IPL_PI_DESC},
    {IPL_E_KEY,(vec_t)2.71828182845904509,IPL_E_DESC},
    {IPL_MAXREAL_KEY,MAXVEC,IPL_MAXREAL_DESC},
    {IPL_REAL_ZERO_KEY,FLOAT_ZERO_EPSILON,IPL_REAL_ZERO_DESC},
    {NULL,0,NULL}
};

/*
 * Destructor
 */
V_LOCAL void
iplFreeTransform(ipl_transform_p self)
{
    if(!self)
        return;
    if(self->name)
        memFree(self->name);
    if(self->nsl_group != LIBV_INVALID_HANDLE)
        nslDestroyFunctionGroup(self->nsl_group);
    vpsClear(&self->passes);
    u8sClear(&self->pass_flags);
    memFree(self);
}

/*
 * iplLoadTransform()
 * Assumes that keyword and transform name _already_ read in.
 * Do _not_ preserves the position of 'lexer'
 */
V_LOCAL ipl_transform_p
iplLoadTransform(vhandle_t lexer,
    nsl_var_entry_p vars)
{
    CALLER_NAME(iplLoadTransform)
    char *ptr;
    u8_t flags;
    u32_t i, num_passes, var_offs = 0, arg0_offs = 0;
    vboolean arg_expected = true;
    vp_stack_p pass_ptr;
    nsl_var_entry_t var;
    bstack_t all_vars;
    ipl_transform_p self;
    self = (ipl_transform_p)memAllocZ(sizeof(ipl_transform_t));
    self->nsl_group = LIBV_INVALID_HANDLE;
    stackInit(&all_vars,sizeof(nsl_var_entry_t));
    if(!lexLast(lexer))
    {
        SYN_UNEXPECTED_EOF(lexer);
        goto err_exit;
    }
    self->name = strDup(lexLast(lexer));
    if(!lexNextIs(lexer,ipl_open_bracket))
    {
        SYN_EXPECTED(lexer,ipl_open_bracket);
        goto err_exit;
    }
    /* merge 'vars' with transform's argument list */
    var_offs = EXIT_CODE_OFFSET + 1;
    while(vars->name)
    {
        memCopy(&var,vars,sizeof(nsl_var_entry_t));
        V_ASSERT(var.offset == var_offs++);
        stackPush(&all_vars,&var);
        vars++;
    }
    arg0_offs = var_offs;
    for(;;)
    {
        ptr = lexNext(lexer);
        if(!ptr)
        {
            SYN_UNEXPECTED_EOF(lexer);
            goto err_exit;
        }
        if(STR_EQ(ptr,ipl_key_void))
        {
            if(arg0_offs != var_offs)
            {
                SYN_UNEXPECTED(lexer,ipl_key_void);
                goto err_exit;
            }
            if(!lexNextIs(lexer,ipl_close_bracket))
            {
                SYN_EXPECTED(lexer,ipl_close_bracket);
                goto err_exit;
            }
        }
        if(STR_EQ(ptr,ipl_close_bracket))
            break;
        if(STR_EQ(ptr,ipl_comma))
        {
            if(arg_expected)
            {
                SYN_UNEXPECTED(lexer,ipl_comma);
                goto err_exit;
            }
            arg_expected = true;
            continue;
        }
        arg_expected = false;
        var.name = strDup(ptr);
        var.offset = var_offs++;
        var.desc = NULL;
        stackPush(&all_vars,&var);
    }
    self->num_args = var_offs - arg0_offs;
    var.name = NULL;
    var.offset = 0;
    var.desc = NULL;
    stackPush(&all_vars,&var);
    vars = (nsl_var_entry_p)stackGetStorage(&all_vars);
    self->nsl_group = nslCreateFunctionGroup(vars,ipl_builtin_consts,
        ipl_unresolved_cb,NULL);
    V_ASSERT(self->nsl_group != LIBV_INVALID_HANDLE);
    if(!lexNextIs(lexer,ipl_open_brace))
    {
        SYN_EXPECTED(lexer,ipl_open_brace);
        goto err_exit;
    }
    num_passes = 0;
    lexNext(lexer);
    for(;;)
    {
        ptr = lexLast(lexer);
        if(!ptr)
        {
            SYN_UNEXPECTED_EOF(lexer);
            goto err_exit;
        }
        if(STR_EQ(ptr,ipl_key_step))
        {
            flags = 0;
        }
        else
        {
            if(!STR_EQ(ptr,ipl_key_pass))
            {
                u32_t len;
                char *buf;
                len = strlen(ipl_key_step) + strlen(ipl_key_pass) + 2;
                buf = (char*)memAlloc(INT_ALIGN(len));
                sprintf(buf,"%s|%s",ipl_key_step,ipl_key_pass);
                SYN_EXPECTED(lexer,buf);
                memFree(buf);
                goto err_exit;
            }
            flags = IPL_FLAG_ITERATE;
        }
        if(!nslParseFunction(self->nsl_group,lexer))
            goto err_exit;
        u8sPush(&self->pass_flags,flags);
        num_passes++;
        if(lexNextIs(lexer,ipl_close_brace))
            break;
    }
    /* at least one pass was parsed */
    V_ASSERT(nslCreateExecutables(self->nsl_group));
    V_ASSERT(nslGetExecutable(self->nsl_group,0,NULL,&self->var_pool));
    for(i = 0;i < num_passes;i++)
    {
        V_ASSERT(nslGetExecutable(self->nsl_group,i,&pass_ptr,NULL));
        vpsPush(&self->passes,pass_ptr);
    }
    self->argp = self->var_pool + arg0_offs;
    goto normal_exit;
err_exit:;
    iplFreeTransform(self);
    self = NULL;
normal_exit:;
    stackPop(&all_vars,&var);
    while(var_offs > arg0_offs)
    {
        stackPop(&all_vars,&var);
        memFree(var.name);
        var_offs--;
    }
    stackClear(&all_vars);
    return self;
}

/*
 * Vector transforms
 */

V_LOCAL void
iplVectorTransform(vector_p v,u32_t start,u32_t end,
    ipl_transform_p trans)
{
    u8_t flags;
    u32_t i, j, num_passes;
    vec_p vars = trans->var_pool;
    vp_stack_p pass;
    V_ASSERT(trans->type == IPL_VECTOR_TRANSFORM);
    if(end >= v->size)
        end = v->size;
    if(start > end)
        start = end;
    vars[MAX_IDX_OFFS] = (vec_t)(v->size - 1);
    num_passes = vpsCount(&trans->passes);
    for(j = 0;j < num_passes;j++)
    {
        pass = (vp_stack_p)vpsGet(&trans->passes,j);
        flags = u8sGet(&trans->pass_flags,j);
        if(!(flags & IPL_FLAG_ITERATE))
        {
            NSL_EXECUTE(pass,vars);
            continue;
        }
        for(i = start;i <= end;i++)
        {
            vars[IDX_OFFS] = (vec_t)i;
            vars[ELEM_OFFS] = v->elements[i];
            NSL_EXECUTE(pass,vars);
            v->elements[i] = vars[ELEM_OFFS];
        }
    }
}

V_LOCAL void
iplVectorPairTransform(vector_p v1,vector_p v2,u32_t start,u32_t end,
    ipl_transform_p trans)
{
    u8_t flags;
    u32_t minsize, i, j, num_passes;
    vec_p vars = trans->var_pool;
    vp_stack_p pass;
    minsize = (v1->size < v2->size) ? v1->size : v2->size;
    V_ASSERT(trans->type == IPL_VECTOR_PAIR_TRANSFORM);
    vars[MAX_IDX_OFFS] = (vec_t)(minsize - 1);
    if(end >= minsize)
        end = minsize;
    if(start > end)
        start = end;
    num_passes = vpsCount(&trans->passes);
    for(j = 0;j < num_passes;j++)
    {
        pass = (vp_stack_p)vpsGet(&trans->passes,j);
        flags = u8sGet(&trans->pass_flags,j);
        if(!(flags & IPL_FLAG_ITERATE))
        {
            NSL_EXECUTE(pass,vars);
            continue;
        }
        for(i = start;i <= end;i++)
        {
            vars[IDX_OFFS] = (vec_t)i;
            vars[ELEM1_OFFS] = v1->elements[i];
            vars[ELEM2_OFFS] = v2->elements[i];
            NSL_EXECUTE(pass,vars);
            v1->elements[i] = vars[ELEM1_OFFS];
            v2->elements[i] = vars[ELEM2_OFFS];
        }
    }
}

/*
 * Matrix transforms
 */

V_LOCAL void
ipl_clip_rect(u32_t *left,u32_t *top,u32_t *right,u32_t *bottom,
    u32_t max_right, u32_t max_bottom)
{
    if(*right >= max_right)
        *right = max_right;
    if(*bottom >= max_bottom)
        *bottom = max_bottom;
    if(*left > *right)
        *left = *right;
    if(*top > *bottom)
        *top = *bottom;
}

V_LOCAL void
iplMatrixTransform(matrix_p m,
    u32_t left,u32_t top,u32_t right,u32_t bottom,
    ipl_transform_p trans)
{
    u8_t flags;
    u32_t i, j, k, num_passes;
    vec_p vars = trans->var_pool;
    vp_stack_p pass;
    V_ASSERT(trans->type == IPL_MATRIX_TRANSFORM);
    vars[MAX_ROW_OFFS] = (vec_t)(m->rows - 1);
    vars[MAX_COL_OFFS] = (vec_t)(m->cols - 1);
    ipl_clip_rect(&left,&top,&right,&bottom,m->cols - 1,m->rows - 1);
    num_passes = vpsCount(&trans->passes);
    for(k = 0;k < num_passes;k++)
    {
        pass = (vp_stack_p)vpsGet(&trans->passes,k);
        flags = u8sGet(&trans->pass_flags,k);
        if(!(flags & IPL_FLAG_ITERATE))
        {
            NSL_EXECUTE(pass,vars);
            continue;
        }
        for(i = top;i <= bottom;i++)
        {
            vars[ROW_OFFS] = (vec_t)i;
            for(j = left;j <= right;j++)
            {
                vars[COL_OFFS] = (vec_t)j;
                vars[CELL_OFFS] = m->cell[i][j];
                NSL_EXECUTE(pass,vars);
                m->cell[i][j] = vars[CELL_OFFS];
            }
        }
    }
}

V_LOCAL void
iplMatrixPairTransform(matrix_p m1,matrix_p m2,
    u32_t left,u32_t top,u32_t right,u32_t bottom,
    ipl_transform_p trans)
{
    u8_t flags;
    u32_t i, j, k, num_passes, min_rows, min_cols;
    vec_p vars = trans->var_pool;
    vp_stack_p pass;
    V_ASSERT(trans->type == IPL_MATRIX_PAIR_TRANSFORM);
    min_rows = (m1->rows < m2->rows) ? m1->rows : m2->rows;
    min_cols = (m1->cols < m2->cols) ? m1->cols : m2->cols;
    vars[MAX_ROW_OFFS] = (vec_t)(min_rows - 1);
    vars[MAX_COL_OFFS] = (vec_t)(min_cols - 1);
    ipl_clip_rect(&left,&top,&right,&bottom,min_cols - 1,min_rows - 1);
    num_passes = vpsCount(&trans->passes);
    for(k = 0;k < num_passes;k++)
    {
        pass = (vp_stack_p)vpsGet(&trans->passes,k);
        flags = u8sGet(&trans->pass_flags,k);
        if(!(flags & IPL_FLAG_ITERATE))
        {
            NSL_EXECUTE(pass,vars);
            continue;
        }
        for(i = top;i <= bottom;i++)
        {
            vars[ROW_OFFS] = (vec_t)i;
            for(j = left;j <= right;j++)
            {
                vars[COL_OFFS] = (vec_t)j;
                vars[CELL1_OFFS] = m1->cell[i][j];
                vars[CELL2_OFFS] = m2->cell[i][j];
                NSL_EXECUTE(pass,vars);
                m1->cell[i][j] = vars[CELL1_OFFS];
                m2->cell[i][j] = vars[CELL2_OFFS];
            }
        }
    }
}

/*
 * Cartesian transforms of RVMFT
 */

V_LOCAL vboolean V_CALL
ipl_cart_trans_callback(void_p arg,u32_t row,u32_t col,
    vboolean neg_freq,vec_p reim)
{
    ipl_transform_p trans = (ipl_transform_p)arg;
    vec_p vars = trans->var_pool;
    vars[ROW_OFFS] = (vec_t)row;
    vars[COL_OFFS] = (vec_t)col;
    vars[NEG_FREQ_OFFS] = neg_freq ? (vec_t)1 : (vec_t)0;
    vars[RE_OFFS] = reim[0];
    vars[IM_OFFS] = reim[1];
    NSL_EXECUTE(trans->current_pass,vars);
    reim[0] = vars[RE_OFFS];
    reim[1] = vars[IM_OFFS];
    return true;
}

V_LOCAL void
iplCartesianTransform(matrix_p rvmft,
    u32_t left,u32_t top,u32_t right,u32_t bottom,
    ipl_transform_p trans)
{
    u8_t flags;
    u32_t i, num_passes;
    vec_p vars = trans->var_pool;
    V_ASSERT(trans->type == IPL_CARTESIAN_TRANSFORM);
    vars[MAX_ROW_OFFS] = (vec_t)(rvmft->rows >> 1);
    vars[MAX_COL_OFFS] = (vec_t)(rvmft->cols >> 1);
    num_passes = vpsCount(&trans->passes);
    for(i = 0;i < num_passes;i++)
    {
        trans->current_pass = (vp_stack_p)vpsGet(&trans->passes,i);
        flags = u8sGet(&trans->pass_flags,i);
        if(!(flags & IPL_FLAG_ITERATE))
        {
            NSL_EXECUTE(trans->current_pass,vars);
            continue;
        }
        matRVMFTWalkCartesian(rvmft,left,top,right,bottom,
            ipl_cart_trans_callback,trans);
    }
}

V_LOCAL vboolean V_CALL
ipl_cart_pair_trans_callback(void_p arg,u32_t row,u32_t col,
    vboolean neg_freq,vec_p reim1,vec_p reim2)
{
    ipl_transform_p trans = (ipl_transform_p)arg;
    vec_p vars = trans->var_pool;
    vars[ROW_OFFS] = (vec_t)row;
    vars[COL_OFFS] = (vec_t)col;
    vars[NEG_FREQ_OFFS] = neg_freq ? (vec_t)1 : (vec_t)0;
    vars[RE1_OFFS] = reim1[0];
    vars[IM1_OFFS] = reim1[1];
    vars[RE2_OFFS] = reim2[0];
    vars[IM2_OFFS] = reim2[1];
    NSL_EXECUTE(trans->current_pass,vars);
    reim1[0] = vars[RE1_OFFS];
    reim1[1] = vars[IM1_OFFS];
    reim2[0] = vars[RE2_OFFS];
    reim2[1] = vars[IM2_OFFS];
    return true;
}

V_LOCAL void
iplCartesianPairTransform(matrix_p rvmft1,matrix_p rvmft2,
    u32_t left,u32_t top,u32_t right,u32_t bottom,
    ipl_transform_p trans)
{
    u8_t flags;
    u32_t i, num_passes;
    vec_p vars = trans->var_pool;
    V_ASSERT(trans->type == IPL_CARTESIAN_PAIR_TRANSFORM);
    V_ASSERT(rvmft1->rows == rvmft2->rows);
    V_ASSERT(rvmft1->cols == rvmft2->cols);
    vars[MAX_ROW_OFFS] = (vec_t)(rvmft1->rows >> 1);
    vars[MAX_COL_OFFS] = (vec_t)(rvmft1->cols >> 1);
    num_passes = vpsCount(&trans->passes);
    for(i = 0;i < num_passes;i++)
    {
        trans->current_pass = (vp_stack_p)vpsGet(&trans->passes,i);
        flags = u8sGet(&trans->pass_flags,i);
        if(!(flags & IPL_FLAG_ITERATE))
        {
            NSL_EXECUTE(trans->current_pass,vars);
            continue;
        }
        matRVMFTWalkCartesianPair(rvmft1,rvmft2,left,top,right,bottom,
            ipl_cart_pair_trans_callback,trans);
    }
}

/*
 * Polar transforms of RVMFT
 */

V_LOCAL vboolean V_CALL
ipl_polar_trans_callback(void_p arg,u32_t row,u32_t col,
    vboolean neg_freq,vec_p magphase)
{
    ipl_transform_p trans = (ipl_transform_p)arg;
    vec_p vars = trans->var_pool;
    vars[ROW_OFFS] = (vec_t)row;
    vars[COL_OFFS] = (vec_t)col;
    vars[NEG_FREQ_OFFS] = neg_freq ? (vec_t)1 : (vec_t)0;
    vars[MAG_OFFS] = magphase[0];
    vars[PHASE_OFFS] = magphase[1];
    NSL_EXECUTE(trans->current_pass,vars);
    magphase[0] = vars[MAG_OFFS];
    magphase[1] = vars[PHASE_OFFS];
    return true;
}

V_LOCAL void
iplPolarTransform(matrix_p rvmft,
    u32_t left,u32_t top,u32_t right,u32_t bottom,
    ipl_transform_p trans)
{
    u8_t flags;
    u32_t i, num_passes;
    vec_p vars = trans->var_pool;
    V_ASSERT(trans->type == IPL_POLAR_TRANSFORM);
    vars[MAX_ROW_OFFS] = (vec_t)(rvmft->rows >> 1);
    vars[MAX_COL_OFFS] = (vec_t)(rvmft->cols >> 1);
    num_passes = vpsCount(&trans->passes);
    for(i = 0;i < num_passes;i++)
    {
        trans->current_pass = (vp_stack_p)vpsGet(&trans->passes,i);
        flags = u8sGet(&trans->pass_flags,i);
        if(!(flags & IPL_FLAG_ITERATE))
        {
            NSL_EXECUTE(trans->current_pass,vars);
            continue;
        }
        matRVMFTWalkPolar(rvmft,left,top,right,bottom,
            ipl_polar_trans_callback,trans);
    }
}

V_LOCAL vboolean V_CALL
ipl_polar_pair_trans_callback(void_p arg,u32_t row,u32_t col,
    vboolean neg_freq,vec_p magphase1,vec_p magphase2)
{
    ipl_transform_p trans = (ipl_transform_p)arg;
    vec_p vars = trans->var_pool;
    vars[ROW_OFFS] = (vec_t)row;
    vars[COL_OFFS] = (vec_t)col;
    vars[NEG_FREQ_OFFS] = neg_freq ? (vec_t)1 : (vec_t)0;
    vars[MAG1_OFFS] = magphase1[0];
    vars[PHASE1_OFFS] = magphase1[1];
    vars[MAG2_OFFS] = magphase2[0];
    vars[PHASE2_OFFS] = magphase2[1];
    NSL_EXECUTE(trans->current_pass,vars);
    magphase1[0] = vars[MAG1_OFFS];
    magphase1[1] = vars[PHASE1_OFFS];
    magphase2[0] = vars[MAG2_OFFS];
    magphase2[1] = vars[PHASE2_OFFS];
    return true;
}

V_LOCAL void
iplPolarPairTransform(matrix_p rvmft1,matrix_p rvmft2,
    u32_t left,u32_t top,u32_t right,u32_t bottom,
    ipl_transform_p trans)
{
    u8_t flags;
    u32_t i, num_passes;
    vec_p vars = trans->var_pool;
    V_ASSERT(trans->type == IPL_POLAR_PAIR_TRANSFORM);
    V_ASSERT(rvmft1->rows == rvmft2->rows);
    V_ASSERT(rvmft1->cols == rvmft2->cols);
    vars[MAX_ROW_OFFS] = (vec_t)(rvmft1->rows >> 1);
    vars[MAX_COL_OFFS] = (vec_t)(rvmft1->cols >> 1);
    num_passes = vpsCount(&trans->passes);
    for(i = 0;i < num_passes;i++)
    {
        trans->current_pass = (vp_stack_p)vpsGet(&trans->passes,i);
        flags = u8sGet(&trans->pass_flags,i);
        if(!(flags & IPL_FLAG_ITERATE))
        {
            NSL_EXECUTE(trans->current_pass,vars);
            continue;
        }
        matRVMFTWalkPolarPair(rvmft1,rvmft2,left,top,right,bottom,
            ipl_polar_pair_trans_callback,trans);
    }
}

/*
 * List of all known types of transforms
 */
typedef struct ipl_transform_parse_entry_s
{
    char *keyword;
    u32_t type;
    nsl_var_entry_p vars;
    char *desc;
}   ipl_transform_parse_entry_t, *ipl_transform_parse_entry_p;

V_LOCAL ipl_transform_parse_entry_t ipl_known_transforms[] = 
{
    {
        IPL_VECTOR_TRANSFORM_KEY,
        IPL_VECTOR_TRANSFORM,
        ipl_vector_vars,
        IPL_VECTOR_TRANSFORM_DESC
    },

    {
        IPL_VECTOR_PAIR_TRANSFORM_KEY,
        IPL_VECTOR_PAIR_TRANSFORM,
        ipl_vector_pair_vars,
        IPL_VECTOR_PAIR_TRANSFORM_DESC
    },

    {
        IPL_MATRIX_TRANSFORM_KEY,
        IPL_MATRIX_TRANSFORM,
        ipl_matrix_vars,
        IPL_MATRIX_TRANSFORM_DESC
    },

    {
        IPL_MATRIX_PAIR_TRANSFORM_KEY,
        IPL_MATRIX_PAIR_TRANSFORM,
        ipl_matrix_pair_vars,
        IPL_MATRIX_PAIR_TRANSFORM_DESC
    },

    {
        IPL_CARTESIAN_TRANSFORM_KEY,
        IPL_CARTESIAN_TRANSFORM,
        ipl_cartesian_vars,
        IPL_CARTESIAN_TRANSFORM_DESC
    },

    {
        IPL_CARTESIAN_PAIR_TRANSFORM_KEY,
        IPL_CARTESIAN_PAIR_TRANSFORM,
        ipl_cartesian_pair_vars,
        IPL_CARTESIAN_PAIR_TRANSFORM_DESC
    },

    {
        IPL_POLAR_TRANSFORM_KEY,
        IPL_POLAR_TRANSFORM,
        ipl_polar_vars,
        IPL_POLAR_TRANSFORM_DESC
    },

    {
        IPL_POLAR_PAIR_TRANSFORM_KEY,
        IPL_POLAR_PAIR_TRANSFORM,
        ipl_polar_pair_vars,
        IPL_POLAR_PAIR_TRANSFORM_DESC
    },

    {NULL,0,NULL,NULL}
};

/*
 * To make the code reentrant, pass this as a 1st argument to
 * all three global functions.
 */
V_LOCAL vp_stack_t transform_cache = TYPED_STACK_EMPTY_INITIALIZER;

void
iplFreeTransforms(void)
{
    ipl_transform_p ptr;
    while(vpsPop(&transform_cache,(void_p*)&ptr))
        iplFreeTransform(ptr);
    vpsClear(&transform_cache);
}


void
iplListTransformTypes(void)
{
    ipl_transform_parse_entry_p head = ipl_known_transforms;
    for(;head->keyword;head++)
    {
        conPrintf("\t\"%s\": %s%c\n",head->keyword,
            head->desc,(head + 1)->keyword ? ';' : '.');
    }
}

V_LOCAL void
iplListTransformVariables(nsl_var_entry_p head)
{
    for(;head->name;head++)
        conPrintf("\t\"%s\": %s%c\n",head->name,head->desc,
            (head + 1)->name ? ';' : '.');
}

void
iplListBuiltinVariables()
{
    ipl_transform_parse_entry_p head = ipl_known_transforms;
    for(;head->keyword;head++)
    {
        conPrintf("\n\t\"%s\":\n",head->keyword);
        iplListTransformVariables(head->vars);
    }
}

void
iplListBuiltinConstants()
{
    nsl_const_entry_p const_head = ipl_builtin_consts;
    for(;const_head->name;const_head++)
        conPrintf("\t\"%s\": %s%c\n",const_head->name,const_head->desc,
            (const_head + 1)->name ? ';' : '.');
}

vboolean
iplFindTransform(u32_t type,char *name,vhandle_t lexer,
    u32_t *index_ret,u32_t *nargs_ret)
{
    CALLER_NAME(iplFindTransform)
    u32_t i, num_trans;
    ipl_transform_p trans;
    ipl_transform_parse_entry_p ptr = ipl_known_transforms;
    /* try to find in cache */
    num_trans = vpsCount(&transform_cache);
    for(i = 0;i < num_trans;i++)
    {
        trans = (ipl_transform_p)vpsGet(&transform_cache,i);
        if(type == trans->type && STR_EQ(name,trans->name))
        {
            *index_ret = i;
            if(nargs_ret)
                *nargs_ret = trans->num_args;
            return true;
        }
    }
    /* not found; try to load */
    while(ptr->keyword)
    {
        if(ptr->type == type)
            break;
        ptr++;
    }
    if(!ptr->keyword)
    {
        ERROR_GENERIC(ipl_unknown_transform);
        return false;
    }
    if(!scriptSearchPair(lexer,ptr->keyword,name))
        return false;
    trans = iplLoadTransform(lexer,ptr->vars);
    if(!trans)
        return false;
    trans->type = type;
    *index_ret = (s32_t)vpsCount(&transform_cache);
    if(nargs_ret)
        *nargs_ret = trans->num_args;
    vpsPush(&transform_cache,trans);
    lexPop(lexer,LEX_POSITION_BIT);
    return true;
}

vboolean
iplExecuteTransform(u32_t index, /* as returned by iplFindTransform() */
    u32_t left,
    u32_t top, /* ignored for vectors; better to set to 0 */
    u32_t right,
    u32_t bottom, /* ignored for vectors; better to set to 0 */
    vector_p v1, vector_p v2, /* ignored for matrix transforms */
    matrix_p m1, matrix_p m2, /* ignored for vector transforms */
    vec_p args)
{
    CALLER_NAME(iplExecuteTransform)
    u32_t i;
    ipl_transform_p trans;
    if(index >= vpsCount(&transform_cache))
    {
        ERROR_GENERIC(ipl_no_such_transform);
        return false;
    }
    trans = (ipl_transform_p)vpsGet(&transform_cache,index);
    if(trans->num_args && !args)
    {
        ERROR_GENERIC(ipl_unable_copy_args);
        return false;
    }
    for(i = 0;i < trans->num_args;i++)
        trans->argp[i] = args[i];
    switch(trans->type)
    {
        case IPL_VECTOR_TRANSFORM:
            if(!v1)
            {
                ERROR_GENERIC(error_incorrect_parameters);
                return false;
            }
            iplVectorTransform(v1,left,right,trans);
            return true;
        case IPL_MATRIX_TRANSFORM:
            if(!m1)
            {
                ERROR_GENERIC(error_incorrect_parameters);
                return false;
            }
            iplMatrixTransform(m1,left,top,right,bottom,trans);
            return true;
        case IPL_VECTOR_PAIR_TRANSFORM:
            if(!v1 || !v2)
            {
                ERROR_GENERIC(error_incorrect_parameters);
                return false;
            }
            iplVectorPairTransform(v1,v2,left,right,trans);
            return true;
        case IPL_MATRIX_PAIR_TRANSFORM:
            if(!m1 || !m2)
            {
                ERROR_GENERIC(error_incorrect_parameters);
                return false;
            }
            iplMatrixPairTransform(m1,m2,left,top,right,bottom,trans);
            return true;
        case IPL_CARTESIAN_TRANSFORM:
            if(!m1)
            {
                ERROR_GENERIC(error_incorrect_parameters);
                return false;
            }
            iplCartesianTransform(m1,left,top,right,bottom,trans);
            return true;
        case IPL_POLAR_TRANSFORM:
            if(!m1)
            {
                ERROR_GENERIC(error_incorrect_parameters);
                return false;
            }
            iplPolarTransform(m1,left,top,right,bottom,trans);
            return true;
        case IPL_CARTESIAN_PAIR_TRANSFORM:
            if(!m1 || !m2 || m1->rows != m2->rows || m1->cols != m2->cols)
            {
                ERROR_GENERIC(error_incorrect_parameters);
                return false;
            }
            iplCartesianPairTransform(m1,m2,left,top,right,bottom,trans);
            return true;
        case IPL_POLAR_PAIR_TRANSFORM:
            if(!m1 || !m2 || m1->rows != m2->rows || m1->cols != m2->cols)
            {
                ERROR_GENERIC(error_incorrect_parameters);
                return false;
            }
            iplPolarPairTransform(m1,m2,left,top,right,bottom,trans);
            return true;
        default:
            break;
    }
    return false;
}
