#include <v_lib.h>
#include "eval.h"
#include "efunc.h"
#include "iplmath.h"
#include "ipltrans.h"
#include "decomp.h"
#include "cspace.h"
#include "messages.h"
#include "istrings.h"

V_LOCAL vp_stack_t cached_args = TYPED_STACK_EMPTY_INITIALIZER;
V_LOCAL vp_stack_t fourier_transformers = TYPED_STACK_EMPTY_INITIALIZER;

V_LOCAL arg_p
evalSearchForName(char *id)
{
    u32_t i, count = vpsCount(&cached_args);
    arg_p *args = (arg_p*)vpsGetStorage(&cached_args);
    for(i=0;i<count;i++)
        if(args[i] && STR_EQ(args[i]->id,id))
            return args[i];
    return NULL;
}

V_LOCAL void
evalFreeArg(arg_p arg)
{
    if(arg->id)
        memFree(arg->id);
    if(arg->data.any)
        arg->destroy(arg->data.any);
    memFree(arg);
}

V_LOCAL void
evalRemoveAndFreeArg(arg_p arg)
{
    u32_t i, count = vpsCount(&cached_args);
    arg_p *args = (arg_p*)vpsGetStorage(&cached_args);
    for(i = 0;i < count;i++)
        if(args[i] == arg)
            args[i] = NULL;
    evalFreeArg(arg);
}

V_LOCAL void
evalFreeAllArgs(void)
{
    u32_t i, count = vpsCount(&cached_args);
    arg_p *args = (arg_p*)vpsGetStorage(&cached_args);
    for(i = 0;i < count;i++)
        if(args[i])
            evalFreeArg(args[i]);
    vpsClear(&cached_args);
}

/* *********************************************************** */

V_LOCAL vhandle_t
evalFindFourierTransformer(u32_t rows,u32_t cols)
{
    CALLER_NAME(evalFindFourierTransformer)
    u32_t i, total, r, c;
    vhandle_t fft_trans, *cache;
    if(!IS_POWER_TWO(rows) || !IS_POWER_TWO(cols))
    {
        ERROR_GENERIC(ipl_fft_requires_powers2)
        return LIBV_INVALID_HANDLE;
    }
    cache = (vhandle_t*)vpsGetStorage(&fourier_transformers);
    total = vpsCount(&fourier_transformers);
    /* search in cache */
    for(i = 0;i < total;i++)
    {
        if(fftRVMGetSize(cache[i],&r,&c) &&
            (r == rows) && (c == cols))
            return cache[i];
    }
    /* nothing appropriate; create */
    fft_trans = fftRVMCreateContext(rows,cols);
    if(fft_trans != LIBV_INVALID_HANDLE)
        vpsPush(&fourier_transformers,fft_trans);
    return fft_trans;
}

V_LOCAL void
evalDestroyAllFourierTransformers()
{
    vhandle_t fft_trans;
    while(vpsPop(&fourier_transformers,&fft_trans))
        if(fft_trans != LIBV_INVALID_HANDLE)
            fftRVMDestroyContext(fft_trans);
    vpsClear(&fourier_transformers);
}

void
evalCollectGarbage(void)
{
    evalFreeAllArgs();
    evalDestroyAllFourierTransformers();
}

/* *********************************************************** */

/*
 * Matrix operations
 */

V_LOCAL vboolean
check_state_create_matrix(arg_p arg)
{
    CALLER_NAME(check_state_create_matrix)
    if(!arg->data.matrix)
    {
        eval_state_p st = &arg->state;
        if(!st->custom_width || !st->custom_height)
        {
            ERROR_GENERIC(ipl_state_wh_unset);
            return false;
        }
        arg->data.matrix = matAlloc(st->custom_height,st->custom_width);
    }
    return arg->data.matrix ? true : false;
}

V_LOCAL vboolean
matrix_copy_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_copy_op)
    arg_p ref_mat;
    if(arg->data.matrix)
    {
        WARNING_GENERIC(ipl_argument_override);
        memFree(arg->data.matrix);
        arg->data.matrix = NULL;
    }
    ref_mat = evalNextArgument(script,ARG_MATRIX);
    if(!ref_mat)
        return false;
    arg->data.matrix = matDuplicate(ref_mat->data.matrix);
    if(arg->data.matrix)
    {
        arg->state.frequency_domain = ref_mat->state.frequency_domain;
        return true;
    }
    return false;
}

V_LOCAL vboolean
matrix_set_rect_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_set_rect_op)
    matrix_p m;
    u32_t left, top, right, bottom;
    vec_t value;
    if(!evalUInt(script,&left) || !evalUInt(script,&top) ||
            !evalUInt(script,&right) || !evalUInt(script,&bottom))
        return false;
    if(!evalReal(script,&value))
        return false;
    if(!arg->data.matrix)
    {
        arg->data.matrix = matAlloc(bottom + 1,right + 1);
        arg->state.frequency_domain = false;
    }
    if(arg->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_wrong_domain);
        return false;
    }
    m = arg->data.matrix;
    if(right >= m->cols)
    {
        WARNING_GENERIC(ipl_wrong_bounds);
        right = m->cols - 1;
    }
    if(bottom >= m->rows)
    {
        WARNING_GENERIC(ipl_wrong_bounds);
        bottom = m->rows - 1;
    }
    if(left > right || top > bottom)
    {
        ERROR_GENERIC(ipl_wrong_bounds);
        return false;
    }
    if(left == right && top == bottom)
        m->cell[top][left] = value;
    else
        matSetRect(arg->data.matrix,left,top,right,bottom,value);
    return true;
}

V_LOCAL vboolean
matrix_set_elem_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_set_elem_op)
    matrix_p m;
    u32_t col, row;
    vec_t value;
    if(!evalUInt(script,&col) || !evalUInt(script,&row))
        return false;
    if(!evalReal(script,&value))
        return false;
    if(!check_state_create_matrix(arg))
        return false;
    m = arg->data.matrix;
    if(col >= m->cols || row >= m->rows)
    {
        ERROR_GENERIC(ipl_wrong_bounds);
        return false;
    }
    m->cell[row][col] = value;
    return true;
}

V_LOCAL vboolean
matrix_get_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_get_op)
    arg_p ref_mat;
    s32_t d_left, d_top;
    u32_t s_left, s_top, s_right, s_bottom;
    if(!evalInt(script,&d_left) ||
            !evalInt(script,&d_top))
        return false;
    ref_mat = evalNextArgument(script,ARG_MATRIX);
    if(!ref_mat)
        return false;
    if(!evalUInt(script,&s_left) ||
            !evalUInt(script,&s_top) ||
            !evalUInt(script,&s_right) ||
            !evalUInt(script,&s_bottom))
        return false;
    if(!arg->data.matrix)
        if(!check_state_create_matrix(arg))
            return false;
    if(arg->state.frequency_domain || ref_mat->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_wrong_domain);
        return false;
    }
    matCopyElements(arg->data.matrix,ref_mat->data.matrix,
        d_left,d_top,s_left,s_top,s_right,s_bottom);
    return true;
}

V_LOCAL vboolean
matrix_shift_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_shift_op)
    matrix_p shifted;
    s32_t dcols, drows;
    if(!evalInt(script,&dcols) ||
            !evalInt(script,&drows))
        return false;
    if(!arg->data.matrix)
        if(!check_state_create_matrix(arg))
            return false;
    if(arg->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_wrong_domain);
        return false;
    }
    shifted = matAlloc(arg->data.matrix->rows,arg->data.matrix->cols);
    matCopyElements(shifted,arg->data.matrix,
        dcols,drows,0,0,shifted->cols - 1,shifted->rows - 1);
    memFree(arg->data.matrix);
    arg->data.matrix = shifted;
    return true;
}

V_LOCAL vboolean
matrix_mirror_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_mirror_op)
    vboolean mirror_x, mirror_y;
    if(!evalBoolean(script,&mirror_x) ||
            !evalBoolean(script,&mirror_y))
        return false;
    if(!arg->data.matrix)
        if(!check_state_create_matrix(arg))
            return false;
    if(arg->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_wrong_domain);
        return false;
    }
    matMirror(arg->data.matrix,mirror_x,mirror_y);
    return true;
}

V_LOCAL vboolean
matrix_transpose_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_transpose_op)
    u32_t r, c;
    matrix_p m, m2;
    vec_p *cell, *cell2;
    if(!arg->data.matrix)
        if(!check_state_create_matrix(arg))
            return false;
    if(arg->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_wrong_domain);
        return false;
    }
    m = arg->data.matrix;
    m2 = matAlloc(m->cols, m->rows);
    cell = m->cell;
    cell2 = m2->cell;
    for(r = 0;r < m2->rows;r++)
    	for(c = 0;c < m2->cols;c++)
    		cell2[r][c] = cell[c][r];
    memFree(m);
    arg->data.matrix = m2;
    return true;
}

