#include <v_lib.h>
#include "eval.h"
#include "iplmath.h"
#include "cspace.h"
#include "messages.h"

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

#define	ASSERT_PARANOID	V_ASSERT

#define	MAT_SAME_SIZE(m1,m2) \
	((m1)->rows == (m2)->rows && (m1)->cols == (m2)->cols)

#define	VEC_SAME_SIZE(v1,v2) \
	((v1)->size == (v2)->size)

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

vp_stack_t	cached_args = TYPED_STACK_EMPTY_INITIALIZER;
vp_stack_t	cached_operands = TYPED_STACK_EMPTY_INITIALIZER;

void
evalFreeAllOperands(void)
{
	void_p op;
	while(vpsPop(&cached_operands,&op))
		if(op)
			memFree(op);
	vpsClear(&cached_operands);
}

V_LOCAL matrix_p
matCacheAlloc(u32_t rows, u32_t cols)
{
	matrix_p m = matAlloc(rows,cols);
	if(m)
		vpsPush(&cached_operands,m);
	return m;
}

V_LOCAL vector_p
vecCacheAlloc(u32_t size)
{
	vector_p v = vecAlloc(size);
	if(v)
		vpsPush(&cached_operands,v);
	return v;
}

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

arg_p
evalAllocArg(char *name,int type)
{
	CALLER_NAME(evalAllocArg)
	arg_p arg;
	if(evalSearchForName(name))
	{
		ERROR_GENERIC_2ARG(ipl_duplicate_definition,name);
		return NULL;
	}
	arg = (arg_p)memAllocZ(sizeof(arg_t));
	V_ASSERT(arg);
	stateDefault(&(arg->state));
    arg->id = strDup(name);
    arg->type = type;
	arg->evaluated = false;
	vpsPush(&cached_args,arg);
	return arg;
}

void
evalFreeArg(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;
			memFree(arg->id);
			memFree(arg);
			break;
		}
	}
}

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])
		{
			if(args[i]->id)
				memFree(args[i]->id);
			memFree(args[i]);
		}
	}
	vpsClear(&cached_args);
}

/*
 * The evaluation process uses implicit recursion, so
 * we need prototypes of corresponding parsing routines
 */

vboolean scriptEvalMatrix(arg_p arg,vhandle_t script);
vboolean scriptEvalVector(arg_p arg,vhandle_t script);

matrix_p
evalMatrix(char *name,vhandle_t script)
{
	CALLER_NAME(evalMatrix)
	arg_p arg;
	arg = evalSearchForName(name);
	if(arg)
	{
		if(arg->type != ARG_MATRIX)
		{
			ERROR_GENERIC_2ARG(ipl_name_in_use,name);
			return NULL;
		}
		if(!arg->evaluated)
		{
			ERROR_GENERIC_2ARG(ipl_circular_reference,name);
			return NULL;
		}
		return arg->op.matrix;
	}
    arg = evalAllocArg(name,ARG_MATRIX);
    if(!arg)
    	return NULL;
	lexPush(script,LEX_POSITION_BIT);
    arg->evaluated = scriptEvalMatrix(arg,script);
	lexPop(script,LEX_POSITION_BIT);
    if(!arg->evaluated)
    {
    	evalFreeArg(arg);
    	return NULL;
    }
    return arg->op.matrix;
}

vector_p
evalVector(char *name,vhandle_t script)
{
	CALLER_NAME(evalVector)
	arg_p arg;
	arg = evalSearchForName(name);
	if(arg)
	{
		if(arg->type != ARG_VECTOR)
		{
			ERROR_GENERIC_2ARG(ipl_name_in_use,name);
			return NULL;
		}
		if(!arg->evaluated)
		{
			ERROR_GENERIC_2ARG(ipl_circular_reference,name);
			return NULL;
		}
		return arg->op.vector;
	}
    arg = evalAllocArg(name,ARG_VECTOR);
    if(!arg)
    	return NULL;
    lexPush(script,LEX_POSITION_BIT);
    arg->evaluated = scriptEvalVector(arg,script);
    lexPop(script,LEX_POSITION_BIT);
    if(!arg->evaluated)
    {
    	evalFreeArg(arg);
    	return NULL;
    }
    return arg->op.vector;
}

vboolean
evalColorChannel(vhandle_t script,matrix_p channels[],
	u32_t index,
	int source_space,int assumed_space)
{
	CALLER_NAME(evalColorChannel)
	char *name;
	if(source_space != assumed_space)
	{
		PARSE_ERROR(script,ipl_wrong_colorspace);
		return false;
	}
	if(!lexNext(script))
	{
		SYN_UNEXPECTED_EOF(script);
		return false;
	}
    name = strDup(lexLast(script));
	channels[index] = evalMatrix(name,script);
	memFree(name);
	return (channels[index]) ? true : false;
}

