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

#define	ARG_MATRIX	0
#define	ARG_VECTOR	1

#define GENERIC_ERROR(caller,error_desc) \
	conlPrintf(LOG_ERROR,"%s(): %s.\n",caller,error_desc)

#define UNDEF_ERROR(caller) GENERIC_ERROR(caller,"argument undefined");

#define SYNTAX_ERROR(script,caller,error_desc) \
	conlPrintf(LOG_ERROR,"%s(): line %u: '%s': %s.\n",caller, \
		tokLastLine(script),dbs(tokLast(script)),error_desc)
	
void
matZeroRows(matrix_p m,u32_t row_start,u32_t row_end)
{
	u32_t r, row_len;
	V_ASSERT(m);
	if(row_start > row_end || row_start > m->rows)
		return;
	if(row_end > m->rows)
		row_end = m->rows;
	row_len = m->cols * sizeof(vec_t);
	for(r = row_start;r < row_end;r++)
		memZero(m->cell[r],row_len);
}

void
matOffset(matrix_p m,vec_t offset)
{
	u32_t r, c;
	V_ASSERT(m);
	for(r = 0;r < m->rows;r++)
		for(c = 0;c < m->cols;c++)
			m->cell[r][c] += offset;
}

void
matZeroCols(matrix_p m,u32_t col_start,u32_t col_end)
{
	V_ASSERT(m);
	if(m->rows == m->cols)
	{
		matTransposeIP(m);
		matZeroRows(m,col_start,col_end);
		matTransposeIP(m);
	}
	else
	{
		matrix_p transposed = matAlloc(m->cols,m->rows);
		matTranspose(transposed,m);
		matZeroRows(transposed,col_start,col_end);
		matTranspose(m,transposed);
		memFree(transposed);
	}
}

void
matBandReject(matrix_p m,u32_t lo_bound,u32_t hi_bound)
{
	matZeroCols(m,lo_bound,hi_bound);
	matZeroRows(m,lo_bound,hi_bound);
}

void
matBandPass(matrix_p m,u32_t lo_bound,u32_t hi_bound)
{
	if(lo_bound)
		matBandReject(m,0,lo_bound-1);
	if(hi_bound < m->rows-1)
		matZeroRows(m,hi_bound+1,m->rows-1);
	if(hi_bound < m->cols-1)
		matZeroCols(m,hi_bound+1,m->cols-1);
}

void
vecRunningSum(vector_p dest,vector_p src)
{
	u32_t i;
    vec_p d = dest->elements, s = src->elements;
	V_ASSERT(src->size == dest->size);
    d[0] = s[0];
    for(i = 1;i < dest->size;i++)
    	d[i] = s[i] + d[i-1];
}

void
vecLinSpaced(vector_p dest,vec_t min,vec_t max)
{
	u32_t i, n_divs;
    vec_t delta;
    n_divs = dest->size;
    delta = (max - min) / (n_divs - 1);
    dest->elements[0] = min;
    for(i = 1;i < n_divs;i++)
    	dest->elements[i] = dest->elements[i-1] + delta;
}

void
matLinSpacedRows(matrix_p dest,vec_t min,vec_t max)
{
	u32_t i, n_divs;
    vec_t delta;
    n_divs = dest->cols;
    V_ASSERT(n_divs > 1);
    delta = (max - min) / (n_divs - 1);
    dest->cell[0][0] = min;
    for(i = 1;i < dest->cols;i++)
    	dest->cell[0][i] = dest->cell[0][i-1] + delta;
    for(i = 1;i < dest->rows;i++)
    	memCopy(&(dest->cell[i][0]),&(dest->cell[0][0]),n_divs * sizeof(vec_t));
}

void
matLinSpacedCols(matrix_p dest,vec_t min,vec_t max)
{
	u32_t i, j, n_divs;
    vec_t delta;
    n_divs = dest->rows;
    V_ASSERT(n_divs > 1);
    delta = (max - min) / (n_divs - 1);
    dest->cell[0][0] = min;
    for(i = 1;i < dest->rows;i++)
    	dest->cell[i][0] = dest->cell[i-1][0] + delta;
    for(i = 1;i < dest->cols;i++)
    {
    	for(j = 0;j < dest->rows;j++)
    		dest->cell[j][i] = dest->cell[j][0];
    }
}

void
matScaleAdd(matrix_p dest,matrix_p src,vec_t scale,vec_t offset)
{
	u32_t r, c;
	V_ASSERT(dest->rows == src->rows);
	V_ASSERT(dest->cols == src->cols);
	for(r = 0;r < dest->rows;r++)
		for(c = 0;c < dest->cols;c++)
			dest->cell[r][c] = offset + src->cell[r][c] * scale;
}

void
vecScaleAdd(vector_p dest,vector_p src,vec_t scale,vec_t offset)
{
	u32_t c;
	V_ASSERT(dest->size == src->size);
	for(c = 0;c < dest->size;c++)
		dest->elements[c] = offset + src->elements[c] * scale;
}

void
matScaleM(matrix_p dest,matrix_p src,matrix_p scale)
{
	u32_t r, c;
	V_ASSERT(dest->rows == src->rows);
	V_ASSERT(dest->cols == src->cols);
	V_ASSERT(dest->rows == scale->rows);
	V_ASSERT(dest->cols == scale->cols);
	for(r = 0;r < dest->rows;r++)
		for(c = 0;c < dest->cols;c++)
			dest->cell[r][c] = src->cell[r][c] * scale->cell[r][c];
}