V_LOCAL vboolean
matrix_expand_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_expand_op)
    matrix_p expanded;
    u32_t new_width, new_height;
    if(!evalUInt(script,&new_width) ||
            !evalUInt(script,&new_height))
        return false;
    if(arg->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_wrong_domain);
        return false;
    }
    if(!new_width || !new_height)
    {
        PARSE_ERROR(script,error_incorrect_parameters);
        return false;
    }
    /* the arg->data.matrix _can_ be NULL */
    expanded = matExpand(arg->data.matrix,new_height,new_width,
        arg->state.pad_value);
    if(arg->data.matrix)
        memFree(arg->data.matrix);
    arg->data.matrix = expanded;
    return true;
}

#include "argsused.h"
V_LOCAL vboolean
matrix_expand_powers2_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_expand_powers2_op)
    matrix_p expanded;
    /* the arg->data.matrix can be NULL, but it is meaningless */
    if(!check_state_create_matrix(arg))
        return false;
    if(arg->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_wrong_domain);
        return false;
    }
    expanded = matExpandPowers2(arg->data.matrix,arg->state.pad_value);
    memFree(arg->data.matrix);
    arg->data.matrix = expanded;
    return true;
}

V_LOCAL vboolean
check_state_duplicate_matrix(arg_p arg,matrix_p m)
{
    matrix_p dst;
    vhandle_t resampler;
    u32_t target_rows;
    u32_t target_cols;
    if(!m)
        return false;
    target_rows = m->rows;
    target_cols = m->cols;
    if(arg->state.custom_width)
        target_cols = arg->state.custom_width;
    if(arg->state.custom_height)
        target_rows = arg->state.custom_height;
    if(arg->state.input_square)
    {
        u32_t size;
        size = (target_cols > target_rows) ? target_cols : target_rows;
        target_cols = target_rows = size;
    }
    if(arg->state.input_powers2)
    {
        NOT_LESS_POWER_TWO(target_cols,target_cols,u32_t);
        NOT_LESS_POWER_TWO(target_rows,target_rows,u32_t);
    }
    if(target_rows != m->rows || target_cols != m->cols)
    {
        resampler = matCreateResampleContext(m->rows,m->cols,
            target_rows,target_cols);
        if(resampler == LIBV_INVALID_HANDLE)
            return false;
        dst = matAlloc(target_rows,target_cols);
        matResample(resampler,dst,m);
        matDestroyResampleContext(resampler);
    }
    else
        dst = matDuplicate(m);
    arg->data.matrix = dst;
    return true;
}

V_LOCAL vboolean
matrix_input_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_input_op)
    char img_name[_MAX_PATH];
    if(arg->data.matrix)
    {
        WARNING_GENERIC(ipl_argument_override);
        memFree(arg->data.matrix);
        arg->data.matrix = NULL;
    }
    arg->state.frequency_domain = false;
    if(!lexCopyNextToken(script,img_name,_MAX_PATH))
    {
        SYN_UNEXPECTED_EOF(script);
        return false;
    }
    return check_state_duplicate_matrix(arg,
        cspaceGetMatrix(img_name,lexNext(script)));
}

V_LOCAL vboolean
matrix_save_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_save_op)
    char file_name[_MAX_PATH];
    if(!lexCopyNextToken(script,file_name,_MAX_PATH))
    {
        SYN_UNEXPECTED_EOF(script);
        return false;
    }
    if(!check_state_create_matrix(arg))
        return false;
    return iplSaveMatrix(file_name,arg->data.matrix);
}

V_LOCAL vboolean
matrix_save32_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_save32_op)
    char file_name[_MAX_PATH];
	char ext[_MAX_PATH];
	char dir[_MAX_PATH];
	char name[_MAX_PATH];
    if(!lexCopyNextToken(script,file_name,_MAX_PATH))
    {
        SYN_UNEXPECTED_EOF(script);
        return false;
    }
    if(!check_state_create_matrix(arg))
        return false;
	fsSplitPath(file_name,dir,name,ext);
	if(strcasecmp(ext,ipl_key_tga))
		fsMakePath(file_name,dir,name,ipl_key_tga);
    return tga_save_gray32(file_name,arg->data.matrix);
}

V_LOCAL vboolean
matrix_load_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_load_op)
    matrix_p m;
    vboolean result;
    char file_name[_MAX_PATH];
    if(arg->data.matrix)
    {
        WARNING_GENERIC(ipl_argument_override);
        memFree(arg->data.matrix);
        arg->data.matrix = NULL;
    }
    arg->state.frequency_domain = false;
    if(!lexCopyNextToken(script,file_name,_MAX_PATH))
    {
        SYN_UNEXPECTED_EOF(script);
        return false;
    }
    m = iplLoadMatrix(file_name);
    if(!m)
        return false;
    result = check_state_duplicate_matrix(arg,m);
    memFree(m);
    return result;
}

V_LOCAL vboolean
matrix_load32_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_load_op)
    matrix_p m;
    vboolean result;
    char file_name[_MAX_PATH];
	char ext[_MAX_PATH];
	char dir[_MAX_PATH];
	char name[_MAX_PATH];
    if(arg->data.matrix)
    {
        WARNING_GENERIC(ipl_argument_override);
        memFree(arg->data.matrix);
        arg->data.matrix = NULL;
    }
    arg->state.frequency_domain = false;
    if(!lexCopyNextToken(script,file_name,_MAX_PATH))
    {
        SYN_UNEXPECTED_EOF(script);
        return false;
    }
	fsSplitPath(file_name,dir,name,ext);
	if(strcasecmp(ext,ipl_key_tga))
		fsMakePath(file_name,dir,name,ipl_key_tga);
    m = tga_load_gray32(file_name);
    if(!m)
        return false;
    result = check_state_duplicate_matrix(arg,m);
    memFree(m);
    return result;
}

V_LOCAL vboolean
matrix_resize_op(arg_p arg,vhandle_t script)
{
    matrix_p m;
    vhandle_t resampler;
    u32_t new_width, new_height;
    if(!evalUInt(script,&new_width) ||
            !evalUInt(script,&new_height))
        return false;
    m = matAlloc(new_height,new_width);
    if(!m)
        return false;
    if(!arg->data.matrix)
    {
        matZero(m);
        arg->data.matrix = m;
        return true;
    }
    resampler = matCreateResampleContext(arg->data.matrix->rows,
        arg->data.matrix->cols,new_height,new_width);
    if(resampler == LIBV_INVALID_HANDLE)
        return false;
    matResample(resampler,m,arg->data.matrix);
    matDestroyResampleContext(resampler);
    memFree(arg->data.matrix);
    arg->data.matrix = m;
    return true;
}

V_LOCAL vboolean
matrix_row_linear_op(arg_p arg,vhandle_t script)
{
    vec_t min, max;
    if(!evalReal(script,&min) || !evalReal(script,&max))
        return false;
    if(!check_state_create_matrix(arg))
        return false;
    matLinSpacedRows(arg->data.matrix,min,max);
    return true;
}

V_LOCAL vboolean
matrix_col_linear_op(arg_p arg,vhandle_t script)
{
    vec_t min, max;
    if(!evalReal(script,&min) || !evalReal(script,&max))
        return false;
    if(!check_state_create_matrix(arg))
        return false;
    matLinSpacedCols(arg->data.matrix,min,max);
    return true;
}

#include "argsused.h"
V_LOCAL vboolean
matrix_fourier_forward_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_fourier_forward_op)
    matrix_p m;
    vhandle_t context;
    if(!check_state_create_matrix(arg))
        return false;
    m = arg->data.matrix;
    if(arg->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_wrong_domain);
        return false;
    }
    context = evalFindFourierTransformer(m->rows,m->cols);
    if(context == LIBV_INVALID_HANDLE)
        return false;
    fftRVMForward(context,m);
    arg->state.frequency_domain = true;
    return true;
}

#include "argsused.h"
V_LOCAL vboolean
matrix_fourier_inverse_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_fourier_inverse_op)
    matrix_p m;
    vhandle_t context;
    if(!check_state_create_matrix(arg))
        return false;
    m = arg->data.matrix;
    if(!arg->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_wrong_domain);
        return false;
    }
    context = evalFindFourierTransformer(m->rows,m->cols);
    if(context == LIBV_INVALID_HANDLE)
        return false;
    fftRVMInverse(context,m);
    arg->state.frequency_domain = false;
    return true;
}