vboolean
evalOutput(char *file_name,vhandle_t script)
{
	CALLER_NAME(evalOutput)
	char *name = NULL;
   	eval_state_t state;
	u32_t source_space = CSPACE_RGB;
	matrix_p channels[4] = {NULL,NULL,NULL,NULL};
	if(!lexNextIs(script,ipl_open_brace))
	{
		SYN_EXPECTED(script,ipl_open_brace);
		return false;
	}
	stateDefault(&state);
	while(lexNext(script))
    {
    	if(name)
    		memFree(name);
    	name = strDup(lexLast(script));
    	if(STR_EQ(name,ipl_close_brace))
        	break;
        else if(STR_EQ(name,ipl_key_state))
        {
			if(!stateChange(&state,script))
				goto err_exit;
        }
		else if(STR_EQ(name,ipl_key_source_space))
        {
        	char *space = lexNext(script);
            V_ASSERT(space);
        	if(STR_EQ(space,ipl_key_rgb))
            	source_space = CSPACE_RGB;
			else if(STR_EQ(space,ipl_key_hsv))
            	source_space = CSPACE_HSV;
			else if(STR_EQ(space,ipl_key_cie))
            	source_space = CSPACE_CIE;
			else
            {
            	PARSE_ERROR(script,ipl_wrong_colorspace);
                goto err_exit;
            }
        }
        else if(STR_EQ(name,ipl_key_red))
        {
        	if(!evalColorChannel(script,channels,0,source_space,CSPACE_RGB))
        		goto err_exit;
        }
        else if(STR_EQ(name,ipl_key_green))
        {
        	if(!evalColorChannel(script,channels,1,source_space,CSPACE_RGB))
        		goto err_exit;
        }
        else if(STR_EQ(name,ipl_key_blue))
        {
        	if(!evalColorChannel(script,channels,2,source_space,CSPACE_RGB))
        		goto err_exit;
        }
        else if(STR_EQ(name,ipl_key_alpha))
        {
        	if(!evalColorChannel(script,channels,3,CSPACE_RGB,CSPACE_RGB))
        		goto err_exit;
        }
        else if(STR_EQ(name,ipl_key_hue))
        {
        	if(!evalColorChannel(script,channels,0,source_space,CSPACE_HSV))
        		goto err_exit;
        }
        else if(STR_EQ(name,ipl_key_saturation))
        {
        	if(!evalColorChannel(script,channels,1,source_space,CSPACE_HSV))
        		goto err_exit;
        }
        else if(STR_EQ(name,ipl_key_value))
        {
        	if(!evalColorChannel(script,channels,2,source_space,CSPACE_HSV))
        		goto err_exit;
        }
        else if(STR_EQ(name,ipl_key_cieX))
        {
        	if(!evalColorChannel(script,channels,0,source_space,CSPACE_CIE))
        		goto err_exit;
        }
        else if(STR_EQ(name,ipl_key_cieY))
        {
        	if(!evalColorChannel(script,channels,1,source_space,CSPACE_CIE))
        		goto err_exit;
        }
        else if(STR_EQ(name,ipl_key_cieZ))
        {
        	if(!evalColorChannel(script,channels,2,source_space,CSPACE_CIE))
        		goto err_exit;
        }
        else
        {
        	PARSE_ERROR(script,ipl_unknown_channel)
       		goto err_exit;
        }
    }
    if(channels[0] && channels[1] && channels[2])
    {
    	if(channels[0]->rows != channels[1]->rows ||
    		channels[0]->rows != channels[2]->rows ||
    		channels[0]->cols != channels[1]->cols ||
    		channels[0]->cols != channels[2]->cols)
    	{
    		PARSE_ERROR(script,ipl_size_mismatch);
    		goto err_exit;
    	}
		outputSaveChannels(file_name,source_space,channels,&state);
		if(name)
			memFree(name);
	    return true;
    }
    PARSE_ERROR(script,ipl_not_enought_channels);
err_exit:;
	if(name)
		memFree(name);
	return false;
}

vboolean
evalAll(vhandle_t script)
{
	CALLER_NAME(evalAll)
	vboolean result = false;
	char *name = NULL;
	while(lexSkipToEx(script,ipl_key_output,STR_COMP))
	{
		if(!lexNext(script))
		{
			SYN_UNEXPECTED_EOF(script);
			goto err_exit;
		}
		if(name)
			memFree(name);
		name = strDup(lexLast(script));
		if(!evalOutput(name,script))
			goto err_exit;
	}
	result = true;
err_exit:;
	if(name)
		memFree(name);
	return result;
}

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

arg_p
evalSearchForFuncArg(vhandle_t script,int type)
{
	CALLER_NAME(evalSearchForFuncArg)
	arg_p arg;
	char *ref;
	if(!lexNext(script))
	{
		SYN_UNEXPECTED_EOF(script);
		return NULL;
	}
	ref = strDup(lexLast(script));
	arg = evalSearchForName(ref);
	if(!arg)
	{
		/* evaluate */
		switch(type)
		{
			case ARG_MATRIX:
				evalMatrix(ref,script);
				arg = evalSearchForName(ref);
				V_ASSERT(!arg || arg->op.matrix);
				break;
			case ARG_VECTOR:
				evalVector(ref,script);
				arg = evalSearchForName(ref);
				V_ASSERT(!arg || arg->op.vector);
				break;
			/*
				FIXME
			case ARG_DECOMPOSITION:
				evalDecomposition(ref,script);
				arg = evalSearchForName(ref);
				break;
			*/
			default:
				assert(0); /* BUG */
				return NULL;
		}
	}
	memFree(ref);
	if(!arg)
		return NULL;
	V_ASSERT(arg->evaluated);
	if(arg->type == type)
		return arg;
	ERROR_GENERIC(ipl_type_mismatch);
	return NULL;
}

