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

#define	ARG_MATRIX	0
#define	ARG_VECTOR	1

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
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 < n_divs;i++)
    	dest->cell[0][i] = dest->cell[0][i-1] + delta;
    for(i = 1;i < dest->rows;i++)
    	memCopy(&(dest->cell[0][i]),&(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 < n_divs;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[0][i];
}

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

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;
	if(arg->op.data)
		memFree(arg->op.data);
	memFree(arg);
}

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]->op.data)
				memFree(args[i]->op.data);
			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;
    arg->evaluated = scriptEvalVector(arg,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(script,name);
	return (channels[index]) ? true : false;
}

vboolean
evalOutput(char *file_name,vhandle_t script)
{
	framebuf_p fb = NULL;
	u32_t source_space = CSPACE_RGB;
	matrix_p channels[3] = {NULL,NULL,NULL};
	matrix_p alpha = NULL;
	while(tokNext(script))
    {
    	char *last = dbs(tokLast(script));
    	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(script,name))
			return false;
	}
	return true;
}

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

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

#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)
	
/* *********************************************************** */

arg_p evalSearchForFuncArg(vhandle_t script)
{
	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)
	{
		UNDEF_ERROR("evalSearchForFuncArg");
		return NULL;
	}
	return arg;
}

vboolean uint_width_of(vhandle_t script,u32_t *res)
{
	arg_p arg = evalSearchForFuncArg(script);
	if(!arg)
		return false;
	if(arg->type != ARG_MATRIX)
	{
		SYNTAX_ERROR(script,"uint_width_of","should be matrix");
		return false;
	}
	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;
}

vboolean uint_height_of(vhandle_t script,u32_t *res)
{
	arg_p arg = evalSearchForFuncArg(script);
	if(!arg)
		return false;
	if(arg->type != ARG_MATRIX)
	{
		SYNTAX_ERROR(script,"uint_height_of","should be matrix");
		return false;
	}
	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;
}

vboolean uint_size_of(vhandle_t script,u32_t *res)
{
	arg_p arg = evalSearchForFuncArg(script);
	if(!arg)
		return false;
	if(arg->type != ARG_VECTOR)
	{
		SYNTAX_ERROR(script,"uint_size_of","should be vector");
		return false;
	}
	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;

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

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

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_height_of,"returns number of elements in given vector"},
	{NULL,NULL}
};

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

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

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

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

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

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

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

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 = matAlloc(st->custom_height,st->custom_width);
	}
	return arg->op.matrix;
}

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 = vecAlloc(st->custom_width);
	}
	return arg->op.vector;
}

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

vp_stack_t fourier_transformers = TYPED_STACK_EMPTY_INITIALIZER;

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
 */

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

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

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

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

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

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

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

/*
 * Vector operations
 */

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

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

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

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

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

/*
 * 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"},
	{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"},
	{NULL,NULL,NULL}
};

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

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

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

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(script,last);
		}
	}
	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(script,last);
		}
	}
	return (arg->op.vector) ? true : false;
}