V_LOCAL vboolean
matrix_zero_rows_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_zero_rows_op)
    matrix_p m;
    u32_t row_min, row_max;
    if(!evalUInt(script,&row_min) ||
            !evalUInt(script,&row_max))
        return false;
    if(row_min > row_max)
    {
        PARSE_ERROR(script,ipl_wrong_bounds)
        return false;
    }
    if(!check_state_create_matrix(arg))
        return false;
    m = arg->data.matrix;
    if(arg->state.frequency_domain)
    {
        if(!IS_POWER_TWO(m->rows) || !IS_POWER_TWO(m->cols))
        {
            PARSE_ERROR(script,ipl_fft_requires_powers2)
            return false;
        }
        matRVMFTZeroRows(m,row_min,row_max);
    }
    else
        matZeroRows(m,row_min,row_max);
    return true;
}

V_LOCAL vboolean
matrix_zero_cols_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_zero_cols_op)
    matrix_p m;
    u32_t col_min, col_max;
    if(!evalUInt(script,&col_min) ||
            !evalUInt(script,&col_max))
        return false;
    if(col_min > col_max)
    {
        PARSE_ERROR(script,ipl_wrong_bounds)
        return false;
    }
    if(!check_state_create_matrix(arg))
        return false;
    m = arg->data.matrix;
    if(arg->state.frequency_domain)
    {
        if(!IS_POWER_TWO(m->rows) || !IS_POWER_TWO(m->cols))
        {
            PARSE_ERROR(script,ipl_fft_requires_powers2)
            return false;
        }
        matRVMFTZeroCols(m,col_min,col_max);
    }
    else
        matZeroCols(m,col_min,col_max);
    return true;
}

V_LOCAL vboolean
matrix_convolve3x3_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_convolve3x3_op)
    arg_p filter;
    matrix_p convolved;
    if(!check_state_create_matrix(arg))
        return false;
    if(arg->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_wrong_domain);
        return false;
    }
    filter = evalNextArgument(script,ARG_MATRIX);
    if(!filter)
        return false;
    if(filter->data.matrix->rows != 3 || filter->data.matrix->cols != 3)
    {
        PARSE_ERROR(script,ipl_3x3_size_required);
        return false;
    }
    convolved = matConvolve3x3(arg->data.matrix,
        filter->data.matrix->cell[0],
        filter->data.matrix->cell[1],
        filter->data.matrix->cell[2]);
    memFree(arg->data.matrix);
    arg->data.matrix = convolved;
    return true;
}

V_LOCAL vboolean
matrix_band_pass_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_band_pass_op)
    matrix_p m;
    u32_t min, max;
    if(!evalUInt(script,&min) ||
            !evalUInt(script,&max))
        return false;
    if(min > max)
    {
        PARSE_ERROR(script,ipl_wrong_bounds)
        return false;
    }
    if(!check_state_create_matrix(arg))
        return false;
    m = arg->data.matrix;
    if(arg->state.frequency_domain)
    {
        if(!IS_POWER_TWO(m->rows) || !IS_POWER_TWO(m->cols))
        {
            PARSE_ERROR(script,ipl_fft_requires_powers2)
            return false;
        }
        matRVMFTBandPass(m,min,max);
    }
    else
        matBandPass(m,min,max);
    return true;
}

V_LOCAL vboolean
matrix_band_reject_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_band_reject_op)
    matrix_p m;
    u32_t min, max;
    if(!evalUInt(script,&min) ||
            !evalUInt(script,&max))
        return false;
    if(min > max)
    {
        PARSE_ERROR(script,ipl_wrong_bounds)
        return false;
    }
    if(!check_state_create_matrix(arg))
        return false;
    m = arg->data.matrix;
    if(arg->state.frequency_domain)
    {
        if(!IS_POWER_TWO(m->rows) || !IS_POWER_TWO(m->cols))
        {
            PARSE_ERROR(script,ipl_fft_requires_powers2)
            return false;
        }
        matRVMFTBandReject(m,min,max);
    }
    else
        matBandReject(m,min,max);
    return true;
}

typedef struct scale_cb_arg_s
{
    vec_t re_scale;
    vec_t im_scale;
}   scale_cb_arg_t, *scale_cb_arg_p;

#include "argsused.h"
V_LOCAL vboolean V_CALL
rvmft_scale_cb(void_p arg,u32_t row,u32_t col,vboolean negf,vec_t reim[])
{
    scale_cb_arg_p sa = (scale_cb_arg_p)arg;
    reim[0] *= sa->re_scale;
    reim[1] *= sa->im_scale;
    return true;
}

V_LOCAL vboolean
matrix_scale_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_scale_op)
    vec_t scale;
    matrix_p m;
    if(!evalReal(script,&scale))
        return false;
    if(!check_state_create_matrix(arg))
        return false;
    m = arg->data.matrix;
    if(arg->state.frequency_domain)
    {
        scale_cb_arg_t sa;
        if(!IS_POWER_TWO(m->rows) || !IS_POWER_TWO(m->cols))
        {
            PARSE_ERROR(script,ipl_fft_requires_powers2)
            return false;
        }
        sa.re_scale = scale;
        sa.im_scale = scale;
        matRVMFTWalkCartesian(arg->data.matrix,
            0,0,m->cols >> 1,m->rows >> 1,rvmft_scale_cb,&sa);
    }
    else
        matScale(arg->data.matrix,arg->data.matrix,scale);
    return true;
}

#include "argsused.h"
V_LOCAL vboolean V_CALL
rvmft_phase_shift_cb(void_p arg,u32_t row,u32_t col,vboolean negf,vec_t mph[])
{
    vec_t ofs = *((vec_p)arg);
    mph[1] += ofs;
    return true;
}

V_LOCAL vboolean
matrix_offset_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_offset_op)
    matrix_p m;
    vec_t offset;
    if(!evalReal(script,&offset))
        return false;
    if(!check_state_create_matrix(arg))
        return false;
    m = arg->data.matrix;
    if(arg->state.frequency_domain)
    {
        if(!IS_POWER_TWO(m->rows) || !IS_POWER_TWO(m->cols))
        {
            PARSE_ERROR(script,ipl_fft_requires_powers2)
            return false;
        }
        matRVMFTWalkPolar(arg->data.matrix,0,0,m->cols >> 1,m->rows >> 1,
            rvmft_phase_shift_cb,&offset);
    }
    else
        matOffset(m,offset);
    return true;
}

V_LOCAL vboolean
matrix_mat_scale_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_mat_scale_op)
    matrix_p m;
    arg_p param = evalNextArgument(script,ARG_MATRIX);
    if(!param)
        return false;
    if(!check_state_create_matrix(arg))
        return false;
    m = arg->data.matrix;
    if(!MAT_SAME_SIZE(m,param->data.matrix))
    {
        PARSE_ERROR(script,ipl_size_mismatch);
        return false;
    }
    if(arg->state.frequency_domain != param->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_domain_mismatch);
        return false;
    }
    if(arg->state.frequency_domain)
    {
        if(!IS_POWER_TWO(m->rows) || !IS_POWER_TWO(m->cols))
        {
            PARSE_ERROR(script,ipl_fft_requires_powers2)
            return false;
        }
        matRVMFTMultiply(m,param->data.matrix);
    }
    else
        matScaleM(m,m,param->data.matrix);
    return true;
}

V_LOCAL vboolean
matrix_mat_unscale_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_mat_unscale_op)
    matrix_p m;
    arg_p param = evalNextArgument(script,ARG_MATRIX);
    if(!param)
        return false;
    if(!check_state_create_matrix(arg))
        return false;
    m = arg->data.matrix;
    if(!MAT_SAME_SIZE(m,param->data.matrix))
    {
        PARSE_ERROR(script,ipl_size_mismatch);
        return false;
    }
    if(arg->state.frequency_domain != param->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_domain_mismatch);
        return false;
    }
    if(arg->state.frequency_domain)
    {
        if(!IS_POWER_TWO(m->rows) || !IS_POWER_TWO(m->cols))
        {
            PARSE_ERROR(script,ipl_fft_requires_powers2)
            return false;
        }
        matRVMFTDivide(m,param->data.matrix);
    }
    else
        matUnscaleM(m,m,param->data.matrix);
    return true;
}

V_LOCAL vboolean V_CALL
rvmft_phase_add_cb(void_p arg,u32_t row,u32_t col,vboolean negf,
    vec_t mph1[],vec_t mph2[])
{
    mph1[1] += mph2[1];
    return true;
}

V_LOCAL vboolean
matrix_mat_offset_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_mat_offset_op)
    matrix_p m;
    arg_p param = evalNextArgument(script,ARG_MATRIX);
    if(!param)
        return false;
    if(!check_state_create_matrix(arg))
        return false;
    m = arg->data.matrix;
    if(!MAT_SAME_SIZE(m,param->data.matrix))
    {
        PARSE_ERROR(script,ipl_size_mismatch);
        return false;
    }
    if(arg->state.frequency_domain != param->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_domain_mismatch);
        return false;
    }
    if(arg->state.frequency_domain)
    {
        if(!IS_POWER_TWO(m->rows) || !IS_POWER_TWO(m->cols))
        {
            PARSE_ERROR(script,ipl_fft_requires_powers2)
            return false;
        }
        matRVMFTWalkPolarPair(m,param->data.matrix,0,0,
            m->cols >> 1,m->rows >> 1,rvmft_phase_add_cb,NULL);
    }
    else
        matAdd(m,m,param->data.matrix);
    return true;
}

