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

V_LOCAL vboolean
ipl_width_of(vhandle_t lexer,vec_t *res)
{
	CALLER_NAME(ipl_width_of)
	arg_p arg = evalNextArgument(lexer,ARG_MATRIX);
	if(!arg)
		return false;
	if(!arg->data.matrix)
	{
		if(arg->state.custom_width)
		{
			WARNING_GENERIC(ipl_using_state);
			*res = (vec_t)(arg->state.custom_width);
			return true;
		}
		PARSE_ERROR(lexer,ipl_null_func_arg);
		return false;
	}
	*res = (vec_t)(arg->data.matrix->cols);
	return true;
}

V_LOCAL vboolean
ipl_height_of(vhandle_t lexer,vec_t *res)
{
	CALLER_NAME(ipl_height_of)
	arg_p arg = evalNextArgument(lexer,ARG_MATRIX);
	if(!arg)
		return false;
	if(!arg->data.matrix)
	{
		if(arg->state.custom_height)
		{
			WARNING_GENERIC(ipl_using_state);
			*res = (vec_t)(arg->state.custom_height);
			return true;
		}
		PARSE_ERROR(lexer,ipl_null_func_arg);
		return false;
	}
	*res = (vec_t)(arg->data.matrix->rows);
	return true;
}

V_LOCAL vboolean
ipl_size_of(vhandle_t lexer,vec_t *res)
{
	CALLER_NAME(ipl_size_of)
	arg_p arg = evalNextArgument(lexer,ARG_VECTOR);
	if(!arg)
		return false;
	if(!arg->data.vector)
	{
		if(arg->state.custom_width)
		{
			WARNING_GENERIC(ipl_using_state);
			*res = (vec_t)(arg->state.custom_width);
			return true;
		}
		PARSE_ERROR(lexer,ipl_null_func_arg);
		return false;
	}
	*res = (vec_t)(arg->data.vector->size);
	return true;
}

V_LOCAL vboolean
ipl_depth_of(vhandle_t lexer,vec_t *res)
{
	CALLER_NAME(ipl_depth_of)
	arg_p arg = evalNextArgument(lexer,ARG_DECOMPOSITION);
	if(!arg)
		return false;
	if(!arg->data.decomposition)
	{
		PARSE_ERROR(lexer,ipl_null_func_arg);
		return false;
	}
	*res = (vec_t)(arg->data.decomposition->depth_levels);
	return true;
}

V_LOCAL vboolean
ipl_user_input(vhandle_t lexer,vec_t *res)
{
	CALLER_NAME(ipl_user_input)
	char buf[256] = {0}, *ptr;
	char *prompt = lexNext(lexer);
	if(!prompt)
	{
		SYN_UNEXPECTED_EOF(lexer);
		return false;
	}
	conPrintf("%s",prompt);
	fgets(buf,256,stdin);
	ptr = strStripLT(buf);
	if(!strToFloat(ptr,res))
	{
		ERROR_GENERIC(ipl_wrong_float_constant);
		return false;
	}
	return true;
}

typedef vboolean (*ipl_func_t)(vhandle_t lexer,vec_t *res);

typedef struct  ipl_func_entry_s
{
	char_p	  *name_ptr;
	ipl_func_t  routine;
	char_p	  *desc_ptr;
}   ipl_func_entry_t, *ipl_func_entry_p;

V_LOCAL ipl_func_entry_t ipl_funcs[] =
{
	{
		&ipl_key_width_of,
		ipl_width_of,
		&ipl_desc_width_of,
	},
	{
		&ipl_key_height_of,
		ipl_height_of,
		&ipl_desc_height_of,
	},
	{
		&ipl_key_size_of,
		ipl_size_of,
		&ipl_desc_size_of,
	},
	{
		&ipl_key_depth_of,
		ipl_depth_of,
		&ipl_desc_depth_of,
	},
	{
		&ipl_key_user_input,
		ipl_user_input,
		&ipl_desc_user_input,
	},
	{NULL,NULL,NULL}
};

V_LOCAL ipl_func_t
evalFindFunction(char *name)
{
	ipl_func_entry_p head;
	for(head = ipl_funcs;head->name_ptr;head++)
		if(STR_EQ(*head->name_ptr,name))
			return head->routine;
	return NULL;
}

void
evalListFunctions(void)
{
	ipl_func_entry_p head;
	conPuts("Available functions:\n");
	for(head = ipl_funcs;head->name_ptr;head++)
		conPrintf("\t\"%s\": %s%c\n",*head->name_ptr,*head->desc_ptr,
			(head + 1)->name_ptr ? ';' : '.');
}

typedef struct ipl_script_s
{
	char_p	  name;
	vhandle_t   nsl_group;
	vec_p	   var_pool;
	vp_stack_p  body;
	vboolean	loaded;
}   ipl_script_t, *ipl_script_p;

V_LOCAL vp_stack_t scripts = TYPED_STACK_EMPTY_INITIALIZER;

V_LOCAL void
iplFreeScript(ipl_script_p script,vboolean remove_from_cache)
{
	if(!script)
		return;
	if(remove_from_cache)
	{
		u32_t pos;
		if(vpsFind(&scripts,script,&pos))
			vpsSet(&scripts,pos,NULL);
	}
	if(script->name)
		memFree(script->name);
	if(script->nsl_group)
		nslDestroyFunctionGroup(script->nsl_group);
	memFree(script);
}

void
evalFreeFunctions(void)
{
	ipl_script_p sc;
	while(vpsPop(&scripts,(void_p*)&sc))
		iplFreeScript(sc,false);
	vpsClear(&scripts);
}

#include "argsused.h"
vboolean V_CALL
ipl_unresolved_cb(void_p arg,vhandle_t lexer,vec_p val_ret)
{
	return evalLastReal(lexer,val_ret);
}