V_LOCAL vboolean
uint_width_of(vhandle_t script,u32_t *res)
{
	CALLER_NAME(uint_width_of)
	arg_p arg = evalSearchForFuncArg(script,ARG_MATRIX);
	if(!arg)
		return false;
	if(!arg->op.matrix)
	{
		if(arg->state.custom_width)
		{
			WARNING_GENERIC(ipl_using_state);
			*res = arg->state.custom_width;
			return true;
		}
		ERROR_GENERIC(ipl_null_func_arg);
		return false;
	}
	*res = arg->op.matrix->cols;
	return true;
}

V_LOCAL vboolean
uint_height_of(vhandle_t script,u32_t *res)
{
	CALLER_NAME(uint_height_of)
	arg_p arg = evalSearchForFuncArg(script,ARG_MATRIX);
	if(!arg)
		return false;
	if(!arg->op.matrix)
	{
		if(arg->state.custom_height)
		{
			WARNING_GENERIC(ipl_using_state);
			*res = arg->state.custom_height;
			return true;
		}
		ERROR_GENERIC(ipl_null_func_arg);
		return false;
	}
	*res = arg->op.matrix->rows;
	return true;
}

V_LOCAL vboolean
uint_size_of(vhandle_t script,u32_t *res)
{
	CALLER_NAME(uint_size_of)
	arg_p arg = evalSearchForFuncArg(script,ARG_VECTOR);
	if(!arg)
		return false;
	if(!arg->op.vector)
	{
		if(arg->state.custom_width)
		{
			WARNING_GENERIC(ipl_using_state);
			*res = arg->state.custom_width;
			return true;
		}
		ERROR_GENERIC(ipl_null_func_arg);
		return false;
	}
	*res = arg->op.vector->size;
	return true;
}

typedef vboolean (*real_func)(vhandle_t script,vec_t *res);
typedef vboolean (*int_func)(vhandle_t script,s32_t *res);
typedef vboolean (*uint_func)(vhandle_t script,u32_t *res);

typedef struct
{
	char 		*func_name;
	real_func	entry;
	char 		*func_desc;
}	real_func_field;

typedef struct
{
	char 		*func_name;
	int_func	entry;
	char 		*func_desc;
}	int_func_field;

typedef struct
{
	char 		*func_name;
	uint_func	entry;
	char 		*func_desc;
}	uint_func_field;

V_LOCAL real_func_field real_func_fields[] =
{
	{NULL,NULL,NULL}
};

V_LOCAL int_func_field int_func_fields[] =
{
	{NULL,NULL,NULL}
};

V_LOCAL uint_func_field uint_func_fields[] =
{
	{
		"width_of",
		uint_width_of,
		"returns number of cols in given matrix"
	},
	{
		"height_of",
		uint_height_of,
		"returns number of cols in given matrix"
	},
	{
		"size_of",
		uint_size_of,
		"returns number of elements in given vector"
	},
	{NULL,NULL}
};

V_LOCAL vboolean
evalReal(char *func_name,vhandle_t script,vec_t *res)
{
	CALLER_NAME(evalReal)
	real_func_field *ptr;
	for(ptr = real_func_fields;ptr->func_name;ptr++)
		if(STR_EQ(ptr->func_name,func_name))
		{
			if((*ptr->entry)(script,res))
				return true;
			return false;
		}
	SYN_UNDECLARED(script,func_name);
	return false;
}

V_LOCAL vboolean
evalInt(char *func_name,vhandle_t script,s32_t *res)
{
	CALLER_NAME(evalInt)
	int_func_field *ptr;
	for(ptr = int_func_fields;ptr->func_name;ptr++)
		if(STR_EQ(ptr->func_name,func_name))
		{
			if((*ptr->entry)(script,res))
				return true;
			return false;
		}
	SYN_UNDECLARED(script,func_name);
	return false;
}

V_LOCAL vboolean
evalUInt(char *func_name,vhandle_t script,u32_t *res)
{
	CALLER_NAME(evalUInt)
	uint_func_field *ptr;
	for(ptr = uint_func_fields;ptr->func_name;ptr++)
		if(STR_EQ(ptr->func_name,func_name))
		{
			if((*ptr->entry)(script,res))
				return true;
			return false;
		}
	SYN_UNDECLARED(script,func_name);
	return false;
}

V_LOCAL void
evalHelpRealFuncs(void)
{
	real_func_field *ptr;
	conPuts("Available real functions:\n");
	for(ptr = real_func_fields;ptr->func_name;ptr++)
		conPrintf("\t'%s' - %s\n",ptr->func_name,strSafe(ptr->func_desc));
}

V_LOCAL void
evalHelpIntFuncs(void)
{
	int_func_field *ptr;
	conPuts("Available int functions:\n");
	for(ptr = int_func_fields;ptr->func_name;ptr++)
		conPrintf("\t'%s' - %s\n",ptr->func_name,strSafe(ptr->func_desc));
}

V_LOCAL void
evalHelpUIntFuncs(void)
{
	uint_func_field *ptr;
	conPuts("Available uint functions:\n");
	for(ptr = uint_func_fields;ptr->func_name;ptr++)
		conPrintf("\t'%s' - %s\n",ptr->func_name,strSafe(ptr->func_desc));
}

void
evalHelpFuncs(void)
{
	evalHelpRealFuncs();
	evalHelpIntFuncs();
	evalHelpUIntFuncs();
}

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