void
matOffsetM(matrix_p dest,matrix_p src,matrix_p offset)
{
	u32_t r, c;
	V_ASSERT(dest->rows == src->rows);
	V_ASSERT(dest->cols == src->cols);
	V_ASSERT(dest->rows == offset->rows);
	V_ASSERT(dest->cols == offset->cols);
	for(r = 0;r < dest->rows;r++)
		for(c = 0;c < dest->cols;c++)
			dest->cell[r][c] = src->cell[r][c] + offset->cell[r][c];
}

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

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)
{
	arg_p arg;
	if(evalSearchForName(name))
	{
		conlPrintf(LOG_ERROR,"evalAllocArg(%s): duplicate definition\n",name);
		return NULL;
	}
	arg = (arg_p)memAllocZ(sizeof(arg_t));
	V_ASSERT(arg);
	stateDefault(&(arg->state));
    strCopy(arg->id,name,MAX_ID);
    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);
			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])
			memFree(args[i]);
	vpsClear(&cached_args);
}

matrix_p
evalMatrix(char *name,vhandle_t script)
{
	arg_p arg;
	arg = evalSearchForName(name);
	if(arg)
	{
		if(arg->type != ARG_MATRIX)
		{
			conlPrintf(LOG_ERROR,"evalMatrix(%s): object of another type "
				"already exists.\n",name);
			return NULL;
		}
		if(!arg->evaluated)
		{
			conlPrintf(LOG_ERROR,"evalMatrix(%s): circular reference.\n",
				name);
			return NULL;
		}
		return arg->op.matrix;
	}
    arg = evalAllocArg(name,ARG_MATRIX);
    if(!arg)
    	return NULL;
	tokPush(script);
    arg->evaluated = scriptEvalMatrix(arg,script);
	tokPop(script);
    if(!arg->evaluated)
    {
    	evalFreeArg(arg);
    	return NULL;
    }
    return arg->op.matrix;
}

vector_p
evalVector(char *name,vhandle_t script)
{
	arg_p arg;
	arg = evalSearchForName(name);
	if(arg)
	{
		if(arg->type != ARG_VECTOR)
		{
			conlPrintf(LOG_ERROR,"evalVector(%s): object of another type "
				"already exists.\n",name);
			return NULL;
		}
		if(!arg->evaluated)
		{
			conlPrintf(LOG_ERROR,"evalVector(%s): circular reference.\n",
				name);
			return NULL;
		}
		return arg->op.vector;
	}
    arg = evalAllocArg(name,ARG_VECTOR);
    if(!arg)
    	return NULL;
    tokPush(script);
    arg->evaluated = scriptEvalVector(arg,script);
    tokPop(script);
    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)
{
	char name[MAX_ID] = "";
	if(source_space != assumed_space)
	{
		conlPuts(LOG_ERROR,
        	"evalColorChannel(): color space mismatch\n");
		return false;
	}
	if(!tokCopyNextToken(script,name,MAX_ID))
	{
		conlPuts(LOG_ERROR,
        	"evalColorChannel(): name expected, got EOF\n");
		return false;
	}
	channels[index] = evalMatrix(name,script);
	return (channels[index]) ? true : false;
}

vboolean
evalOutput(char *file_name,vhandle_t script)
{
   	char last[MAX_ID];
	framebuf_p fb = NULL;
	u32_t source_space = CSPACE_RGB;
	matrix_p channels[3] = {NULL,NULL,NULL};
	matrix_p alpha = NULL;
	if(!tokNextIs(script,"{"))
	{
		SYNTAX_ERROR(script,"evalOutput","expected '{'");
		return false;
	}
	while(tokCopyNextToken(script,last,MAX_ID))
    {
    	
    	if(STR_EQ(last,"}"))
        	break;
		else if(STR_EQ(last,"source_space"))
        {
        	char *space = tokNext(script);
            V_ASSERT(space);
        	if(STR_EQ(space,"RGB"))
            	source_space = CSPACE_RGB;
			else if(STR_EQ(space,"HSV"))
            	source_space = CSPACE_HSV;
			else if(STR_EQ(space,"CIE"))
            	source_space = CSPACE_CIE;
			else
            {
            	conlPrintf(LOG_ERROR,
                	"evalOutput(): unknown color space '%s'\n",space);
                return false;
            }
        }
        else if(STR_EQ(last,"red"))
        {
        	if(!evalColorChannel(script,channels,0,source_space,CSPACE_RGB))
        		goto eval_error;
        }
        else if(STR_EQ(last,"green"))
        {
        	if(!evalColorChannel(script,channels,1,source_space,CSPACE_RGB))
        		goto eval_error;
        }
        else if(STR_EQ(last,"blue"))
        {
        	if(!evalColorChannel(script,channels,2,source_space,CSPACE_RGB))
        		goto eval_error;
        }
        else if(STR_EQ(last,"alpha"))
        {
        	if(!evalColorChannel(script,&alpha,0,CSPACE_RGB,CSPACE_RGB))
        		goto eval_error;
        }
        else if(STR_EQ(last,"hue"))
        {
        	if(!evalColorChannel(script,channels,0,source_space,CSPACE_HSV))
        		goto eval_error;
        }
        else if(STR_EQ(last,"saturation"))
        {
        	if(!evalColorChannel(script,channels,1,source_space,CSPACE_HSV))
        		goto eval_error;
        }
        else if(STR_EQ(last,"value"))
        {
        	if(!evalColorChannel(script,channels,2,source_space,CSPACE_HSV))
        		goto eval_error;
        }
        else if(STR_EQ(last,"cieX"))
        {
        	if(!evalColorChannel(script,channels,0,source_space,CSPACE_CIE))
        		goto eval_error;
        }
        else if(STR_EQ(last,"cieY"))
        {
        	if(!evalColorChannel(script,channels,1,source_space,CSPACE_CIE))
        		goto eval_error;
        }
        else if(STR_EQ(last,"cieZ"))
        {
        	if(!evalColorChannel(script,channels,2,source_space,CSPACE_CIE))
        		goto eval_error;
        }
        else
        {
    		conlPrintf(LOG_ERROR,
    			"evalOutput(%s): unknown channel '%s'.\n",
				file_name,last);
        	return false;
        }
    }
    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)
    	{
    		conlPrintf(LOG_ERROR,
    			"evalOutput(%s): channels of different size.\n",
				file_name);
    		return false;
    	}
    	fb = fbAlloc(channels[0]->cols,channels[0]->rows);
    	V_ASSERT(fb);
    	ctransStore3Matrices(fb,source_space,
    		channels[0],channels[1],channels[2]);
	    if(alpha)
	    {
	    	if(channels[0]->rows == alpha->rows &&
	    		channels[0]->cols == alpha->cols)
	    		ctransStore1Matrix(fb,alpha,false,false,false,true);
	    	else
    			conlPrintf(LOG_WARNING,
    				"evalOutput(%s): alpha of different size: ignored.\n",
					file_name);
	    }
	    fbSave(fb,file_name);
	    memFree(fb);
	    return true;
    }
	conlPrintf(LOG_ERROR,"evalOutput(%s): not enought channels.\n",
		file_name);
	return false;