V_LOCAL vboolean
matrix_adjust_contrast_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_adjust_contrast_op)
    matrix_p m;
    vec_t min, max;
    if(arg->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_wrong_domain);
        return false;
    }
    if(!evalReal(script,&min) ||
            !evalReal(script,&max))
        return false;
    if(!check_state_create_matrix(arg))
        return false;
    m = arg->data.matrix;
    return matAdjustContrast(m,m,min,max);
}

V_LOCAL vboolean
matrix_normalize_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_normalize_op)
    matrix_p m;
    if(arg->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_wrong_domain);
        return false;
    }
    if(!check_state_create_matrix(arg))
        return false;
    m = arg->data.matrix;
    return matNormalize(m,m,0,1);
}

V_LOCAL vboolean
matrix_range_normalize_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_range_normalize_op)
    matrix_p m;
    vec_t min, max;
    if(arg->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_wrong_domain);
        return false;
    }
    if(!check_state_create_matrix(arg))
        return false;
    m = arg->data.matrix;
    if(!evalReal(script,&min) || !evalReal(script,&max))
        return false;
    return matNormalize(m,m,min,max);
}

V_LOCAL vboolean
matrix_gamma_correction_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_gamma_correction_op)
    matrix_p m;
    arg_p param;
    if(arg->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_wrong_domain);
        return false;
    }
    param = evalNextArgument(script,ARG_VECTOR);
    if(!param)
        return false;
    if(!check_state_create_matrix(arg))
        return false;
    m = arg->data.matrix;
    matGammaCorrection(m,m,param->data.vector,false);
    return true;
}

V_LOCAL vboolean
matrix_draw_decomp_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_draw_decomp_op)
    matrix_p m;
    arg_p param;
    bt_decomp_p dec;
    if(arg->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_wrong_domain);
        return false;
    }
    param = evalNextArgument(script,ARG_DECOMPOSITION);
    if(!param)
        return false;
    dec = param->data.decomposition;
    if(!arg->data.matrix)
        arg->data.matrix = matAlloc(dec->source_rows,dec->source_cols);
    m = arg->data.matrix;
    if(dec->source_rows != m->rows || dec->source_cols != m->cols)
    {
        PARSE_ERROR(script,ipl_size_mismatch);
        return false;
    }
    iplDrawDecomposition(m,dec);
    return true;
}

V_LOCAL vboolean
matrix_draw_decomp_scale_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_draw_decomp_op)
    matrix_p m;
    arg_p param, scale;
    bt_decomp_p dec;
    if(arg->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_wrong_domain);
        return false;
    }
    param = evalNextArgument(script,ARG_DECOMPOSITION);
    if(!param)
        return false;
    dec = param->data.decomposition;
    if(!arg->data.matrix)
        arg->data.matrix = matAlloc(dec->source_rows,dec->source_cols);
    m = arg->data.matrix;
    if(dec->source_rows != m->rows || dec->source_cols != m->cols)
    {
        PARSE_ERROR(script,ipl_size_mismatch);
        return false;
    }
    scale = evalNextArgument(script,ARG_VECTOR);
    if(!scale)
        return false;
    if(dec->depth_levels != scale->data.vector->size)
    {
        PARSE_ERROR(script,ipl_size_mismatch);
        return false;
    }
    iplDrawDecompositionScale(m,dec,scale->data.vector);
    return true;
}

V_LOCAL vboolean
matrix_draw_decomp_linear_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_draw_decomp_linear_op)
    matrix_p m;
    arg_p param;
    bt_decomp_p dec;
    vec_t min, max;
    if(arg->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_wrong_domain);
        return false;
    }
    param = evalNextArgument(script,ARG_DECOMPOSITION);
    if(!param)
        return false;
    dec = param->data.decomposition;
    if(!arg->data.matrix)
        arg->data.matrix = matAlloc(dec->source_rows,dec->source_cols);
    m = arg->data.matrix;
    if(dec->source_rows != m->rows || dec->source_cols != m->cols)
    {
        PARSE_ERROR(script,ipl_size_mismatch);
        return false;
    }
    if(!evalReal(script,&min) ||
            !evalReal(script,&max))
        return false;
    iplDrawDecompositionLinearScale(m,dec,min,max);
    return true;
}

V_LOCAL vec_p
evalParseTransformArgs(vhandle_t lexer,u32_t num_reals)
{
    CALLER_NAME(evalParseTransformArgs)
    u32_t i;
    vec_p dest;
    if(!lexNextIs(lexer,ipl_open_bracket))
    {
        SYN_EXPECTED(lexer,ipl_open_bracket);
        return NULL;
    }
    dest = (vec_p)memAlloc(num_reals * sizeof(vec_t));
    num_reals--;
    for(i = 0;i < num_reals;i++)
    {
        if(!evalReal(lexer,dest + i))
            goto err_exit;
        if(!lexNextIs(lexer,ipl_comma))
        {
            SYN_EXPECTED(lexer,ipl_comma);
            goto err_exit;
        }
    }
    if(!evalReal(lexer,dest + i))
        goto err_exit;
    if(!lexNextIs(lexer,ipl_close_bracket))
    {
        SYN_EXPECTED(lexer,ipl_close_bracket);
        goto err_exit;
    }
    return dest;
err_exit:;
    memFree(dest);
    return NULL;
}

V_LOCAL vboolean
evalExecuteMatrixTransform(u32_t trans_type,arg_p arg,vhandle_t script,
    vboolean native_frequency)
{
    CALLER_NAME(evalExecuteMatrixTransform)
    vboolean res;
    u32_t idx, nargs, right, bottom;
    vec_p args;
    if(!iplFindTransform(trans_type,lexNext(script),script,
            &idx,&nargs))
        return false;
    if(!check_state_create_matrix(arg))
        return false;
    if(nargs)
    {
        args = evalParseTransformArgs(script,nargs);
        if(!args)
            return false;
    }
    else
        args = NULL;
    if(arg->state.frequency_domain != native_frequency)
    {
        PARSE_ERROR(script,ipl_wrong_domain);
        if(args)
            memFree(args);
        return false;
    }
    if(native_frequency)
    {
        right = arg->data.matrix->cols >> 1;
        bottom = arg->data.matrix->rows >> 1;
    }
    else
    {
        right = arg->data.matrix->cols - 1;
        bottom = arg->data.matrix->rows - 1;
    }
    res = iplExecuteTransform(idx,0,0,right,bottom,NULL,NULL,
        arg->data.matrix,NULL,args);
    if(args)
        memFree(args);
    return res;
}

V_LOCAL vboolean
evalExecuteMatrixPairTransform(u32_t trans_type,arg_p arg,
    vhandle_t script,vboolean native_frequency)
{
    CALLER_NAME(evalExecuteMatrixPairTransform)
    vboolean res;
    u32_t idx, nargs, right, bottom;
    vec_p args;
    arg_p other;
    if(!iplFindTransform(trans_type,lexNext(script),
            script,&idx,&nargs))
        return false;
    if(!check_state_create_matrix(arg))
        return false;
    if(nargs)
    {
        args = evalParseTransformArgs(script,nargs);
        if(!args)
            return false;
    }
    else
        args = NULL;
    if(arg->state.frequency_domain != native_frequency)
    {
        PARSE_ERROR(script,ipl_wrong_domain);
        if(args)
            memFree(args);
        return false;
    }
    other = evalNextArgument(script,ARG_MATRIX);
    if(!other)
    {
        if(args)
            memFree(args);
        return false;
    }
    if(arg->state.frequency_domain != other->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_domain_mismatch);
        if(args)
            memFree(args);
        return false;
    }
    if(native_frequency &&
        !MAT_SAME_SIZE(arg->data.matrix,other->data.matrix))
    {
        PARSE_ERROR(script,ipl_size_mismatch);
        if(args)
            memFree(args);
        return false;
    }
    if(native_frequency)
    {
        right = arg->data.matrix->cols >> 1;
        bottom = arg->data.matrix->rows >> 1;
    }
    else
    {
        right = arg->data.matrix->cols - 1;
        bottom = arg->data.matrix->rows - 1;
    }
    res = iplExecuteTransform(idx,0,0,right,bottom,NULL,NULL,
        arg->data.matrix,other->data.matrix,args);
    if(args)
        memFree(args);
    return res;
}