vboolean
evalRealFunction(vhandle_t script,vec_t *result)
{
	CALLER_NAME(evalRealFunction)
	if(!lexNextFloat(script,result)) /* check for constant */
	{
		vboolean res;
		char *command;
		if(!lexLast(script))
		{
			SYN_UNEXPECTED_EOF(script);
			return false;
		}
		command = strDup(lexLast(script));
		res = evalReal(command,script,result);
		memFree(command);
		return res;
	}
	return true;
}

vboolean
evalIntFunction(vhandle_t script,s32_t *result)
{
	CALLER_NAME(evalIntFunction)
	if(!lexNextInteger(script,result)) /* check for constant */
	{
		vboolean res;
		char *command;
		if(!lexLast(script))
		{
			SYN_UNEXPECTED_EOF(script);
			return false;
		}
		command = strDup(lexLast(script));
		res = evalInt(command,script,result);
		memFree(command);
		return res;
	}
	return true;
}

vboolean
evalUIntFunction(vhandle_t script,u32_t *result)
{
	CALLER_NAME(evalUIntFunction)
	s32_t temp;
	if(!lexNextInteger(script,&temp)) /* check for constant */
	{
		vboolean res;
		char *command;
		if(!lexLast(script))
		{
			SYN_UNEXPECTED_EOF(script);
			return false;
		}
		command = strDup(lexLast(script));
		res = evalUInt(command,script,result);
		memFree(command);
		return res;
	}
	if(temp < 0)
	{
		ERROR_GENERIC(ipl_range_error);
		return false;
	}
	*result = (u32_t)temp;
	return true;
}

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

V_LOCAL matrix_p
check_state_create_matrix(arg_p arg)
{
	CALLER_NAME(check_state_create_matrix)
	V_ASSERT(arg->type == ARG_MATRIX);
	if(!arg->op.matrix)
	{
		eval_state_p st = &arg->state;
		if(st->custom_width && st->custom_height)
			arg->op.matrix = matCacheAlloc(st->custom_height,st->custom_width);
	}
	ERROR_GENERIC(ipl_state_wh_unset);
	return arg->op.matrix;
}

V_LOCAL vector_p
check_state_create_vector(arg_p arg)
{
	CALLER_NAME(check_state_create_vector)
	V_ASSERT(arg->type == ARG_VECTOR);
	if(!arg->op.vector)
	{
		eval_state_p st = &arg->state;
		if(st->custom_width)
			arg->op.vector = vecCacheAlloc(st->custom_width);
	}
	ERROR_GENERIC(ipl_state_size_unset);
	return arg->op.vector;
}

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

vp_stack_t fourier_transformers = TYPED_STACK_EMPTY_INITIALIZER;

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

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

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

/*
 * Matrix operations
 */

V_LOCAL vboolean
matrix_input_op(arg_p arg,vhandle_t script)
{
	CALLER_NAME(matrix_input_op)
	char img_name[_MAX_PATH];
	V_ASSERT(arg->type == ARG_MATRIX);
	if(lexCopyNextToken(script,img_name,_MAX_PATH))
	{
		arg->op.matrix = inputGetMatrix(&arg->state,img_name,lexNext(script));
		return (arg->op.matrix) ? true : false;
	}
	SYN_UNEXPECTED_EOF(script);
	return false;
}

V_LOCAL vboolean
matrix_save_op(arg_p arg,vhandle_t script)
{
	CALLER_NAME(matrix_save_op)
	u32_t r, c, c_lim;
	vhandle_t outfile;
	matrix_p m;
	char file_name[_MAX_PATH];
	V_ASSERT(arg->type == ARG_MATRIX);
	if(!lexCopyNextToken(script,file_name,_MAX_PATH))
	{
		SYN_UNEXPECTED_EOF(script);
		return false;
	}
	outfile = fileOpenWrite(file_name);
	if(outfile == LIBV_INVALID_HANDLE)
		return false;
	m = check_state_create_matrix(arg);
	if(!m)
	{
		fileClose(outfile);
		return false;
	}
	c_lim = m->cols - 1;
	for(r = 0;r < m->rows;r++)
	{
		for(c = 0;c < c_lim;c++)
			filePrintFloatStr(outfile,dotchar,m->cell[r][c],ipl_tab_string);
		filePrintFloatStr(outfile,dotchar,m->cell[r][c],ipl_eol_string);
	}
	fileClose(outfile);
	return true;
}

V_LOCAL vboolean
matrix_row_linear_op(arg_p arg,vhandle_t script)
{
	matrix_p m;
	vec_t min, max;
	ASSERT_PARANOID(arg->type == ARG_MATRIX);
	if(!evalRealFunction(script,&min) || !evalRealFunction(script,&max))
		return false;
	if((m = check_state_create_matrix(arg)) == NULL)
		return false;
	matLinSpacedRows(m,min,max);
	return true;
}