eval_error:;
	conlPrintf(LOG_ERROR,"evalOutput(%s): unable to eval channel.\n",
		file_name);
	return false;
}

vboolean
evalAll(vhandle_t script)
{
	while(tokSkipToEx(script,"output",STR_COMP))
	{
		char name[MAX_ID];
		if(!tokCopyNextToken(script,name,MAX_ID))
		{
			conlPuts(LOG_ERROR,
        		"evalAll(): output file name expected, got EOF\n");
			return false;
		}
		if(!evalOutput(name,script))
			return false;
	}
	return true;
}

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

V_LOCAL vboolean
scriptSearchPair(vhandle_t script,char *key,char *name)
{
	tokRestart(script);
	while(tokSkipToEx(script,key,STR_COMP))
		if(tokNextIsEx(script,name,STR_COMP))
			return true;
	return false;
}

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

arg_p
evalSearchForFuncArg(vhandle_t script,int type,vboolean evaluate)
{
	arg_p arg;
	char ref[MAX_ID];
	if(!tokCopyNextToken(script,ref,MAX_ID))
	{
		SYNTAX_ERROR(script,"evalSearchForFuncArg","unexpected EOF");
		return NULL;
	}
	arg = evalSearchForName(ref);
	if(!arg)
	{
		/* evaluate */
		if(!evaluate)
		{
			UNDEF_ERROR("evalSearchForFuncArg");
			return NULL;
		}
		switch(type)
		{
			case ARG_MATRIX:
				evalMatrix(ref,script);
				arg = evalSearchForName(ref);
				break;
			case ARG_VECTOR:
				evalVector(ref,script);
				arg = evalSearchForName(ref);
				break;
			default:
				GENERIC_ERROR("evalSearchForFuncArg","unknown type, unable to evaluate");
				return NULL;
		}
	}
	if(arg->type == type)
		return arg;
	GENERIC_ERROR("evalSearchForFuncArg","type mismatch");
	return NULL;
}

V_LOCAL vboolean
uint_width_of(vhandle_t script,u32_t *res)
{
	arg_p arg = evalSearchForFuncArg(script,ARG_MATRIX,true);
	if(!arg)
		return false;
	V_ASSERT(arg->type == ARG_MATRIX);
	if(!arg->op.matrix)
	{
		if(arg->state.custom_width)
		{
			conlPrintf(LOG_WARNING,
				"uint_width_of(): arg->op == NULL, using state field.\n");
			*res = arg->state.custom_width;
			return true;
		}
		UNDEF_ERROR("uint_width_of");
		return false;
	}
	*res = arg->op.matrix->cols;
	return true;
}

V_LOCAL vboolean
uint_height_of(vhandle_t script,u32_t *res)
{
	arg_p arg = evalSearchForFuncArg(script,ARG_MATRIX,true);
	if(!arg)
		return false;
	V_ASSERT(arg->type == ARG_MATRIX);
	if(!arg->op.matrix)
	{
		if(arg->state.custom_height)
		{
			conlPrintf(LOG_WARNING,
				"uint_height_of(): arg->op == NULL, using state field.\n");
			*res = arg->state.custom_height;
			return true;
		}
		UNDEF_ERROR("uint_height_of");
		return false;
	}
	*res = arg->op.matrix->rows;
	return true;
}

V_LOCAL vboolean
uint_size_of(vhandle_t script,u32_t *res)
{
	arg_p arg = evalSearchForFuncArg(script,ARG_VECTOR,true);
	if(!arg)
		return false;
	V_ASSERT(arg->type == ARG_VECTOR);
	if(!arg->op.vector)
	{
		if(arg->state.custom_width)
		{
			conlPrintf(LOG_WARNING,
				"uint_size_of(): arg->op == NULL, using state field.\n");
			*res = arg->state.custom_width;
			return true;
		}
		UNDEF_ERROR("uint_size_of");
		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)
{
	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;
			GENERIC_ERROR("evalReal","unable to evaluate function");
			return false;
		}
	SYNTAX_ERROR(script,"evalReal","undefined function");
	return false;
}

V_LOCAL vboolean
evalInt(char *func_name,vhandle_t script,s32_t *res)
{
	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;
			GENERIC_ERROR("evalInt","unable to evaluate function");
			return false;
		}
	SYNTAX_ERROR(script,"evalInt","undefined function");
	return false;
}

V_LOCAL vboolean
evalUInt(char *func_name,vhandle_t script,u32_t *res)
{
	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;
			GENERIC_ERROR("evalUInt","unable to evaluate function");
			return false;
		}
	SYNTAX_ERROR(script,"evalUInt","undefined function");
	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,dbs(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,dbs(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,dbs(ptr->func_desc));
}

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

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

vboolean
evalRealFunction(vhandle_t script,vec_t *result)
{
	if(!tokNextFloat(script,result)) /* check for constant */
	{
		char command[MAX_ID]; /* check for command */
		if(!tokCopyLastToken(script,command,MAX_ID))
		{
			SYNTAX_ERROR(script,"evalRealFunction","unexpected EOF");
			return false;
		}
		if(!evalReal(command,script,result))
			return false;
	}
	return true;
}