V_LOCAL vboolean
matrix_transform_op(arg_p arg,vhandle_t script)
{
    return evalExecuteMatrixTransform(IPL_MATRIX_TRANSFORM,
        arg,script,false);
}

V_LOCAL vboolean
matrix_pair_transform_op(arg_p arg,vhandle_t script)
{
    return evalExecuteMatrixPairTransform(IPL_MATRIX_PAIR_TRANSFORM,
        arg,script,false);
}

V_LOCAL vboolean
cartesian_transform_op(arg_p arg,vhandle_t script)
{
    return evalExecuteMatrixTransform(IPL_CARTESIAN_TRANSFORM,
        arg,script,true);
}

V_LOCAL vboolean
cartesian_pair_transform_op(arg_p arg,vhandle_t script)
{
    return evalExecuteMatrixPairTransform(IPL_CARTESIAN_PAIR_TRANSFORM,
        arg,script,true);
}

V_LOCAL vboolean
polar_transform_op(arg_p arg,vhandle_t script)
{
    return evalExecuteMatrixTransform(IPL_POLAR_TRANSFORM,
        arg,script,true);
}

V_LOCAL vboolean
polar_pair_transform_op(arg_p arg,vhandle_t script)
{
    return evalExecuteMatrixPairTransform(IPL_POLAR_PAIR_TRANSFORM,
        arg,script,true);
}

V_LOCAL vboolean
matrix_pse_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(matrix_pse_op)
    matrix_p pse, src;
    if(!check_state_create_matrix(arg))
        return false;
    src = arg->data.matrix;
    if(!arg->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_domain_mismatch)
    }
    if(!IS_POWER_TWO(src->rows) || !IS_POWER_TWO(src->cols))
    {
        PARSE_ERROR(script,ipl_fft_requires_powers2);
        return false;
    }
    pse = matAlloc(src->rows >> 1,src->cols >> 1);
    matRVMFTSpectrum(pse,src);
    memFree(arg->data.matrix);
    arg->data.matrix = pse;
    arg->state.frequency_domain = false;
    return true;
}

/* *********************************************************** */

/*
 * Vector operations
 */

V_LOCAL vboolean
check_state_create_vector(arg_p arg)
{
    CALLER_NAME(check_state_create_vector)
    if(!arg->data.vector)
    {
        eval_state_p st = &arg->state;
        if(!st->custom_size)
        {
            ERROR_GENERIC(ipl_state_size_unset);
            return false;
        }
        arg->data.vector = vecAlloc(st->custom_size);
    }
    return arg->data.vector ? true : false;
}

V_LOCAL vboolean
vector_copy_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(vector_copy_op)
    arg_p ref_vec;
    if(arg->data.vector)
    {
        WARNING_GENERIC(ipl_argument_override);
        memFree(arg->data.vector);
        arg->data.vector = NULL;
    }
    ref_vec = evalNextArgument(script,ARG_VECTOR);
    if(!ref_vec)
        return false;
    arg->data.vector = vecDuplicate(ref_vec->data.vector);
    return arg->data.vector ? true : false;
}

V_LOCAL vboolean
vector_save_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(vector_save_op)
    char file_name[_MAX_PATH];
    if(!lexCopyNextToken(script,file_name,_MAX_PATH))
    {
        SYN_UNEXPECTED_EOF(script);
        return false;
    }
    if(!check_state_create_vector(arg))
        return false;
    return iplSaveVector(file_name,arg->data.vector);
}

/* do _not_ duplicate it */
V_LOCAL vboolean
check_state_resize_vector(arg_p arg,vector_p v)
{
    u32_t i;
    vector_p ax, vnew, axnew;
    vhandle_t interp;
    if(!v)
        return false;
    if(!arg->state.custom_size || arg->state.custom_size == v->size)
    {
        arg->data.vector = v;
        return true;
    }
    ax = vecAlloc(v->size);
    vecLinSpaced(ax,0,1);
    interp = cspCreate(ax, v);
    vnew = vecAlloc(arg->state.custom_size);
    axnew = vecAlloc(arg->state.custom_size);
    vecLinSpaced(axnew,0,1);
    for(i = 0;i < axnew->size;i++)
        vnew->elements[i] = cspInterpolate(interp,axnew->elements[i]);
    memFree(ax);
    memFree(axnew);
    memFree(v);
    cspDestroy(interp);
    arg->data.vector = vnew;
    return true;
}

V_LOCAL vboolean
vector_load_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(vector_load_op)
    char file_name[_MAX_PATH];
    if(arg->data.vector)
    {
        WARNING_GENERIC(ipl_argument_override);
        memFree(arg->data.vector);
        arg->data.vector = NULL;
    }
    if(!lexCopyNextToken(script,file_name,_MAX_PATH))
    {
        SYN_UNEXPECTED_EOF(script);
        return false;
    }
    return check_state_resize_vector(arg,iplLoadVector(file_name));
}

V_LOCAL vboolean
vector_shift_op(arg_p arg,vhandle_t script)
{
    s32_t amount;
    if(!evalInt(script,&amount))
        return false;
    if(!check_state_create_vector(arg))
        return false;
    return vecShift(arg->data.vector,amount);
}

#include "argsused.h"
V_LOCAL vboolean
vector_mirror_op(arg_p arg,vhandle_t script)
{
    if(!check_state_create_vector(arg))
        return false;
    vecMirror(arg->data.vector);
    return true;
}

V_LOCAL vboolean
vector_linear_op(arg_p arg,vhandle_t script)
{
    vec_t min, max;
    if(!evalReal(script,&min) ||
            !evalReal(script,&max))
        return false;
    if(!check_state_create_vector(arg))
        return false;
    vecLinSpaced(arg->data.vector,min,max);
    return true;
}

V_LOCAL vboolean
vector_histogram_op(arg_p arg,vhandle_t script)
{
    arg_p src = evalNextArgument(script,ARG_MATRIX);
    if(!src)
        return false;
    if(!check_state_create_vector(arg))
        return false;
    matHistogram(arg->data.vector,src->data.matrix,false,NULL);
    return true;
}

V_LOCAL vboolean
vector_range_histogram_op(arg_p arg,vhandle_t script)
{
    vec_t min, max;
    arg_p src = evalNextArgument(script,ARG_MATRIX);
    if(!src)
        return false;
    if(!check_state_create_vector(arg))
        return false;
    if(!evalReal(script,&min) || !evalReal(script,&max))
        return false;
    matHistogramRange(arg->data.vector,src->data.matrix,false,NULL,min,max);
    return true;
}

V_LOCAL vboolean
vector_histogram_with_coords_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(vector_histogram_with_coords_op)
    vector_p v;
    arg_p src, hst_space;
    src = evalNextArgument(script,ARG_MATRIX);
    if(!src)
        return false;
    hst_space = evalNextArgument(script,ARG_VECTOR);
    if(!hst_space)
        return false;
    if(!check_state_create_vector(arg))
        return false;
    v = arg->data.vector;
    if(!VEC_SAME_SIZE(v,hst_space->data.vector))
    {
        PARSE_ERROR(script,ipl_size_mismatch);
        return false;
    }
    matHistogram(v,src->data.matrix,false,hst_space->data.vector);
    return true;
}

V_LOCAL vboolean
vector_range_histogram_with_coords_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(vector_range_histogram_with_coords_op)
    vector_p v;
    vec_t min, max;
    arg_p src, hst_space;
    src = evalNextArgument(script,ARG_MATRIX);
    if(!src)
        return false;
    if(!evalReal(script,&min) || !evalReal(script,&max))
        return false;
    hst_space = evalNextArgument(script,ARG_VECTOR);
    if(!hst_space)
        return false;
    if(!check_state_create_vector(arg))
        return false;
    v = arg->data.vector;
    if(!VEC_SAME_SIZE(v,hst_space->data.vector))
    {
        PARSE_ERROR(script,ipl_size_mismatch);
        return false;
    }
    matHistogramRange(v,src->data.matrix,false,
        hst_space->data.vector,min,max);
    return true;
}

V_LOCAL vboolean
vector_integrate_op(arg_p arg,vhandle_t script)
{
    if(!check_state_create_vector(arg))
        return false;
    vecRunningSum(arg->data.vector,arg->data.vector);
    return true;
}

V_LOCAL vboolean
vector_normalize_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(vector_normalize_op)
    if(arg->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_wrong_domain);
        return false;
    }
    if(!check_state_create_vector(arg))
        return false;
    vecNormalize(arg->data.vector,arg->data.vector,0,1);
    return true;
}

V_LOCAL vboolean
vector_range_normalize_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(vector_range_normalize_op)
    vec_t min, max;
    if(arg->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_wrong_domain);
        return false;
    }
    if(!check_state_create_vector(arg))
        return false;
    if(!evalReal(script,&min) || !evalReal(script,&max))
        return false;
    vecNormalize(arg->data.vector,arg->data.vector,min,max);
    return true;
}