V_LOCAL vboolean
matrix_col_linear_op(arg_p arg,vhandle_t script)
{
	matrix_p m;
	vec_t min, max;
	ASSERT_PARANOID(arg->type == ARG_MATRIX);
	if(!evalRealFunction(script,&min) || !evalRealFunction(script,&max))
		return false;
	m = check_state_create_matrix(arg);
	if(!m)
		return false;
	matLinSpacedCols(m,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;
	ASSERT_PARANOID(arg->type == ARG_MATRIX);
	m = check_state_create_matrix(arg);
	if(!m)
		return false;
	if(arg->state.frequency_domain)
		WARNING_GENERIC(ipl_already_frequency);
	context = evalFindFourierTransformer(m->rows,m->cols);
	if(context == LIBV_INVALID_HANDLE)
		return false;
	fftRVMForward(context,arg->op.matrix);
	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;
	ASSERT_PARANOID(arg->type == ARG_MATRIX);
	m = check_state_create_matrix(arg);
	if(!m)
		return false;
	if(!arg->state.frequency_domain)
		WARNING_GENERIC(ipl_already_spatial);
	context = evalFindFourierTransformer(m->rows,m->cols);
	if(context == LIBV_INVALID_HANDLE)
		return false;
	fftRVMInverse(context,arg->op.matrix);
	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;
	ASSERT_PARANOID(arg->type == ARG_MATRIX);
	if(!evalUIntFunction(script,&row_min) ||
			!evalUIntFunction(script,&row_max))
		return false;
	if(row_min > row_max)
	{
		PARSE_ERROR(script,ipl_wrong_bounds)
		return false;
	}
	m = check_state_create_matrix(arg);
	if(!m)
		return false;
	if(arg->state.frequency_domain)
	{
		if(!IS_POWER_TWO(m->rows) || !IS_POWER_TWO(m->cols))
		{
			ERROR_GENERIC(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;
	ASSERT_PARANOID(arg->type == ARG_MATRIX);
	if(!evalUIntFunction(script,&col_min) ||
			!evalUIntFunction(script,&col_max))
		return false;
	if(col_min > col_max)
	{
		PARSE_ERROR(script,ipl_wrong_bounds)
		return false;
	}
	m = check_state_create_matrix(arg);
	if(!m)
		return false;
	if(arg->state.frequency_domain)
	{
		if(!IS_POWER_TWO(m->rows) || !IS_POWER_TWO(m->cols))
		{
			ERROR_GENERIC(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_band_pass_op(arg_p arg,vhandle_t script)
{
	CALLER_NAME(matrix_band_pass_op)
	matrix_p m;
	u32_t min, max;
	ASSERT_PARANOID(arg->type == ARG_MATRIX);
	if(!evalUIntFunction(script,&min) ||
			!evalUIntFunction(script,&max))
		return false;
	if(min > max)
	{
		PARSE_ERROR(script,ipl_wrong_bounds)
		return false;
	}
	m = check_state_create_matrix(arg);
	if(!m)
		return false;
	if(arg->state.frequency_domain)
	{
		if(!IS_POWER_TWO(m->rows) || !IS_POWER_TWO(m->cols))
		{
			ERROR_GENERIC(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;
	ASSERT_PARANOID(arg->type == ARG_MATRIX);
	if(!evalUIntFunction(script,&min) ||
			!evalUIntFunction(script,&max))
		return false;
	if(min > max)
	{
		PARSE_ERROR(script,ipl_wrong_bounds)
		return false;
	}
	m = check_state_create_matrix(arg);
	if(!m)
		return false;
	if(arg->state.frequency_domain)
	{
		if(!IS_POWER_TWO(m->rows) || !IS_POWER_TWO(m->cols))
		{
			ERROR_GENERIC(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)
	matrix_p m;
	vec_t scale;
	ASSERT_PARANOID(arg->type == ARG_MATRIX);
	if(!evalRealFunction(script,&scale))
		return false;
	m = check_state_create_matrix(arg);
	if(!m)
		return false;
	if(arg->state.frequency_domain)
	{
		scale_cb_arg_t sa;
		if(!IS_POWER_TWO(m->rows) || !IS_POWER_TWO(m->cols))
		{
			ERROR_GENERIC(ipl_fft_requires_powers2)
			return false;
		}
		sa.re_scale = scale;
		sa.im_scale = scale;
		matRVMFTWalkCartesian(m,0,0,m->cols >> 1,m->rows >> 1,
			rvmft_scale_cb,&sa);
	}
	else
		matScale(m,m,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;
	ASSERT_PARANOID(arg->type == ARG_MATRIX);
	if(!evalRealFunction(script,&offset))
		return false;
	m = check_state_create_matrix(arg);
	if(!m)
		return false;
	if(arg->state.frequency_domain)
	{
		if(!IS_POWER_TWO(m->rows) || !IS_POWER_TWO(m->cols))
		{
			ERROR_GENERIC(ipl_fft_requires_powers2)
			return false;
		}
		matRVMFTWalkPolar(m,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 = evalSearchForFuncArg(script,ARG_MATRIX);
	if(!param)
		return false;
	m = check_state_create_matrix(arg);
	if(!m)
		return false;
	if(!MAT_SAME_SIZE(m,param->op.matrix))
	{
		ERROR_GENERIC(ipl_size_mismatch);
		return false;
	}
	if(arg->state.frequency_domain != param->state.frequency_domain)
	{
		ERROR_GENERIC(ipl_domain_mismatch);
		return false;
	}
	if(arg->state.frequency_domain)
	{
		if(!IS_POWER_TWO(m->rows) || !IS_POWER_TWO(m->cols))
		{
			ERROR_GENERIC(ipl_fft_requires_powers2)
			return false;
		}
		matRVMFTMultiply(m,param->op.matrix);
	}
	else
		matScaleM(m,m,param->op.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 = evalSearchForFuncArg(script,ARG_MATRIX);
	if(!param)
		return false;
	m = check_state_create_matrix(arg);
	if(!m)
		return false;
	if(!MAT_SAME_SIZE(m,param->op.matrix))
	{
		ERROR_GENERIC(ipl_size_mismatch);
		return false;
	}
	if(arg->state.frequency_domain != param->state.frequency_domain)
	{
		ERROR_GENERIC(ipl_domain_mismatch);
		return false;
	}
	if(arg->state.frequency_domain)
	{
		if(!IS_POWER_TWO(m->rows) || !IS_POWER_TWO(m->cols))
		{
			ERROR_GENERIC(ipl_fft_requires_powers2)
			return false;
		}
		matRVMFTDivide(m,param->op.matrix);
	}
	else
		matUnscaleM(m,m,param->op.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 = evalSearchForFuncArg(script,ARG_MATRIX);
	if(!param)
		return false;
	m = check_state_create_matrix(arg);
	if(!m)
		return false;
	if(!MAT_SAME_SIZE(m,param->op.matrix))
	{
		ERROR_GENERIC(ipl_size_mismatch);
		return false;
	}
	if(arg->state.frequency_domain != param->state.frequency_domain)
	{
		ERROR_GENERIC(ipl_domain_mismatch);
		return false;
	}
	if(arg->state.frequency_domain)
	{
		if(!IS_POWER_TWO(m->rows) || !IS_POWER_TWO(m->cols))
		{
			ERROR_GENERIC(ipl_fft_requires_powers2)
			return false;
		}
		matRVMFTWalkPolarPair(m,param->op.matrix,0,0,
			m->cols >> 1,m->rows >> 1,rvmft_phase_add_cb,NULL);
	}
	else
		matAdd(m,m,param->op.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;
	ASSERT_PARANOID(arg->type == ARG_MATRIX);
	if(arg->state.frequency_domain)
	{
		ERROR_GENERIC(ipl_wrong_domain);
		return false;
	}
	if(!evalRealFunction(script,&min) ||
			!evalRealFunction(script,&max))
		return false;
	m = check_state_create_matrix(arg);
	if(!m)
		return false;
	return matAdjustContrast(m,m,min,max);
}

V_LOCAL vboolean
matrix_maximize_contrast_op(arg_p arg,vhandle_t script)
{
	CALLER_NAME(matrix_maximize_contrast_op)
	matrix_p m;
	ASSERT_PARANOID(arg->type == ARG_MATRIX);
	if(arg->state.frequency_domain)
	{
		ERROR_GENERIC(ipl_wrong_domain);
		return false;
	}
	m = check_state_create_matrix(arg);
	if(!m)
		return false;
	return matMaximizeContrast(m,m);
}

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;
	ASSERT_PARANOID(arg->type == ARG_MATRIX);
	if(arg->state.frequency_domain)
	{
		ERROR_GENERIC(ipl_wrong_domain);
		return false;
	}
	param = evalSearchForFuncArg(script,ARG_VECTOR);
	if(!param)
		return false;
	m = check_state_create_matrix(arg);
	if(!m)
		return false;
	matGammaCorrection(m,m,param->op.vector,false);
	return true;
}

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

/*
 * Vector operations
 */

V_LOCAL vboolean
vector_save_op(arg_p arg,vhandle_t script)
{
	CALLER_NAME(vector_save_op)
	u32_t i, lim;
	vhandle_t outfile;
	vector_p v;
	char file_name[_MAX_PATH];
	ASSERT_PARANOID(arg->type == ARG_VECTOR);
	if(!lexCopyNextToken(script,file_name,_MAX_PATH))
	{
		SYN_UNEXPECTED_EOF(script);
		return false;
	}
	outfile = fileOpenWrite(file_name);
	if(outfile == LIBV_INVALID_HANDLE)
		return false;
	v = check_state_create_vector(arg);
	if(!v)
		return false;
	lim = v->size - 1;
	for(i = 0;i < lim;i++)
		filePrintFloatStr(outfile,dotchar,v->elements[i],ipl_tab_string);
	filePrintFloatStr(outfile,dotchar,v->elements[i],ipl_eol_string);
	fileClose(outfile);
	return true;
}

V_LOCAL vboolean
vector_linear_op(arg_p arg,vhandle_t script)
{
	vector_p v;
	vec_t min, max;
	ASSERT_PARANOID(arg->type == ARG_VECTOR);
	if(!evalRealFunction(script,&min) ||
			!evalRealFunction(script,&max))
		return false;
	v = check_state_create_vector(arg);
	if(!v)
		return false;
	vecLinSpaced(v,min,max);
	return true;
}

V_LOCAL vboolean
vector_histogram_op(arg_p arg,vhandle_t script)
{
	vector_p v;
	arg_p param = evalSearchForFuncArg(script,ARG_MATRIX);
	ASSERT_PARANOID(arg->type == ARG_VECTOR);
	if(!param)
		return false;
	v = check_state_create_vector(arg);
	if(!v)
		return false;
	matHistogram(v,param->op.matrix,false,NULL);
	return true;
}

V_LOCAL vboolean
vector_integrate_op(arg_p arg,vhandle_t script)
{
	vector_p v;
	ASSERT_PARANOID(arg->type == ARG_VECTOR);
	v = check_state_create_vector(arg);
	if(!v)
		return false;
	vecRunningSum(v,v);
	return true;
}

V_LOCAL vboolean
vector_maximize_contrast_op(arg_p arg,vhandle_t script)
{
	vector_p v;
	ASSERT_PARANOID(arg->type == ARG_VECTOR);
	v = check_state_create_vector(arg);
	if(!v)
		return false;
	vecMaximizeContrast(v,v);
	return true;
}

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

/*
 * Decomposition operations
 */

/*
TODO
*/

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

/*
 * Operations common for matrices and vectors
 */

vboolean generic_constant_op(arg_p arg,vhandle_t script)
{
	CALLER_NAME(generic_constant_op)
	vec_t val, *ptr;
	u32_t r, c;
	vector_p v;
	matrix_p m;
	if(!evalRealFunction(script,&val))
		return false;
	switch(arg->type)
	{
		case ARG_MATRIX:
			m = check_state_create_matrix(arg);
			if(!m)
				return false;
			for(r = 0;r < m->rows;r++)
			{
				ptr = m->cell[r];
				for(c = 0;c < m->cols;c++)
					ptr[c] = val;
			}
			break;
		case ARG_VECTOR:
			v = check_state_create_vector(arg);
			if(!v)
				return false;
			ptr = v->elements;
			for(c = 0;c < v->size;c++)
				v->elements[c] = val;
			break;
		default:
			ERROR_GENERIC(ipl_invalid_operation);
			return false;
	}
	return true;
}

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(!evalRealFunction(script,&min) ||
			!evalRealFunction(script,&max))
		return false;
	switch(arg->type)
	{
		case ARG_MATRIX:
			m = check_state_create_matrix(arg);
			if(!m)
				return false;
			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:
			v = check_state_create_vector(arg);
			if(!v)
				return false;
			ranNextVectorRange(random_gen,v,min,max);
			break;
		default:
			ERROR_GENERIC(ipl_invalid_operation);
			return false;
	}
	return true;
}

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(!evalRealFunction(script,&mean) ||
			!evalRealFunction(script,&stdev))
		return false;
	switch(arg->type)
	{
		case ARG_MATRIX:
			m = check_state_create_matrix(arg);
			if(!m)
				return false;
			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:
			v = check_state_create_vector(arg);
			if(!v)
				return false;
			ranNextGaussianVector(random_gen,v,mean,stdev);
			break;
		default:
			ERROR_GENERIC(ipl_invalid_operation);
			return false;
	}
	return true;
}

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

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

typedef struct op_parse_entry_s
{
	char 			*name;
	op_routine_t	routine;
	char			*desc;
}	op_parse_entry_t, *op_parse_entry_p;

V_LOCAL op_parse_entry_t matrix_operations[] =
{
	{
		"input",
		matrix_input_op,
		"reads matrix from image file; requires image name and channel name"
	},
	{
		"save",
		matrix_save_op,
		"writes matrix to text file; requires file name"
	},
	{
		"constant",
		generic_constant_op,
		"sets all elements of matrix to given float constant"
	},
	{
		"uniform",
		generic_uniform_op,
		"uniform distribution; requires bounds"
	},
	{
		"gaussian",
		generic_gaussian_op,
		"normal distribution; requires mean and standard deviation"
	},
	{
		"row_linear",
		matrix_row_linear_op,
		"interpolates from given 'min' to given 'max', writes to every row"
	},
	{
		"col_linear",
		matrix_col_linear_op,
		"interpolates from given 'min' to given 'max', writes to every col"
	},
	{
		"fourier_forward",
		matrix_fourier_forward_op,
		"replaces matrix by its 2D forward Fourier transform"
	},
	{
		"fourier_inverse",
		matrix_fourier_inverse_op,
		"inverses Fourier transform"
	},
	{
		"scale",
		matrix_scale_op,
		"multiplies matrix elements by given real 'scale'"
	},
	{
		"offset",
		matrix_offset_op,
		"adds given 'offset' to matrix elements (or performs phase shift)"
	},
	{
		"mat_scale",
		matrix_mat_scale_op,
		"per-element multiplication of two matrices or two Fourier transforms"
	},
	{
		"mat_unscale",
		matrix_mat_unscale_op,
		"per-element division of two matrices or two Fourier transforms"
	},
	{
		"mat_offset",
		matrix_mat_offset_op,
		"per-element addition of two matrices or phase addition of two Fourier transforms"
	},
	{
		"zero_rows",
		matrix_zero_rows_op,
		"clears rows from given 'min' to given 'max' (can operate on Fourier transforms)"
	},
	{
		"zero_cols",
		matrix_zero_cols_op,
		"clears cols from given 'min' to given 'max' (can operate on Fourier transforms)"
	},
	{
		"band_reject",
		matrix_band_reject_op,
		"clears both cols and rows from given 'min' to given 'max' (can operate on Fourier transforms)"
	},
	{
		"band_pass",
		matrix_band_pass_op,
		"cols & rows from given 'min' to given 'max' untouched, other cleared (can operate on Fourier transforms)"
	},
	{
		"adjust_contrast",
		matrix_adjust_contrast_op,
		"linear map from given [min;max] to [0,1]"
	},
	{
		"maximize_contrast",
		matrix_maximize_contrast_op,
		"linear map from [matrix lower;matrix upper] to [0,1]"
	},
	{
		"gamma_correction",
		matrix_gamma_correction_op,
		"curve map from [matrix lower;matrix upper] to [0,1]; uses given vector as curve"
	},
	{NULL,NULL,NULL}
};

V_LOCAL op_parse_entry_t vector_operations[] =
{
	{
		"save",
		vector_save_op,
		"writes vector to text file; requires file name"
	},
	{
		"uniform",
		generic_uniform_op,
		"uniform distribution; requires bounds"
	},
	{
		"gaussian",
		generic_gaussian_op,
		"normal distribution; requires mean and standard deviation"
	},
	{
		"linear",
		vector_linear_op,
		"interpolates from given 'min' to given 'max'"
	},
	{
		"histogram",
		vector_histogram_op,
		"computes histogram of given matrix"
	},
	{
		"integrate",
		vector_integrate_op,
		"computes running sum (discrete integral)"
	},
	{
		"maximize_contrast",
		vector_maximize_contrast_op,
		"linear map from [vector lower;vector upper] to [0,1]"
	},
	{NULL,NULL,NULL}
};

V_LOCAL op_parse_entry_t decomposition_operations[] =
{
	{NULL,NULL,NULL}
};

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

#define	evalFindMatrixOp(name) \
	evalFindRoutine(name,matrix_operations)

#define	evalFindVectorOp(name) \
	evalFindRoutine(name,vector_operations)

#define	evalFindDecompositionOp(name) \
	evalFindRoutine(name,decomposition_operations)

V_LOCAL void
evalListOperations(op_parse_entry_p head)
{
	for(;head->name;head++)
		conPrintf("\t'%s' - %s\n",head->name,strSafe(head->desc));
}

void evalHelp()
{
	conPuts("Available matrix operations:\n");
	evalListOperations(matrix_operations);
	conPuts("Available vector operations:\n");
	evalListOperations(vector_operations);
	conPuts("Available decompositions operations:\n");
	evalListOperations(decomposition_operations);
	evalHelpFuncs();
	conPuts("for list of state variables, pass --help-state\n");
}

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

vboolean
scriptEvalMatrix(arg_p arg,vhandle_t script)
{
	CALLER_NAME(scriptEvalMatrix)
	matrix_p ref_mat;
	op_routine_t op_routine;
	char *token = NULL;
	V_ASSERT(arg->type == ARG_MATRIX);
	if(!scriptSearchPair(script,ipl_key_matrix,arg->id))
		return false;
	if(!lexNextIs(script,ipl_open_brace))
	{
		SYN_EXPECTED(script,ipl_open_brace);
		return false;
	}
	while(lexNext(script))
	{
		if(token)
			memFree(token);
		token = strDup(lexLast(script));
		if(STR_EQ(token,ipl_close_brace))
		{
			memFree(token);
			return (arg->op.matrix) ? true : false;
		} else if(STR_EQ(token,ipl_key_state))
		{
			if(!stateChange(&(arg->state),script))
				break;
		} else if((op_routine = evalFindMatrixOp(token)) != NULL)
		{
			if(!(*op_routine)(arg,script))
				break;
		} else
		{
			/* no commands: assume input */
			if(arg->op.matrix)
			{
				ERROR_GENERIC(ipl_redefinition);
				break;
			}
			ref_mat = evalMatrix(token,script);
			if(!ref_mat)
				break;
			if(arg->state.custom_width || arg->state.custom_height)
			{
				WARNING_GENERIC(ipl_state_invalidated);
			}
			arg->state.custom_width = ref_mat->cols;
			arg->state.custom_height = ref_mat->rows;
			arg->op.matrix = matCacheAlloc(ref_mat->rows,ref_mat->cols);
			matCopy(arg->op.matrix,ref_mat);
		}
	}
	memFree(token);
	return false;
}

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

vboolean
scriptEvalVector(arg_p arg,vhandle_t script)
{
	CALLER_NAME(scriptEvalVector)
	vector_p ref_vec;
	op_routine_t op_routine;
	char *token;
	V_ASSERT(arg->type == ARG_VECTOR);
	if(!scriptSearchPair(script,ipl_key_vector,arg->id))
		return false;
	if(!lexNextIs(script,ipl_open_brace))
	{
		SYN_EXPECTED(script,ipl_open_brace);
		return false;
	}
	while(lexNext(script))
	{
		if(token)
			memFree(token);
		token = strDup(lexLast(script));
		if(STR_EQ(token,ipl_close_brace))
		{
			memFree(token);
			return (arg->op.vector) ? true : false;
		} else if(STR_EQ(token,ipl_key_state))
		{
			if(!stateChange(&(arg->state),script))
				break;
		} else if((op_routine = evalFindVectorOp(token)) != NULL)
		{
			if(!(*op_routine)(arg,script))
				break;
		} else
		{
			/* no commands: assume input */
			if(arg->op.vector)
			{
				ERROR_GENERIC(ipl_redefinition);
				break;
			}
			ref_vec = evalVector(token,script);
			if(!ref_vec)
				break;
			if(arg->state.custom_width)
			{
				WARNING_GENERIC(ipl_state_invalidated);
			}
			arg->state.custom_width = ref_vec->size;
			arg->op.vector = vecCacheAlloc(ref_vec->size);
			vecCopy(arg->op.vector,ref_vec);
		}
	}
	memFree(token);
	return false;
}