vboolean
evalIntFunction(vhandle_t script,s32_t *result)
{
	if(!tokNextInteger(script,result)) /* check for constant */
	{
		char command[MAX_ID]; /* check for command */
		if(!tokCopyLastToken(script,command,MAX_ID))
		{
			SYNTAX_ERROR(script,"evalIntFunction","unexpected EOF");
			return false;
		}
		if(!evalInt(command,script,result))
			return false;
	}
	return true;
}

vboolean
evalUIntFunction(vhandle_t script,u32_t *result)
{
	s32_t temp;
	if(!tokNextInteger(script,&temp)) /* check for constant */
	{
		char command[MAX_ID]; /* check for command */
		if(!tokCopyLastToken(script,command,MAX_ID))
		{
			SYNTAX_ERROR(script,"evalUIntFunction","unexpected EOF");
			return false;
		}
		if(!evalUInt(command,script,result))
			return false;
		return true;
	}
	if(temp < 0)
		return false;
	*result = temp;
	return true;
}

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

V_LOCAL matrix_p
check_state_create_matrix(arg_p arg)
{
	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);
	}
	return arg->op.matrix;
}

V_LOCAL vector_p
check_state_create_vector(arg_p arg)
{
	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);
	}
	return arg->op.vector;
}

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

vp_stack_t fourier_transformers = TYPED_STACK_EMPTY_INITIALIZER;

V_LOCAL vhandle_t
evalFindFourierTransformer(u32_t length)
{
	u32_t i, total, l, pwr = 0;
	vhandle_t fft_trans, *cache;
	if(!IS_POWER_TWO(length))
	{
		conlPrintf(LOG_ERROR,"evalFindFourierTransformer(%u): must be power of 2.\n",length);
		return LIBV_INVALID_HANDLE;
	}
	cache = (vhandle_t*)vpsGetStorage(&fourier_transformers);
	total = vpsCount(&fourier_transformers);
	/* search in cache */
	for(i = 0;i < total;i++)
		if(fftGetLen(cache[i],&l) && l == length)
			return cache[i];
	/* nothing appropriate: create new */
	while(((u32_t)1 << pwr) < length) /* pwr = LOG2(length) */
		pwr++;
	fft_trans = fftCreateContext(pwr,true);
	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)
			fftDestroyContext(fft_trans);
	vpsClear(&fourier_transformers);
}

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

/*
 * Matrix operations
 */

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

V_LOCAL vboolean
matrix_save_op(arg_p arg,vhandle_t script)
{
	u32_t r, c;
	vhandle_t outfile;
	matrix_p m;
	char file_name[_MAX_PATH];
	V_ASSERT(arg->type == ARG_MATRIX);
	if(!tokCopyNextToken(script,file_name,_MAX_PATH))
		return false;
	outfile = fileOpenWrite(file_name);
	if(outfile == LIBV_INVALID_HANDLE)
		return false;
	m = check_state_create_matrix(arg);
	if(!m)
	{
		fileClose(outfile);
		UNDEF_ERROR("matrix_save_op");
		return false;
	}
	for(r = 0;r < m->rows;r++)
	{
		for(c = 0;c < m->cols;c++)
			filePrintf(outfile,"%g\t",m->cell[r][c]);
		filePuts(outfile,"\n");
	}
	fileClose(outfile);
	return true;
}