V_LOCAL vboolean
evalExecuteVectorTransform(u32_t trans_type,arg_p arg,vhandle_t script,
    vboolean native_frequency)
{
    CALLER_NAME(evalExecuteVectorTransform)
    vboolean res;
    u32_t idx, nargs, right;
    vec_p args;
    if(!iplFindTransform(trans_type,lexNext(script),script,
            &idx,&nargs))
        return false;
    if(!check_state_create_vector(arg))
        return false;
    if(nargs)
    {
        args = evalParseTransformArgs(script,nargs);
        if(!args)
            return false;
    }
    else
        args = NULL;
    if(arg->state.frequency_domain != native_frequency)
    {
        PARSE_ERROR(script,ipl_wrong_domain);
        if(args)
            memFree(args);
        return false;
    }
    if(native_frequency)
        right = arg->data.vector->size >> 1;
    else
        right = arg->data.vector->size - 1;
    res = iplExecuteTransform(idx,0,0,right,0,arg->data.vector,NULL,
        NULL,NULL,args);
    if(args)
        memFree(args);
    return res;
}

V_LOCAL vboolean
evalExecuteVectorPairTransform(u32_t trans_type,arg_p arg,
    vhandle_t script,vboolean native_frequency)
{
    CALLER_NAME(evalExecuteVectorPairTransform)
    vboolean res;
    u32_t idx, nargs, right;
    vec_p args;
    arg_p other;
    if(!iplFindTransform(trans_type,lexNext(script),
            script,&idx,&nargs))
        return false;
    if(!check_state_create_vector(arg))
        return false;
    if(nargs)
    {
        args = evalParseTransformArgs(script,nargs);
        if(!args)
            return false;
    }
    else
        args = NULL;
    if(arg->state.frequency_domain != native_frequency)
    {
        PARSE_ERROR(script,ipl_wrong_domain);
        if(args)
            memFree(args);
        return false;
    }
    other = evalNextArgument(script,ARG_VECTOR);
    if(!other)
    {
        if(args)
            memFree(args);
        return false;
    }
    if(arg->state.frequency_domain != other->state.frequency_domain)
    {
        PARSE_ERROR(script,ipl_domain_mismatch);
        if(args)
            memFree(args);
        return false;
    }
    if(native_frequency &&
        !VEC_SAME_SIZE(arg->data.vector,other->data.vector))
    {
        PARSE_ERROR(script,ipl_size_mismatch);
        if(args)
            memFree(args);
        return false;
    }
    if(native_frequency)
        right = arg->data.vector->size >> 1;
    else
        right = arg->data.vector->size - 1;
    res = iplExecuteTransform(idx,0,0,right,0,arg->data.vector,
        other->data.vector,NULL,NULL,args);
    if(args)
        memFree(args);
    return res;
}

V_LOCAL vboolean
vector_transform_op(arg_p arg,vhandle_t script)
{
    return evalExecuteVectorTransform(IPL_VECTOR_TRANSFORM,
        arg,script,false);
}

V_LOCAL vboolean
vector_pair_transform_op(arg_p arg,vhandle_t script)
{
    return evalExecuteVectorPairTransform(IPL_VECTOR_PAIR_TRANSFORM,
        arg,script,false);
}

/* *********************************************************** */

/*
 * Decomposition operations
 */

V_LOCAL vboolean
decomp_create_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(decomp_create_op)
    arg_p param;
    vec_t threshold;
    u32_t hst_divs;
    if(arg->data.decomposition)
    {
        WARNING_GENERIC(ipl_argument_override);
        iplFreeDecomposition(arg->data.decomposition);
        arg->data.decomposition = NULL;
    }
    param = evalNextArgument(script,ARG_MATRIX);
    if(!param)
        return false;
    if(!evalReal(script,&threshold) ||
            !evalUInt(script,&hst_divs))
        return false;
    if(hst_divs < 2)
    {
        PARSE_ERROR(script,error_incorrect_parameters);
        return false;
    }
    arg->data.decomposition =
        iplDecomposeImage(param->data.matrix,threshold,hst_divs);
    return arg->data.decomposition ? true : false;
}

V_LOCAL vboolean
decomp_save_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(decomp_save_op)
    vboolean save_values, relative_frequencies;
    char fname[_MAX_PATH];
    if(!arg->data.decomposition)
    {
        PARSE_ERROR(script,ipl_incomplete_definition);
        return false;
    }
    if(!lexCopyNextToken(script,fname,_MAX_PATH))
    {
        SYN_UNEXPECTED_EOF(script);
        return false;
    }
    if(!evalBoolean(script,&save_values) ||
            !evalBoolean(script,&relative_frequencies))
        return false;
    iplSaveDecompositionDescription(fname,arg->data.decomposition,
        save_values,true,relative_frequencies);
    return true;
}

/* *********************************************************** */

/*
 * Operations common for matrices and vectors
 */

V_LOCAL vboolean
generic_puts_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(generic_puts_op)
    if(!lexNext(script))
    {
        SYN_UNEXPECTED_EOF(script);
        return false;
    }
    conPrintf("(%s): %s\n",arg->id,lexLast(script));
    return true;
}

#include "argsused.h"
V_LOCAL vboolean
generic_run_time_op(arg_p arg,vhandle_t script)
{
    conPrintf("(%s): ",arg->id);
    conPrintf(ipl_run_time_fmt,iplRunTime());
    return true;
}

V_LOCAL vboolean
generic_constant_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(generic_constant_op)
    matrix_p m;
    vec_t val, *ptr, *endptr;
    if(!evalReal(script,&val))
        return false;
    switch(arg->type)
    {
        case ARG_MATRIX:
            if(!check_state_create_matrix(arg))
                return false;
            m = arg->data.matrix;
            matSetRect(m,0,0,m->cols - 1,m->rows - 1,val);
            break;
        case ARG_VECTOR:
            if(!check_state_create_vector(arg))
                return false;
            ptr = arg->data.vector->elements;
            endptr = ptr + arg->data.vector->size;
            while(ptr < endptr)
                *ptr++ = val;
            break;
        default:
            PARSE_ERROR(script,ipl_invalid_operation);
            return false;
    }
    return true;
}

V_LOCAL vboolean
generic_uniform_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(generic_uniform_op)
    vec_t min, max;
    u32_t r;
    vector_p v;
    matrix_p m;
    if(!evalReal(script,&min) ||
            !evalReal(script,&max))
        return false;
    switch(arg->type)
    {
        case ARG_MATRIX:
            if(!check_state_create_matrix(arg))
                return false;
            m = arg->data.matrix;
            v = vecAlloc(m->cols);
            for(r = 0;r < m->rows;r++)
            {
                ranNextVectorRange(random_gen,v,min,max);
                memCopy(m->cell[r],v->elements,v->size * sizeof(vec_t));
            }
            memFree(v);
            break;
        case ARG_VECTOR:
            if(!check_state_create_vector(arg))
                return false;
            v = arg->data.vector;
            ranNextVectorRange(random_gen,v,min,max);
            break;
        default:
            PARSE_ERROR(script,ipl_invalid_operation);
            return false;
    }
    return true;
}

V_LOCAL vboolean
generic_gaussian_op(arg_p arg,vhandle_t script)
{
    CALLER_NAME(generic_uniform_op)
    vec_t mean, stdev;
    u32_t r;
    vector_p v;
    matrix_p m;
    if(!evalReal(script,&mean) ||
            !evalReal(script,&stdev))
        return false;
    switch(arg->type)
    {
        case ARG_MATRIX:
            if(!check_state_create_matrix(arg))
                return false;
            m = arg->data.matrix;
            v = vecAlloc(m->cols);
            for(r = 0;r < m->rows;r++)
            {
                ranNextGaussianVector(random_gen,v,mean,stdev);
                memCopy(m->cell[r],v->elements,v->size * sizeof(vec_t));
            }
            memFree(v);
            break;
        case ARG_VECTOR:
            if(!check_state_create_vector(arg))
                return false;
            v = arg->data.vector;
            ranNextGaussianVector(random_gen,v,mean,stdev);
            break;
        default:
            PARSE_ERROR(script,ipl_invalid_operation);
            return false;
    }
    return true;
}

/* *********************************************************** */

typedef vboolean (*op_routine_t)(arg_p,vhandle_t);

typedef struct op_parse_entry_s
{
    char_p          *name_ptr;
    op_routine_t    routine;
    char_p          *desc_ptr;
}   op_parse_entry_t, *op_parse_entry_p;

