#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;
}