V_LOCAL vboolean
matrix_row_linear_op(arg_p arg,vhandle_t script)
{
	matrix_p m;
	vec_t min, max;
	V_ASSERT(arg->type == ARG_MATRIX);
	if(!evalRealFunction(script,&min))
	{
		conlPrintf(LOG_ERROR,
			"matrix_row_linear_op(): expected float 'min', got '%s'\n",
			dbs(tokLast(script)));
		return false;
	}
	if(!evalRealFunction(script,&max))
	{
		conlPrintf(LOG_ERROR,
			"matrix_row_linear_op(): expected float 'max', got '%s'\n",
			dbs(tokLast(script)));
		return false;
	}
	m = check_state_create_matrix(arg);
	if(!m)
	{
		UNDEF_ERROR("matrix_row_linear_op");
		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;
	V_ASSERT(arg->type == ARG_MATRIX);
	if(!evalRealFunction(script,&min))
	{
		conlPrintf(LOG_ERROR,
			"matrix_col_linear_op(): expected float 'min', got '%s'\n",
			dbs(tokLast(script)));
		return false;
	}
	if(!evalRealFunction(script,&max))
	{
		conlPrintf(LOG_ERROR,
			"matrix_col_linear_op(): expected float 'max', got '%s'\n",
			dbs(tokLast(script)));
		return false;
	}
	m = check_state_create_matrix(arg);
	if(!m)
	{
		UNDEF_ERROR("matrix_col_linear_op");
		return false;
	}
	matLinSpacedCols(m,min,max);
	return true;
}

V_LOCAL vboolean
matrix_fourier_forward_op(arg_p arg,vhandle_t script)
{
	matrix_p m;
	vhandle_t context;
	V_ASSERT(arg->type == ARG_MATRIX);
	m = check_state_create_matrix(arg);
	if(!m)
	{
		UNDEF_ERROR("matrix_fourier_forward");
		return false;
	}
	if(m->cols != m->rows)
	{
		GENERIC_ERROR("matrix_fourier_forward",
			"matrix must be square; try to use "
			"'custom_width' and 'custom_height' state parameters");
		return false;
	}
	if(!IS_POWER_TWO(m->cols))
	{
		GENERIC_ERROR("matrix_fourier_forward",
			"dimensions must be powers of 2; try to use "
			"'custom_width' and 'custom_height' state parameters");
		return false;
	}
	context = evalFindFourierTransformer(m->cols);
	if(context == LIBV_INVALID_HANDLE)
	{
		SYNTAX_ERROR(script,"matrix_fourier_forward",
			"unable to initialize Real FFT code");
		return false;
	}
	fftForward2D(context,m);
	return true;
}

V_LOCAL vboolean
matrix_fourier_inverse_op(arg_p arg,vhandle_t script)
{
	matrix_p m;
	vhandle_t context;
	V_ASSERT(arg->type == ARG_MATRIX);
	m = check_state_create_matrix(arg);
	if(!m)
	{
		UNDEF_ERROR("matrix_fourier_inverse");
		return false;
	}
	if(m->cols != m->rows)
	{
		GENERIC_ERROR("matrix_fourier_inverse",
			"matrix must be square; try to use "
			"'custom_width' and 'custom_height' state parameters");
		return false;
	}
	if(!IS_POWER_TWO(m->cols))
	{
		GENERIC_ERROR("matrix_fourier_inverse",
			"dimensions must be powers of 2; try to use "
			"'custom_width' and 'custom_height' state parameters");
		return false;
	}
	context = evalFindFourierTransformer(m->cols);
	if(context == LIBV_INVALID_HANDLE)
	{
		SYNTAX_ERROR(script,"matrix_fourier_inverse",
			"unable to initialize Real FFT code");
		return false;
	}
	fftInverse2D(context,m);
	return true;
}

V_LOCAL vboolean
matrix_zero_rows_op(arg_p arg,vhandle_t script)
{
	matrix_p m;
	u32_t row_min, row_max;
	V_ASSERT(arg->type == ARG_MATRIX);
	if(!evalUIntFunction(script,&row_min))
	{
		conlPrintf(LOG_ERROR,
			"matrix_zero_rows_op(): expected uint 'row_min', got '%s'\n",
			dbs(tokLast(script)));
		return false;
	}
	if(!evalUIntFunction(script,&row_max))
	{
		conlPrintf(LOG_ERROR,
			"matrix_zero_rows_op(): expected uint 'row_max', got '%s'\n",
			dbs(tokLast(script)));
		return false;
	}
	if(row_min > row_max)
	{
		conlPrintf(LOG_ERROR,
			"matrix_zero_rows_op(): row_min (%u) > row_max (%u)\n",
			row_min, row_max);
		return false;
	}
	m = check_state_create_matrix(arg);
	if(!m)
	{
		UNDEF_ERROR("matrix_zero_rows_op");
		return false;
	}
	matZeroRows(m,row_min,row_max);
	return true;
}

V_LOCAL vboolean
matrix_zero_cols_op(arg_p arg,vhandle_t script)
{
	matrix_p m;
	u32_t col_min, col_max;
	V_ASSERT(arg->type == ARG_MATRIX);
	if(!evalUIntFunction(script,&col_min))
	{
		conlPrintf(LOG_ERROR,
			"matrix_zero_cols_op(): expected uint 'col_min', got '%s'\n",
			dbs(tokLast(script)));
		return false;
	}
	if(!evalUIntFunction(script,&col_max))
	{
		conlPrintf(LOG_ERROR,
			"matrix_zero_cols_op(): expected uint 'col_max', got '%s'\n",
			dbs(tokLast(script)));
		return false;
	}
	if(col_min > col_max)
	{
		conlPrintf(LOG_ERROR,
			"matrix_zero_cols_op(): col_min (%u) > col_max (%u)\n",
			col_min, col_max);
		return false;
	}
	m = check_state_create_matrix(arg);
	if(!m)
	{
		UNDEF_ERROR("matrix_zero_cols_op");
		return false;
	}
	matZeroCols(m,col_min,col_max);
	return true;
}

V_LOCAL vboolean
matrix_band_pass_op(arg_p arg,vhandle_t script)
{
	matrix_p m;
	u32_t min, max;
	V_ASSERT(arg->type == ARG_MATRIX);
	if(!evalUIntFunction(script,&min))
	{
		conlPrintf(LOG_ERROR,
			"matrix_band_pass_op(): expected uint 'min', got '%s'\n",
			dbs(tokLast(script)));
		return false;
	}
	if(!evalUIntFunction(script,&max))
	{
		conlPrintf(LOG_ERROR,
			"matrix_band_pass_op(): expected uint 'max', got '%s'\n",
			dbs(tokLast(script)));
		return false;
	}
	if(min > max)
	{
		conlPrintf(LOG_ERROR,
			"matrix_band_pass_op(): min (%u) > max (%u)\n",min, max);
		return false;
	}
	m = check_state_create_matrix(arg);
	if(!m)
	{
		UNDEF_ERROR("matrix_band_pass_op");
		return false;
	}
	matBandPass(m,min,max);
	return true;
}

V_LOCAL vboolean
matrix_band_reject_op(arg_p arg,vhandle_t script)
{
	matrix_p m;
	u32_t min, max;
	V_ASSERT(arg->type == ARG_MATRIX);
	if(!evalUIntFunction(script,&min))
	{
		conlPrintf(LOG_ERROR,
			"matrix_band_reject_op(): expected uint 'min', got '%s'\n",
			dbs(tokLast(script)));
		return false;
	}
	if(!evalUIntFunction(script,&max))
	{
		conlPrintf(LOG_ERROR,
			"matrix_band_reject_op(): expected uint 'max', got '%s'\n",
			dbs(tokLast(script)));
		return false;
	}
	if(min > max)
	{
		conlPrintf(LOG_ERROR,
			"matrix_band_reject_op(): min (%u) > max (%u)\n",min, max);
		return false;
	}
	m = check_state_create_matrix(arg);
	if(!m)
	{
		UNDEF_ERROR("matrix_band_reject_op");
		return false;
	}
	matBandReject(m,min,max);
	return true;
}

V_LOCAL vboolean
matrix_scale_op(arg_p arg,vhandle_t script)
{
	matrix_p m;
	vec_t scale;
	V_ASSERT(arg->type == ARG_MATRIX);
	if(!evalRealFunction(script,&scale))
	{
		conlPrintf(LOG_ERROR,
			"matrix_scale_op(): expected float 'scale', got '%s'\n",
			dbs(tokLast(script)));
		return false;
	}
	m = check_state_create_matrix(arg);
	if(!m)
	{
		UNDEF_ERROR("matrix_scale_op");
		return false;
	}
	matScale(m,m,scale);
	return true;
}

V_LOCAL vboolean
matrix_offset_op(arg_p arg,vhandle_t script)
{
	matrix_p m;
	vec_t offset;
	V_ASSERT(arg->type == ARG_MATRIX);
	if(!evalRealFunction(script,&offset))
	{
		conlPrintf(LOG_ERROR,
			"matrix_offset_op(): expected float 'offset', got '%s'\n",
			dbs(tokLast(script)));
		return false;
	}
	m = check_state_create_matrix(arg);
	if(!m)
	{
		UNDEF_ERROR("matrix_offset_op");
		return false;
	}
	matOffset(m,offset);
	return true;
}

V_LOCAL vboolean
matrix_mat_scale_op(arg_p arg,vhandle_t script)
{
	matrix_p m;
	arg_p param = evalSearchForFuncArg(script,ARG_MATRIX,true);
	if(!param)
	{
		UNDEF_ERROR("matrix_mat_scale_op");
		return false;
	}
	m = check_state_create_matrix(arg);
	if(!m)
	{
		UNDEF_ERROR("matrix_mat_scale_op");
		return false;
	}
	if(m->rows != param->op.matrix->rows ||
		m->cols != param->op.matrix->cols)
	{
		GENERIC_ERROR("matrix_mat_scale_op","operands with different size");
		return false;
	}
	matScaleM(m,m,param->op.matrix);
	return true;
}

V_LOCAL vboolean
matrix_mat_offset_op(arg_p arg,vhandle_t script)
{
	matrix_p m;
	arg_p param = evalSearchForFuncArg(script,ARG_MATRIX,true);
	if(!param)
	{
		UNDEF_ERROR("matrix_mat_offset_op");
		return false;
	}
	m = check_state_create_matrix(arg);
	if(!m)
	{
		UNDEF_ERROR("matrix_mat_offset_op");
		return false;
	}
	if(m->rows != param->op.matrix->rows ||
		m->cols != param->op.matrix->cols)
	{
		GENERIC_ERROR("matrix_mat_offset_op","operands with different size");
		return false;
	}
	matAdd(m,m,param->op.matrix);
	return true;
}

V_LOCAL vboolean
matrix_adjust_contrast_op(arg_p arg,vhandle_t script)
{
	matrix_p m;
	vec_t min, max;
	V_ASSERT(arg->type == ARG_MATRIX);
	if(!evalRealFunction(script,&min))
	{
		conlPrintf(LOG_ERROR,
			"matrix_adjust_contrast_op(): expected float 'range_min', got '%s'\n",
			dbs(tokLast(script)));
		return false;
	}
	if(!evalRealFunction(script,&max))
	{
		conlPrintf(LOG_ERROR,
			"matrix_adjust_contrast_op(): expected float 'range_max', got '%s'\n",
			dbs(tokLast(script)));
		return false;
	}
	m = check_state_create_matrix(arg);
	if(!m)
	{
		UNDEF_ERROR("matrix_adjust_contrast_op");
		return false;
	}
	return cspaceAdjustContrast(m,m,min,max);
}

V_LOCAL vboolean
matrix_maximize_contrast_op(arg_p arg,vhandle_t script)
{
	matrix_p m;
	V_ASSERT(arg->type == ARG_MATRIX);
	V_ASSERT(script);
	m = check_state_create_matrix(arg);
	if(!m)
	{
		UNDEF_ERROR("matrix_maximize_contrast_op");
		return false;
	}
	return cspaceMaximizeContrast(m,m);
}

V_LOCAL vboolean
matrix_gamma_correction_op(arg_p arg,vhandle_t script)
{
	matrix_p m;
	arg_p param = evalSearchForFuncArg(script,ARG_VECTOR,true);
	if(!param)
	{
		UNDEF_ERROR("matrix_gamma_correction_op");
		return false;
	}
	m = check_state_create_matrix(arg);
	if(!m)
	{
		UNDEF_ERROR("matrix_gamma_correction_op");
		return false;
	}
	cspaceGammaCorrection(m,m,param->op.vector,false);
	return true;
}

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

/*
 * Vector operations
 */

V_LOCAL vboolean
vector_save_op(arg_p arg,vhandle_t script)
{
	u32_t i;
	vhandle_t outfile;
	vector_p v;
	char file_name[_MAX_PATH];
	V_ASSERT(arg->type == ARG_VECTOR);
	if(!tokCopyNextToken(script,file_name,_MAX_PATH))
		return false;
	outfile = fileOpenWrite(file_name);
	if(outfile == LIBV_INVALID_HANDLE)
		return false;
	v = check_state_create_vector(arg);
	if(!v)
	{
		UNDEF_ERROR("vector_save_op");
		return false;
	}
	for(i = 0;i < v->size;i++)
		filePrintf(outfile,"%g\t",v->elements[i]);
	filePuts(outfile,"\n");
	fileClose(outfile);
	return true;
}

V_LOCAL vboolean
vector_linear_op(arg_p arg,vhandle_t script)
{
	vector_p v;
	vec_t min, max;
	V_ASSERT(arg->type == ARG_VECTOR);
	if(!evalRealFunction(script,&min))
	{
		conlPrintf(LOG_ERROR,
			"vector_linear_op(): expected float 'min', got '%s'\n",
			dbs(tokLast(script)));
		return false;
	}
	if(!evalRealFunction(script,&max))
	{
		conlPrintf(LOG_ERROR,
			"vector_linear_op(): expected float 'max', got '%s'\n",
			dbs(tokLast(script)));
		return false;
	}
	v = check_state_create_vector(arg);
	if(!v)
	{
		UNDEF_ERROR("vector_linear_op");
		return false;
	}
	vecLinSpaced(v,min,max);
	return true;
}

V_LOCAL vboolean
vector_fourier_forward_op(arg_p arg,vhandle_t script)
{
	vector_p v;
	vhandle_t context;
	V_ASSERT(arg->type == ARG_VECTOR);
	v = check_state_create_vector(arg);
	if(!v)
	{
		UNDEF_ERROR("vector_fourier_forward");
		return false;
	}
	if(!IS_POWER_TWO(v->size))
	{
		GENERIC_ERROR("vector_fourier_forward",
			"length must be power of 2; try to use "
			"'custom_size' state parameter");
		return false;
	}
	context = evalFindFourierTransformer(v->size);
	if(context == LIBV_INVALID_HANDLE)
	{
		SYNTAX_ERROR(script,"vector_fourier_forward",
			"unable to initialize Real FFT code");
		return false;
	}
	fftForward(context,v->elements);
	return true;
}

V_LOCAL vboolean
vector_fourier_inverse_op(arg_p arg,vhandle_t script)
{
	vector_p v;
	vhandle_t context;
	V_ASSERT(arg->type == ARG_VECTOR);
	v = check_state_create_vector(arg);
	if(!v)
	{
		UNDEF_ERROR("vector_fourier_inverse");
		return false;
	}
	if(!IS_POWER_TWO(v->size))
	{
		GENERIC_ERROR("vector_fourier_inverse",
			"length must be power of 2; try to use "
			"'custom_size' state parameter");
		return false;
	}
	context = evalFindFourierTransformer(v->size);
	if(context == LIBV_INVALID_HANDLE)
	{
		SYNTAX_ERROR(script,"vector_fourier_inverse",
			"unable to initialize Real FFT code");
		return false;
	}
	fftInverse(context,v->elements);
	return true;
}

V_LOCAL vboolean
vector_histogram_op(arg_p arg,vhandle_t script)
{
	vector_p v;
	arg_p param = evalSearchForFuncArg(script,ARG_MATRIX,true);
	if(!param)
	{
		UNDEF_ERROR("vector_historgam_op");
		return false;
	}
	v = check_state_create_vector(arg);
	if(!v)
	{
		UNDEF_ERROR("vector_historgam_op");
		return false;
	}
	cspaceHistogram(v,param->op.matrix,false);
	return true;
}

V_LOCAL vboolean
vector_integrate_op(arg_p arg,vhandle_t script)
{
	vector_p v;
	V_ASSERT(script);
	v = check_state_create_vector(arg);
	if(!v)
	{
		UNDEF_ERROR("vector_integrate_op");
		return false;
	}
	vecRunningSum(v,v);
	return true;
}

V_LOCAL vboolean
vector_maximize_contrast_op(arg_p arg,vhandle_t script)
{
	vector_p v;
	V_ASSERT(script);
	v = check_state_create_vector(arg);
	if(!v)
	{
		UNDEF_ERROR("vector_integrate_op");
		return false;
	}
	cspaceMaximizeContrastV(v,v);
	return true;
}

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

/*
 * Operations common for matrices and vectors
 */

vboolean generic_constant_op(arg_p arg,vhandle_t script)
{
	vec_t val;
	u32_t r, c;
	vector_p v;
	matrix_p m;
	if(!evalRealFunction(script,&val))
	{
		conlPrintf(LOG_ERROR,
			"generic_constant(): expected float, got '%s'\n",
			dbs(tokLast(script)));
		return false;
	}
	switch(arg->type)
	{
		case ARG_MATRIX:
			m = check_state_create_matrix(arg);
			if(!m)
			{
				UNDEF_ERROR("generic_constant_op");
				return false;
			}
			for(r = 0;r < m->rows;r++)
				for(c = 0;c < m->cols;c++)
					m->cell[r][c] = val;
			break;
		case ARG_VECTOR:
			v = check_state_create_vector(arg);
			if(!v)
			{
				UNDEF_ERROR("generic_constant_op");
				return false;
			}
			for(c = 0;c < v->size;c++)
				v->elements[c] = val;
			break;
		default:
			return false;
	}
	return true;
}

vboolean generic_random_op(arg_p arg,vhandle_t script)
{
	vec_t min, max;
	u32_t r, c;
	vector_p v;
	matrix_p m;
	if(!evalRealFunction(script,&min))
	{
		conlPrintf(LOG_ERROR,
			"generic_random(): expected float, got '%s'\n",
			dbs(tokLast(script)));
		return false;
	}
	if(!evalRealFunction(script,&max))
	{
		conlPrintf(LOG_ERROR,
			"generic_random(): expected float, got '%s'\n",
			dbs(tokLast(script)));
		return false;
	}
	switch(arg->type)
	{
		case ARG_MATRIX:
			m = check_state_create_matrix(arg);
			if(!m)
			{
				UNDEF_ERROR("generic_random_op");
				return false;
			}
			for(r = 0;r < m->rows;r++)
				for(c = 0;c < m->cols;c++)
					m->cell[r][c] = urnNextFloatRange(random_gen,min,max);
			break;
		case ARG_VECTOR:
			v = check_state_create_vector(arg);
			if(!v)
			{
				UNDEF_ERROR("generic_constant_op");
				return false;
			}
			urnNextVectorRange(random_gen,v,min,max);
			break;
		default:
			return false;
	}
	return true;
}

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

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

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

typedef struct
{
	char 		*op_name;
	matrix_op	routine;
	char		*description;
}	matrix_op_field;

typedef struct
{
	char 		*op_name;
	vector_op	routine;
	char		*description;
}	vector_op_field;

matrix_op_field matrix_op_fields[] =
{
	{	"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"
	},
	{
		"random",
		generic_random_op,
		"sets all elements of matrix to randoms between given 'min' and 'max'"
	},
	{
		"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,
		"computes 2D forward Fourier transform"
	},
	{
		"fourier_inverse",
		matrix_fourier_inverse_op,
		"computes 2D inverse Fourier transform"
	},
	{
		"scale",
		matrix_scale_op,
		"multiplies matrix elements by given real 'scale'"
	},
	{
		"offset",
		matrix_offset_op,
		"adds given real 'offset' to matrix elements"
	},
	{
		"mat_scale",
		matrix_mat_scale_op,
		"multiplies matrix elements by corresponding elements of given matrix"
	},
	{
		"mat_offset",
		matrix_mat_offset_op,"adds given matrix"
	},
	{
		"zero_rows",
		matrix_zero_rows_op,
		"clears rows from given 'min' to given 'max'"
	},
	{
		"zero_cols",
		matrix_zero_cols_op,
		"clears cols from given 'min' to given 'max'"
	},
	{
		"band_reject",
		matrix_band_reject_op,
		"clears both cols and rows from given 'min' to given 'max'"
	},
	{
		"band_pass",
		matrix_band_pass_op,
		"cols & rows from given 'min' to given 'max' untouched, other cleared"
	},
	{
		"adjust_contrast",
		matrix_adjust_contrast_op,
		"linear map from given [min;max] to [0,1]"
	},
	{
		"maximize_contrast",
		matrix_maximize_contrast_op,
		"linear map from [maxrix lower;matrix upper] to [0,1]"
	},
	{
		"gamma_correction",
		matrix_gamma_correction_op,
		"curve map from [maxrix lower;matrix upper] to [0,1]; uses given vector as curve"
	},
	{NULL,NULL,NULL}
};

vector_op_field vector_op_fields[]=
{
	{
		"save",
		vector_save_op,
		"writes vector to text file; requires file name"
	},
	{
		"constant",
		generic_constant_op,
		"sets all elements of vector to given float constant"
	},
	{
		"random",
		generic_random_op,
		"sets all elements of vector to randoms between given 'min' and 'max'"
	},
	{
		"linear",
		vector_linear_op,
		"interpolates from given 'min' to given 'max'"
	},
	{
		"fourier_forward",
		vector_fourier_forward_op,
		"computes forward Fourier transform"
	},
	{
		"fourier_inverse",
		vector_fourier_inverse_op,
		"computes inverse Fourier transform"
	},
	{
		"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 vboolean
evalFindMatrixOp(char *op,matrix_op *proc_ret)
{
	matrix_op_field *ptr;
	for(ptr = matrix_op_fields;ptr->op_name;ptr++)
		if(STR_EQ(ptr->op_name,op))
		{
			*proc_ret = ptr->routine;
			return true;
		}
	return false;
}

V_LOCAL vboolean
evalFindVectorOp(char *op,vector_op *proc_ret)
{
	vector_op_field *ptr;
	for(ptr = vector_op_fields;ptr->op_name;ptr++)
		if(STR_EQ(ptr->op_name,op))
		{
			*proc_ret = ptr->routine;
			return true;
		}
	return false;
}

V_LOCAL void
evalHelpMatrixOps(void)
{
	matrix_op_field *ptr;
	conPuts("Available matrix operations:\n");
	for(ptr = matrix_op_fields;ptr->op_name;ptr++)
		conPrintf("\t'%s' - %s\n",ptr->op_name,dbs(ptr->description));
}

V_LOCAL void
evalHelpVectorOps(void)
{
	vector_op_field *ptr;
	conPuts("Available vector operations:\n");
	for(ptr = vector_op_fields;ptr->op_name;ptr++)
		conPrintf("\t'%s' - %s\n",ptr->op_name,dbs(ptr->description));
}

void evalHelp()
{
	evalHelpMatrixOps();
	evalHelpVectorOps();
	evalHelpFuncs();
	conPuts("for list of state variables, pass --help-state\n");
}

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

vboolean
scriptEvalMatrix(arg_p arg,vhandle_t script)
{
	matrix_op op_routine;
	V_ASSERT(arg->type == ARG_MATRIX);
	if(!scriptSearchPair(script,"matrix",arg->id))
	{
		conlPrintf(LOG_ERROR,"scriptEvalMatrix(%s): undefined.\n",
			arg->id);
		return false;
	}
	if(!tokNextIs(script,"{"))
	{
		SYNTAX_ERROR(script,"scriptEvalMatrix","expected '{'");
		return false;
	}
	while(tokNext(script))
	{
		char last[256];
		tokCopyLastToken(script,last,256);
		if(STR_EQ(last,"}"))
		{
			break;
		} else if(STR_EQ(last,"state"))
		{
			if(!stateChange(&(arg->state),script))
				break;
		} else if(evalFindMatrixOp(last,&op_routine))
		{
			if(!(*op_routine)(arg,script))
				break;
		} else
		{
			/* no commands: assume input */
			arg->op.matrix = evalMatrix(last,script);
		}
	}
	return (arg->op.matrix) ? true : false;
}

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

vboolean
scriptEvalVector(arg_p arg,vhandle_t script)
{
	vector_op op_routine;
	V_ASSERT(arg->type == ARG_VECTOR);
	if(!scriptSearchPair(script,"vector",arg->id))
	{
		conlPrintf(LOG_ERROR,"scriptEvalVector(%s): undefined.\n",
			arg->id);
		return false;
	}
	if(!tokNextIs(script,"{"))
	{
		SYNTAX_ERROR(script,"scriptEvalVector","expected '{'");
		return false;
	}
	while(tokNext(script))
	{
		char last[256];
		tokCopyLastToken(script,last,256);
		if(STR_EQ(last,"}"))
		{
			break;
		} else if(STR_EQ(last,"state"))
		{
			if(!stateChange(&(arg->state),script))
				break;
		} else if(evalFindVectorOp(last,&op_routine))
		{
			if(!(*op_routine)(arg,script))
				break;
		} else
		{
			/* no commands: assume input */
			arg->op.vector = evalVector(last,script);
		}
	}
	return (arg->op.vector) ? true : false;
}