V_LOCAL op_parse_entry_t matrix_operations[] =
{
    {
        &ipl_key_matrix_copy,
        matrix_copy_op,
        &ipl_desc_matrix_copy,
    },
    {
        &ipl_key_matrix_input,
        matrix_input_op,
        &ipl_desc_matrix_input,
    },
    {
        &ipl_key_matrix_get,
        matrix_get_op,
        &ipl_desc_matrix_get,
    },
    {
        &ipl_key_matrix_set_elem,
        matrix_set_elem_op,
        &ipl_desc_matrix_set_elem,
    },
    {
        &ipl_key_matrix_set_rect,
        matrix_set_rect_op,
        &ipl_desc_matrix_set_rect,
    },
    {
        &ipl_key_matrix_shift,
        matrix_shift_op,
        &ipl_desc_matrix_shift,
    },
    {
        &ipl_key_matrix_mirror,
        matrix_mirror_op,
        &ipl_desc_matrix_mirror,
    },
    {
        &ipl_key_matrix_transpose,
        matrix_transpose_op,
        &ipl_desc_matrix_transpose,
    },
    {
        &ipl_key_matrix_expand,
        matrix_expand_op,
        &ipl_desc_matrix_expand,
    },
    {
        &ipl_key_matrix_expand_powers2,
        matrix_expand_powers2_op,
        &ipl_desc_matrix_expand_powers2,
    },
    {
        &ipl_key_matrix_resize,
        matrix_resize_op,
        &ipl_desc_matrix_resize,
    },
    {
        &ipl_key_matrix_save,
        matrix_save_op,
        &ipl_desc_matrix_save,
    },
    {
        &ipl_key_matrix_save32,
        matrix_save32_op,
        &ipl_desc_matrix_save32,
    },
    {
        &ipl_key_matrix_load,
        matrix_load_op,
        &ipl_desc_matrix_load,
    },
    {
        &ipl_key_matrix_load32,
        matrix_load32_op,
        &ipl_desc_matrix_load32,
    },
    {
        &ipl_key_generic_constant,
        generic_constant_op,
        &ipl_desc_generic_constant,
    },
    {
        &ipl_key_generic_uniform,
        generic_uniform_op,
        &ipl_desc_generic_uniform,
    },
    {
        &ipl_key_generic_gaussian,
        generic_gaussian_op,
        &ipl_desc_generic_gaussian,
    },
    {
        &ipl_key_matrix_row_linear,
        matrix_row_linear_op,
        &ipl_desc_matrix_row_linear,
    },
    {
        &ipl_key_matrix_col_linear,
        matrix_col_linear_op,
        &ipl_desc_matrix_col_linear,
    },
    {
        &ipl_key_matrix_fourier_forward,
        matrix_fourier_forward_op,
        &ipl_desc_matrix_fourier_forward,
    },
    {
        &ipl_key_matrix_fourier_inverse,
        matrix_fourier_inverse_op,
        &ipl_desc_matrix_fourier_inverse,
    },
    {
        &ipl_key_matrix_scale,
        matrix_scale_op,
        &ipl_desc_matrix_scale,
    },
    {
        &ipl_key_matrix_offset,
        matrix_offset_op,
        &ipl_desc_matrix_offset,
    },
    {
        &ipl_key_matrix_mat_scale,
        matrix_mat_scale_op,
        &ipl_desc_matrix_mat_scale,
    },
    {
        &ipl_key_matrix_mat_unscale,
        matrix_mat_unscale_op,
        &ipl_desc_matrix_mat_unscale,
    },
    {
        &ipl_key_matrix_mat_offset,
        matrix_mat_offset_op,
        &ipl_desc_matrix_mat_offset,
    },
    {
        &ipl_key_matrix_zero_rows,
        matrix_zero_rows_op,
        &ipl_desc_matrix_zero_rows,
    },
    {
        &ipl_key_matrix_zero_cols,
        matrix_zero_cols_op,
        &ipl_desc_matrix_zero_cols,
    },
    {
        &ipl_key_matrix_convolve3x3,
        matrix_convolve3x3_op,
        &ipl_desc_matrix_convolve3x3,
    },
    {
        &ipl_key_matrix_band_reject,
        matrix_band_reject_op,
        &ipl_desc_matrix_band_reject,
    },
    {
        &ipl_key_matrix_band_pass,
        matrix_band_pass_op,
        &ipl_desc_matrix_band_pass,
    },
    {
        &ipl_key_matrix_adjust_contrast,
        matrix_adjust_contrast_op,
        &ipl_desc_matrix_adjust_contrast,
    },
    {
        &ipl_key_matrix_normalize,
        matrix_normalize_op,
        &ipl_desc_matrix_normalize,
    },
    {
        &ipl_key_matrix_range_normalize,
        matrix_range_normalize_op,
        &ipl_desc_matrix_range_normalize,
    },
    {
        &ipl_key_matrix_gamma_correction,
        matrix_gamma_correction_op,
        &ipl_desc_matrix_gamma_correction,
    },
    {
        &ipl_key_matrix_draw_decomposition,
        matrix_draw_decomp_op,
        &ipl_desc_matrix_draw_decomposition,
    },
    {
        &ipl_key_matrix_draw_decomposition_scale,
        matrix_draw_decomp_scale_op,
        &ipl_desc_matrix_draw_decomposition_scale,
    },
    {
        &ipl_key_matrix_draw_decomposition_linear,
        matrix_draw_decomp_linear_op,
        &ipl_desc_matrix_draw_decomposition_linear,
    },
    {
        &ipl_key_matrix_matrix_transform,
        matrix_transform_op,
        &ipl_desc_matrix_matrix_transform,
    },
    {
        &ipl_key_matrix_cartesian_transform,
        cartesian_transform_op,
        &ipl_desc_matrix_cartesian_transform,
    },
    {
        &ipl_key_matrix_polar_transform,
        polar_transform_op,
        &ipl_desc_matrix_polar_transform,
    },
    {
        &ipl_key_matrix_matrix_pair_transform,
        matrix_pair_transform_op,
        &ipl_desc_matrix_matrix_pair_transform,
    },
    {
        &ipl_key_matrix_cartesian_pair_transform,
        cartesian_pair_transform_op,
        &ipl_desc_matrix_cartesian_pair_transform,
    },
    {
        &ipl_key_matrix_polar_pair_transform,
        polar_pair_transform_op,
        &ipl_desc_matrix_polar_pair_transform,
    },
    {
        &ipl_key_matrix_spectrum,
        matrix_pse_op,
        &ipl_desc_matrix_spectrum,
    },
    {
        &ipl_key_generic_puts,
        generic_puts_op,
        &ipl_desc_generic_puts,
    },
    {
        &ipl_key_generic_run_time,
        generic_run_time_op,
        &ipl_desc_generic_run_time,
    },
    {NULL,NULL,NULL}
};

V_LOCAL op_parse_entry_t vector_operations[] =
{
    {
        &ipl_key_vector_copy,
        vector_copy_op,
        &ipl_desc_vector_copy,
    },
    {
        &ipl_key_vector_save,
        vector_save_op,
        &ipl_desc_vector_save,
    },
    {
        &ipl_key_vector_load,
        vector_load_op,
        &ipl_desc_vector_load,
    },
    {
        &ipl_key_vector_shift,
        vector_shift_op,
        &ipl_desc_vector_shift,
    },
    {
        &ipl_key_vector_mirror,
        vector_mirror_op,
        &ipl_desc_vector_mirror,
    },
    {
        &ipl_key_generic_uniform,
        generic_uniform_op,
        &ipl_desc_generic_uniform,
    },
    {
        &ipl_key_generic_gaussian,
        generic_gaussian_op,
        &ipl_desc_generic_gaussian,
    },
    {
        &ipl_key_generic_constant,
        generic_constant_op,
        &ipl_desc_generic_constant,
    },
    {
        &ipl_key_vector_linear,
        vector_linear_op,
        &ipl_desc_vector_linear,
    },
    {
        &ipl_key_vector_histogram,
        vector_histogram_op,
        &ipl_desc_vector_histogram,
    },
    {
        &ipl_key_vector_range_histogram,
        vector_range_histogram_op,
        &ipl_desc_vector_range_histogram,
    },
    {
        &ipl_key_vector_histogram_with_coords,
        vector_histogram_with_coords_op,
        &ipl_desc_vector_histogram_with_coords,
    },
    {
        &ipl_key_vector_range_histogram_with_coords,
        vector_range_histogram_with_coords_op,
        &ipl_desc_vector_range_histogram_with_coords,
    },
    {
        &ipl_key_vector_integrate,
        vector_integrate_op,
        &ipl_desc_vector_integrate,
    },
    {
        &ipl_key_vector_normalize,
        vector_normalize_op,
        &ipl_desc_vector_normalize,
    },
    {
        &ipl_key_vector_range_normalize,
        vector_range_normalize_op,
        &ipl_desc_vector_range_normalize,
    },
    {
        &ipl_key_vector_vector_transform,
        vector_transform_op,
        &ipl_desc_vector_vector_transform,
    },
    {
        &ipl_key_vector_vector_pair_transform,
        vector_pair_transform_op,
        &ipl_desc_vector_vector_pair_transform,
    },
    {
        &ipl_key_generic_puts,
        generic_puts_op,
        &ipl_desc_generic_puts,
    },
    {
        &ipl_key_generic_run_time,
        generic_run_time_op,
        &ipl_desc_generic_run_time,
    },
    {NULL,NULL,NULL}
};