/* assumes keyword 'function' _and_ function name already read in */
V_LOCAL vboolean
iplLoadUserFunc(ipl_script_p self,vhandle_t lexer)
{
	CALLER_NAME(iplLoadUserFunc)
	self->nsl_group = LIBV_INVALID_HANDLE;
	self->loaded = false;
	if(!lexLast(lexer))
	{
		SYN_UNEXPECTED_EOF(lexer);
		return false;
	}
	self->name = strDup(lexLast(lexer));
	self->nsl_group = nslCreateFunctionGroup(NULL,ipl_builtin_consts,
		ipl_unresolved_cb,NULL);
	if(self->nsl_group == LIBV_INVALID_HANDLE)
		return false;
	if(!nslParseFunction(self->nsl_group,lexer))
		return false;
	V_ASSERT(nslCreateExecutables(self->nsl_group));
	V_ASSERT(nslGetExecutable(self->nsl_group,0,&self->body,&self->var_pool));
	self->loaded = true;
	return true;
}

/* preserves position of 'lexer' */
V_LOCAL ipl_script_p
iplFindUserFunc(char *name,vhandle_t lexer)
{
	CALLER_NAME(iplFindUserFunc)
	u32_t i, nc = vpsCount(&scripts);
	ipl_script_p self;
	for(i = 0;i < nc;i++)
	{
		self = (ipl_script_p)vpsGet(&scripts,i);
		if(self && STR_EQ(name,self->name))
		{
			if(!self->loaded)
			{
				PARSE_ERROR(lexer,ipl_circular_reference);
				return NULL;
			}
			return self;
		}
	}
	if(!scriptSearchPair(lexer,ipl_key_function,name))
		return NULL;
	self = (ipl_script_p)memAllocZ(sizeof(ipl_script_t));
	vpsPush(&scripts,self);
	if(!iplLoadUserFunc(self,lexer))
	{
		iplFreeScript(self,true);
		self = NULL;
	}
	lexPop(lexer,LEX_POSITION_BIT);
	return self;
}

V_LOCAL vboolean
eval_real_script(char *fn_name,vhandle_t lexer,vec_p val_ret)
{
	ipl_script_p fn = iplFindUserFunc(fn_name,lexer);
	if(!fn)
		return false;
	NSL_EXECUTE(fn->body,fn->var_pool);
	*val_ret = fn->var_pool[EXIT_CODE_OFFSET];
	return true;
}

V_LOCAL vboolean
eval_last_real(vhandle_t lexer,vec_t *result,vboolean func_only)
{
	CALLER_NAME(eval_last_real)
	char *name;
	ipl_func_t func;
	if(!func_only && lexLastFloat(lexer,result)) /* check for constant */
		return true;
	name = lexLast(lexer);
	if(!name)
	{
		SYN_UNEXPECTED_EOF(lexer);
		return false;
	}
	func = evalFindFunction(name);
	if(func)
		return (*func)(lexer,result);
	if(eval_real_script(name,lexer,result))
		return true;
	return false;
}

vboolean
evalLastReal(vhandle_t lexer,vec_t *result)
{
	return eval_last_real(lexer,result,false);
}

vboolean
evalReal(vhandle_t lexer,vec_t *result)
{
	lexNext(lexer);
	return eval_last_real(lexer,result,false);
}

vboolean
evalLastInt(vhandle_t lexer,s32_t *result)
{
	CALLER_NAME(evalLastInt)
	vec_t tmp, diff;
	if(strToInt(lexLast(lexer),result))
		return true;
	if(!eval_last_real(lexer,&tmp,true))
		return false;
	*result = (s32_t)floor(tmp);
	diff = (vec_t)*result - tmp;
	if(!IsZero(diff))
	{
		if(v_abs(diff) >= 1)
		{
			PARSE_ERROR(lexer,ipl_roundoff_too_large);
			return false;
		}
		WARNING_GENERIC(ipl_cast_to_integer);
	}
	return true;
}

vboolean
evalLastUInt(vhandle_t lexer,u32_t *result)
{
	CALLER_NAME(evalLastUInt)
	vec_t tmp, diff;
	if(strToUInt(lexLast(lexer),result))
		return true;
	if(!eval_last_real(lexer,&tmp,true))
		return false;
	*result = (u32_t)floor(tmp);
	diff = (vec_t)*result - tmp;
	if(!IsZero(diff))
	{
		if(v_abs(diff) >= 1)
		{
			PARSE_ERROR(lexer,ipl_roundoff_too_large);
			return false;
		}
		WARNING_GENERIC(ipl_cast_to_integer);
	}
	return true;
}

vboolean
evalInt(vhandle_t lexer,s32_t *result)
{
	lexNext(lexer);
	return evalLastInt(lexer,result);
}

vboolean
evalUInt(vhandle_t lexer,u32_t *result)
{
	lexNext(lexer);
	return evalLastUInt(lexer,result);
}

vboolean
evalBoolean(vhandle_t lexer,vboolean *result)
{
	CALLER_NAME(evalBoolean)
	char *token;
	vec_t tmp;
	token = lexNext(lexer);
	if(!token)
	{
		SYN_UNEXPECTED_EOF(lexer);
		return false;
	}
	if(STR_EQ(token,ipl_key_true))
	{
		*result = true;
		return true;
	}
	if(STR_EQ(token,ipl_key_false))
	{
		*result = false;
		return true;
	}
	if(!eval_last_real(lexer,&tmp,true))
		return false;
	if(IsZero(tmp))
	{
		*result = false;
		return true;
	}
	if(IsZero(tmp - 1))
	{
		*result = true;
		return true;
	}
	WARNING_GENERIC(ipl_cast_to_integer);
	*result = true;
	return true;
}