V_LOCAL op_parse_entry_t decomposition_operations[] =
{
    {
        &ipl_key_decomposition_decompose,
        decomp_create_op,
        &ipl_desc_decomposition_decompose,
    },
    {
        &ipl_key_decomposition_save,
        decomp_save_op,
        &ipl_desc_decomposition_save,
    },
    {
        &ipl_key_generic_puts,
        generic_puts_op,
        &ipl_desc_generic_puts,
    },
    {
        &ipl_key_generic_run_time,
        generic_run_time_op,
        &ipl_desc_generic_run_time,
    },
    {NULL,NULL,NULL}
};

V_LOCAL op_routine_t
evalFindRoutine(char *name,op_parse_entry_p head)
{
    for(;head->name_ptr;head++)
        if(STR_EQ(*head->name_ptr,name))
            return head->routine;
    return NULL;
}

/* *********************************************************** */

typedef struct toplevel_entry_s
{
    char_p  *key_ptr;
    u32_t   type;
    op_parse_entry_p operations;
    void    (*data_destructor)(void_p any_data);
    char_p  *desc_ptr;
}   toplevel_entry_t, *toplevel_entry_p;

V_LOCAL void
generic_free(void_p ptr)
{
    memFree(ptr);
}

V_LOCAL void
decomp_free(void_p ptr)
{
    iplFreeDecomposition((bt_decomp_p)ptr);
}

V_LOCAL toplevel_entry_t toplevel_tokens[] =
{
    {
        &ipl_key_matrix,
        ARG_MATRIX,
        matrix_operations,
        generic_free,
        &ipl_desc_matrix,
    },
    {
        &ipl_key_vector,
        ARG_VECTOR,
        vector_operations,
        generic_free,
        &ipl_desc_vector,
    },
    {
        &ipl_key_decomposition,
        ARG_DECOMPOSITION,
        decomposition_operations,
        decomp_free,
        &ipl_desc_decomposition,
    },
    {NULL,0,0,NULL,NULL}
};

V_LOCAL toplevel_entry_p
evalToplevelEntryForType(u32_t type)
{
    toplevel_entry_p head = toplevel_tokens;
    for(;head->key_ptr;head++)
        if(head->type == type)
            return head;
    return NULL;
}

V_LOCAL toplevel_entry_p
evalToplevelEntryForKey(char *key)
{
    toplevel_entry_p head = toplevel_tokens;
    for(;head->key_ptr;head++)
        if(STR_EQ(*head->key_ptr,key))
            return head;
    return NULL;
}

V_LOCAL void
evalListOperations(char *prefix,op_parse_entry_p head)
{
    for(;head->name_ptr;head++)
        conPrintf("%s%s%s%c\n\n",prefix,*head->name_ptr,
            *head->desc_ptr,(head + 1)->name_ptr ? ';' : '.');
}

void
evalListClasses()
{
    toplevel_entry_p head = toplevel_tokens;
    for(;head->key_ptr;head++)
    {
        conPrintf("\t\"%s\": %s%c\n",
            *head->key_ptr,
            *head->desc_ptr,
            (head + 1)->key_ptr ? ';' : '.');
    }
}

void
evalListClassOperations()
{
    toplevel_entry_p head = toplevel_tokens;
    for(;head->key_ptr;head++)
    {
        conPrintf(ipl_valid_class_ops_fmt,*head->key_ptr);
        evalListOperations(ipl_tab_string,head->operations);
    }
}

/* *********************************************************** */

arg_p
evalArgument(char *name,u32_t type,vhandle_t script)
{
    CALLER_NAME(evalArgument)
    arg_p arg = NULL;
    op_routine_t op_routine;
    toplevel_entry_p top;
    char *token = NULL;
    arg = evalSearchForName(name);
    if(arg)
    {
        if(arg->type != type)
        {
            PARSE_ERROR(script,ipl_name_in_use);
            return NULL;
        }
        if(!arg->data.any)
        {
            PARSE_ERROR(script,ipl_circular_reference);
            return NULL;
        }
        return arg;
    }
    top = evalToplevelEntryForType(type);
    V_ASSERT(top);
    if(!scriptSearchPair(script,*top->key_ptr,name))
        return NULL;
    arg = (arg_p)memAllocZ(sizeof(arg_t));
    V_ASSERT(arg);
    stateDefault(&(arg->state));
    arg->id = strDup(name);
    arg->type = type;
    arg->destroy = top->data_destructor;
    vpsPush(&cached_args,arg);
    if(!lexNextIs(script,ipl_open_brace))
    {
        SYN_EXPECTED(script,ipl_open_brace);
        goto err_exit;
    }
    for(;;)
    {
        if(!lexNext(script))
        {
            SYN_UNEXPECTED_EOF(script);
            break;
        }
        if(token)
            memFree(token);
        token = strDup(lexLast(script));
        if(STR_EQ(token,ipl_close_brace))
            goto normal_exit;
        if(STR_EQ(token,ipl_key_state))
        {
            if(stateChange(&(arg->state),script))
                continue;
            break;
        }
        op_routine = evalFindRoutine(token,top->operations);
        if(op_routine)
        {
            if((*op_routine)(arg,script))
                continue;
            break;
        }
        PARSE_ERROR_3ARG(script,ipl_unknown,*top->key_ptr,ipl_operation);
        break;
    }
err_exit:;
    evalRemoveAndFreeArg(arg);
    arg = NULL;
normal_exit:;
    if(arg && !arg->data.any)
    {
        PARSE_ERROR(script,ipl_incomplete_definition);
        evalRemoveAndFreeArg(arg);
        arg = NULL;
    }
    if(token)
        memFree(token);
    lexPop(script,LEX_POSITION_BIT);
    return arg;
}

arg_p
evalNextArgument(vhandle_t script,int type)
{
    CALLER_NAME(evalNextArgument)
    arg_p arg;
    if(!lexNext(script))
    {
        SYN_UNEXPECTED_EOF(script);
        return NULL;
    }
    arg = evalArgument(lexLast(script),type,script);
    return arg;
}

/* *********************************************************** */

vboolean
evalGlobals(vhandle_t script)
{
    CALLER_NAME(evalGlobals)
    vboolean res = true;
    while(res && lexSkip_r(script,ipl_key_global))
    {
        if(!lexNextIs(script,ipl_open_brace))
        {
            SYN_EXPECTED(script,ipl_open_brace);
            res = false;
            break;
        }
        while(res && lexNext(script))
        {
            char *last = lexLast(script);
            if(STR_EQ(last,ipl_close_brace))
                break;
            if(STR_EQ(last,ipl_key_random_seed))
            {
                if(!evalUInt(script,&random_seed))
                    res = false;
                continue;
            }
            if(STR_EQ(last,ipl_key_alias))
            {
                char *alias_key;
                if(lexNext(script))
                    alias_key = strDup(lexLast(script));
                else
                    alias_key = NULL;
                if(alias_key && lexNext(script))
                    lexDefineAlias(script,alias_key,lexLast(script));
                else
                {
                    SYN_UNEXPECTED_EOF(script);
                    res = false;
                }
                if(alias_key)
                    memFree(alias_key);
                continue;
            }
            SYN_UNEXPECTED(script,last);
            res = false;
        }
    }
    lexSetInteger(script,LEX_CURRENT_LINE,1);
    return res;
}

/* *********************************************************** */

vboolean
evalTargets(vhandle_t script)
{
    CALLER_NAME(evalTargets)
    char *token = NULL;
    toplevel_entry_p entry;
    while(lexSkip_r(script,ipl_key_targets))
    {
        if(!lexNextIs(script,ipl_open_brace))
        {
            SYN_EXPECTED(script,ipl_open_brace);
            return false;
        }
        while(lexNext(script))
        {
            token = lexLast(script);
            if(STR_EQ(token,ipl_close_brace))
                break;
            if(STR_EQ(token,ipl_key_output))
            {
                if(!cspaceEvalOutput(script))
                    return false;
                continue;
            }
            entry = evalToplevelEntryForKey(token);
            if(!entry)
            {
                PARSE_ERROR(script,ipl_unknown_toplevel_token);
                return false;
            }
            if(!evalNextArgument(script,entry->type))
                return false;
        }
        if(token && STR_EQ(token,ipl_close_brace))
            continue;
        SYN_UNEXPECTED_EOF(script);
        return false;
    }
    return true;
}
