#include "psmod.h"

#define	RANGE_INVALID	-1

/* FIXME: obsolete, move code to icsg.c */

/*
 * Initialized in global_rangom()
 * Destroyed in psDestroy()
 */
vhandle_t random_generator = LIBV_INVALID_HANDLE;

typedef enum
{
	plane_constrain,
	box_constrain,
	sphere_constrain,
	cylinder_constrain
}	constrain_type_t;

typedef struct
{
	vec_t min, max;
}	linear_constrain_t, *linear_constrain_p;

typedef struct
{
	constrain_type_t	type;
	vboolean			solid;
	vec_t				spacing;
	union
	{
		struct
		{
			vec3_t	normal;
			vec_t	dist;
		} plane;
		struct
		{
			vec3_t	mins;
			vec3_t	maxs;
		} box;
		struct
		{
			vec3_t	center;
			vec_t	radius;
		} sphere;
		struct
		{
			vec_t	lb_center_and_dir[6];
			vec_t	radius;
			vec_t	height;
		} cylinder;
	} constrain;
} spatial_constrain_t, *spatial_constrain_p;

typedef struct dc_parse_entry_s
{
	char		name[MAX_ID];
	vboolean	(*entry)(vhandle_t script,dc_p dc);
	char		desc[MAX_DESC];
}	dc_parse_entry_t, *dc_parse_entry_p;

typedef struct
{
	vec_p ptr;
	vec_p endptr;
	u32_t elem_size;
	u32_t num_elems;
	dc_p dc;
	vec3_t mins;
	vec3_t maxs;
	vhandle_t script;
}	dist_arg_t, *dist_arg_p;

V_LOCAL vboolean
dc_range(vhandle_t script,dc_p dc)
{
	CALLER_NAME(dc_range)
	if(!lexNextInteger(script,&dc->range_min))
	{
		SYN_EXPECTED(script,"integer range_min");
		return false;
	}
	if(!lexNextInteger(script,&dc->range_max))
	{
		SYN_EXPECTED(script,"integer range_max");
		return false;
	}
	return true;
}

V_LOCAL vboolean
dc_reset(vhandle_t script,dc_p dc)
{
	CALLER_NAME(dc_reset)
	u32_t idx;
	static char *pair[] = {"range", "constrains"};
	if(!lexNextMatchesEx(script,pair,2,&idx,STR_COMP))
	{
		SYN_EXPECTED(script,"range|constrains");
		return false;
	}
	if(idx)
		icResetDC(dc);
	else
		icResetRange(dc);
	return true;
}

V_LOCAL vboolean
dc_interval(vhandle_t script,dc_p dc)
{
	CALLER_NAME(dc_interval)
	linear_constrain_t lc;
	if(!lexNextFloat(script,&lc.min))
	{
		SYN_EXPECTED(script,"float min");
		return false;
	}
	if(!lexNextFloat(script,&lc.max))
	{
		SYN_EXPECTED(script,"float max");
		return false;
	}
	stackPush(&dc->linear_constrains,&lc);
	return true;
}

V_LOCAL vboolean
dc_spacing(vhandle_t script,dc_p dc)
{
	CALLER_NAME(dc_spacing)
	if(!lexNextFloat(script,&dc->spacing_3d))
	{
		SYN_EXPECTED(script,"float");
		return false;
	}
	return true;
}

V_LOCAL vboolean
dc_timeout(vhandle_t script,dc_p dc)
{
	CALLER_NAME(dc_timeout)
	vec_t val;
	if(!lexNextFloat(script,&val))
	{
		SYN_EXPECTED(script,"float");
		return false;
	}
	dc->timeout_ms = (u32_t)(1000 * val);
	return true;
}

/*
 * Used also by unary.c
 */
vboolean
icCheckSolid(vhandle_t script,vboolean *solid_ret)
{
	CALLER_NAME(icCheckSolid)
	u32_t idx;
	static char *pair[] = {"solid","hollow"};
	if(!lexNextMatchesEx(script,pair,2,&idx,STR_COMP))
	{
		SYN_EXPECTED(script,"hollow | solid");
		return false;
	}
	*solid_ret = (idx) ? true : false;
	return true;
}

V_LOCAL vboolean
dc_plane(vhandle_t script,dc_p dc)
{
	vec_t nd;
	spatial_constrain_t sc;
	CALLER_NAME(dc_plane)
	sc.solid = true;
	if(!icParseVec3Table(sc.constrain.plane.normal,
		sizeof(vec3_t),1,NULL,script))
	{
		SYN_EXPECTED(script,"const vec3 table[1] normal");
		return false;
	}
	nd = vec3Abs(sc.constrain.plane.normal);
	if(IsZero(nd))
	{
		ERROR_GENERIC("degenerate normal");
		return false;
	}
	vec3Scale(sc.constrain.plane.normal,sc.constrain.plane.normal,nd);
	if(!lexNextFloat(script,&sc.constrain.plane.dist))
	{
		SYN_EXPECTED(script,"float dist");
		return false;
	}
	sc.spacing = dc->spacing_3d;
	sc.type = plane_constrain;
	stackPush(&dc->spatial_constrains,&sc);
	return true;
}

V_LOCAL vboolean
dc_sphere(vhandle_t script,dc_p dc)
{
	spatial_constrain_t sc;
	CALLER_NAME(dc_sphere)
	if(icCheckSolid(script,&sc.solid))
		return false;
	if(!icParseVec3Table(sc.constrain.sphere.center,
		sizeof(vec3_t),1,NULL,script))
	{
		SYN_EXPECTED(script,"const vec3 table[1] radius");
		return false;
	}
	if(!lexNextFloat(script,&sc.constrain.sphere.radius))
	{
		SYN_EXPECTED(script,"float radius");
		return false;
	}
	sc.spacing = dc->spacing_3d;
	sc.type = sphere_constrain;
	stackPush(&dc->spatial_constrains,&sc);
	return true;
}

V_LOCAL vboolean
dc_cylinder(vhandle_t script,dc_p dc)
{
	spatial_constrain_t sc;
	CALLER_NAME(dc_cylinder)
	if(icCheckSolid(script,&sc.solid))
		return false;
	if(!icParseVec3Table(sc.constrain.cylinder.lb_center_and_dir,
		sizeof(vec3_t),2,NULL,script))
	{
		SYN_EXPECTED(script,"const vec3 table[2] base, axis");
		return false;
	}
	if(!lexNextFloat(script,&sc.constrain.cylinder.radius))
	{
		SYN_EXPECTED(script,"float radius");
		return false;
	}
	if(!lexNextFloat(script,&sc.constrain.cylinder.height))
	{
		SYN_EXPECTED(script,"float height");
		return false;
	}
	sc.spacing = dc->spacing_3d;
	sc.type = cylinder_constrain;
	stackPush(&dc->spatial_constrains,&sc);
	return true;
}

V_LOCAL vboolean
dc_box(vhandle_t script,dc_p dc)
{
	spatial_constrain_t sc;
	CALLER_NAME(dc_box)
	vec3_t center_ext[2];
	if(icCheckSolid(script,&sc.solid))
		return false;
	if(!icParseVec3Table(center_ext,sizeof(vec3_t),2,NULL,script))
	{
		SYN_EXPECTED(script,"const vec3 table[2] center, extents");
		return false;
	}
	vec3Scale(center_ext[1],center_ext[1],(vec_t)0.5);
	vec3Sub(sc.constrain.box.mins,center_ext[0],center_ext[1]);
	vec3Add(sc.constrain.box.maxs,center_ext[0],center_ext[1]);
	sc.spacing = dc->spacing_3d;
	sc.type = box_constrain;
	stackPush(&dc->spatial_constrains,&sc);
	return true;
}

static dc_parse_entry_t	dc_ops[]={
	{"range",dc_range,"int,int:operation range for 1D/3D distributions"},
	{"reset",dc_reset,"[range|constrains]:clears either range or constrains"},
	{"timeout",dc_timeout,"float timeout:timeout for 1D/3D distributions"},
	{"interval",dc_interval,"float,float:lower an upper bounds for 1D distributions"},
	{"spacing",dc_spacing,"float:minimal distance for 3D distributions"},
	{"plane",dc_plane,"vec3 table[1] normal;float dist: 3D plane constrain"},
	{"sphere",dc_sphere,"[hollow|solid];vec3 table[1] center;float radius: 3D sphere constrain"},
	{"cylinder",dc_cylinder,"[hollow|solid];vec3 table[2] lower_base_center, axis_dir;float radius; float height: 3D cylinder constrain"},
	{"box",dc_box,"[hollow|solid];vec3 table[2] center,extents: 3D box constrain"},
	{"",NULL,""}
};

V_LOCAL void
icHelpDC(void)
{
	unsigned i;
	conPuts("Known operations with distribution constrains:\n");
	for(i=0;dc_ops[i].entry;i++)
		conPrintf("%s\t - %s.\n",dc_ops[i].name,dc_ops[i].desc);
}

void
icResetRange(dc_p dc)
{
	dc->range_min = dc->range_max = RANGE_INVALID;
}

void
icResetDC(dc_p dc)
{
	stackResize(&dc->linear_constrains,0);
	stackResize(&dc->spatial_constrains,0);
	dc->spacing_3d = 0;
}

void
icInitDC(dc_p dc)
{
	stackInit(&dc->linear_constrains,sizeof(linear_constrain_t));
	stackInit(&dc->spatial_constrains,sizeof(spatial_constrain_t));
	icResetRange(dc);
	dc->accept_extra = NULL;
	dc->accept_extra_arg = NULL;
	dc->spacing_3d = 0;
	dc->timeout_ms = 10000;
}

void
icClearDC(dc_p dc)
{
	stackClear(&dc->linear_constrains);
	stackClear(&dc->spatial_constrains);
}

vboolean
icParseDC(vhandle_t script,dc_p dc)
{
	CALLER_NAME(icParseDC)
	int section_count = 0;
	unsigned i;
	char op[MAX_ID];
	if(!lexCopyNextToken(script,op,MAX_ID))
	{
		SYN_UNEXPECTED_EOF(script);
		return false;
	}
	if(STR_EQ(op,"{")) /* section */
	{
		section_count++;
		if(!lexCopyNextToken(script,op,MAX_ID))
		{
			SYN_UNEXPECTED_EOF(script);
			return false;
		}
	}
	do
	{
		if(section_count && STR_EQ(op,"}"))
			break;
		for(i=0;dc_ops[i].entry;i++)
		{
			if(STR_COMP(op,dc_ops[i].name))
				continue;
			if(!(*(dc_ops[i].entry))(script,dc))
			{
				SYN_EXPECTED(script,dc_ops[i].desc);
				return false;
			}
			break;
		}
		if(!dc_ops[i].entry)
		{
			ERROR_INVALID_STR("unknown DC operation",op);
			return false;
		}
	} while(section_count);
	return true;
}

vboolean
icGetOperationRange(dc_p dc,u32_p min,u32_p max)
{
	if(dc->range_min < 0 || dc->range_max < 0 
		|| dc->range_max < dc->range_min)
		return false;
	*min = (u32_t)dc->range_min;
	*max = (u32_t)dc->range_max;
	return true;
}

V_LOCAL vboolean
icIsSphereInsideSphere(vec3_t center,vec_t radius,
	vec3_t outer_center,vec_t outer_radius)
{
	vec3_t delta;
	vec3Sub(delta,center,outer_center);
	return (vec3Abs(delta) + radius) < outer_radius ? true : false;
}

V_LOCAL vboolean
icIsSphereOutsideSphere(vec3_t center,vec_t radius,
	vec3_t outer_center,vec_t outer_radius)
{
	vec3_t delta;
	vec3Sub(delta,center,outer_center);
	return (vec3Abs(delta) - radius) > outer_radius ? true : false;
}

V_LOCAL vboolean
icIsSphereInsideBox(vec3_t center,vec_t radius,
	vec3_t mins,vec3_t maxs)
{
	unsigned i;
	for(i=0;i<3;i++)
		if(mins[i] >= (center[i]-radius) || maxs[i] <= (center[i]+radius))
			return false;
	return true;
}

V_LOCAL vboolean
icIsSphereOutsideBox(vec3_t center,vec_t radius,
	vec3_t mins,vec3_t maxs)
{
	unsigned i;
	for(i=0;i<3;i++)
		if(mins[i] > (center[i]+radius) || maxs[i] < (center[i]-radius))
			return true;
	return false;
}

V_LOCAL vboolean
icIsSphereInsideCylinder(vec3_t center,vec_t radius,
	vec3_t lower_base_center,vec3_t axis_dir,vec_t cyl_radius,vec_t cyl_height)
{
	vec_t dist, axial_dist;
	vec3_t delta, prj;
	/* check if center is between lower and upper planes */
	vec3Sub(delta,center,lower_base_center);
	dist = vec3Dot(axis_dir,delta);
	if(dist < radius || dist > (cyl_height - radius))
		return false;
	/* check for distance from axis */
	vec3Scale(prj,axis_dir,dist);
	vec3Sub(prj,delta,prj);
	axial_dist = vec3Abs(prj);
	if(axial_dist > (cyl_radius - radius))
		return false;
	return true;
}

V_LOCAL vboolean
icIsSphereOutsideCylinder(vec3_t center,vec_t radius,
	vec3_t lower_base_center,vec3_t axis_dir,
	vec_t cyl_radius,vec_t cyl_height)
{
	vec_t dist, axial_dist;
	vec3_t delta, prj;
	/* check if center is outside lower and upper planes */
	vec3Sub(delta,center,lower_base_center);
	dist = vec3Dot(axis_dir,delta);
	if(dist < -radius || dist > (cyl_height + radius))
		return true;
	/* check for distance from axis */
	vec3Scale(prj,axis_dir,dist);
	vec3Sub(prj,delta,prj);
	axial_dist = vec3Abs(prj);
	if(axial_dist > (cyl_radius + radius))
		return true;
	return false;
}

V_LOCAL vboolean
icComputeGlobalRange(vec3_t mins,vec3_t maxs,dc_p dc)
{
	CALLER_NAME(icComputeGlobalRange)
	u32_t i, count, zero_count;
	linear_constrain_p ptr;
	count = stackCount(&dc->linear_constrains);
	if(!count)
		return false;
	ptr = (linear_constrain_p)stackGetStorage(&dc->linear_constrains);
	mins[0] = MAXFLOAT;
	maxs[0] = -MAXFLOAT;
	for(zero_count=i=0;i<count;i++)
	{
		if(IsZero(ptr->max - ptr->min))
		{
			zero_count++;
			if(i>1)
				WARNING_GENERIC("Skipped non-1st zero-length interval");
		}
		if(mins[0] > ptr->min)
			mins[0] = ptr->min;
		if(maxs[0] < ptr->max)
			maxs[0] = ptr->max;
		ptr++;
	}
	/*
	 * Only one _alone_ zero-len interval allowed
	 */
	if(zero_count && count>1)
	{
		ERROR_GENERIC(
			"Can't operate with more than one zero-length interval / mixed distribution.");
		mins[0] = MAXFLOAT;
		maxs[0] = -MAXFLOAT;
		return false;
	}
	return true;
}

V_LOCAL void
bboxAddSphere(vec3_t mins,vec3_t maxs,vec3_t center,vec_t radius)
{
	vec3_t sm, sx, sd;
	vec3Set(sd,radius,radius,radius);
	vec3Sub(sm,center,sd);
	vec3Add(sx,center,sd);
	bboxAddPoint(mins,maxs,sm);
	bboxAddPoint(mins,maxs,sx);
}

#define SCT_LEN	64
V_LOCAL vboolean sincostab_build = false;
V_LOCAL vec_t sincostab[SCT_LEN][2];

V_LOCAL void
bboxAddCylinder(vec3_t mins,vec3_t maxs,
	vec_p lb_center_and_dir,
	vec_t radius,vec_t height)
{
	unsigned i;
	vec3_t p1, p2, dir_delta, pt;
	vec3_t e1, e2;
	vec3_t axis_dir;
	vec3Copy(axis_dir,lb_center_and_dir+3);
	if(!sincostab_build)
	{
		vec_t angle = 0, delta = M_2PI / SCT_LEN;
		for(i=0;i<SCT_LEN;i++)
		{
			sincostab[i][0] = (vec_t)sin(angle);
			sincostab[i][1] = (vec_t)cos(angle);
			angle += delta;
		}
		sincostab_build = true;
	}
	/*
	 * Compute local basis
	 */
	vec3Perpendicular(e1,axis_dir);
	vec3Cross(e2,axis_dir,e1);
	vec3Scale(dir_delta,axis_dir,height);
	/*
	 * Compute a lot of points on upper and lower bases
	 */
	for(i=0;i<SCT_LEN;i++)
	{
		vec3Scale(p1,e1,sincostab[i][1]);
		vec3Scale(p2,e2,sincostab[i][0]);
		vec3Add(pt,p1,p2);
		vec3Scale(pt,pt,radius);
		vec3Add(pt,pt,lb_center_and_dir);
		bboxAddPoint(mins,maxs,pt);
		vec3Add(pt,pt,dir_delta);
		bboxAddPoint(mins,maxs,pt);
	}
}

V_LOCAL vboolean
icComputeGlobalBoundingBox(vec3_t mins,vec3_t maxs,dc_p dc)
{
	CALLER_NAME(icComputeGlobalBoundingBox)
	u32_t count, valid_count = 0;
	spatial_constrain_p ptr;
	count = stackCount(&dc->spatial_constrains);
	if(!count)
		return false;
	ptr = (spatial_constrain_p)stackGetStorage(&dc->spatial_constrains);
	bboxClear(mins,maxs);
	for(;count--;ptr++)
	{
		switch(ptr->type)
		{
			case plane_constrain: /* non-closed shape ! */
				continue;
			case box_constrain:
				bboxAddPoint(mins,maxs,ptr->constrain.box.mins);
				bboxAddPoint(mins,maxs,ptr->constrain.box.maxs);
				break;
			case sphere_constrain:
				bboxAddSphere(mins,maxs,ptr->constrain.sphere.center,
					ptr->constrain.sphere.radius);
				break;
			case cylinder_constrain:
				bboxAddCylinder(mins,maxs,
					ptr->constrain.cylinder.lb_center_and_dir,
					ptr->constrain.cylinder.radius,
					ptr->constrain.cylinder.height);
				break;
			default:
				ERROR_GENERIC("unknown constrain");
				return false;
		}
		valid_count++;
	}
	if(!valid_count)
	{
		ERROR_GENERIC("must be at least one closed shape");
		return false;
	}
	return true;
}

V_LOCAL vboolean
icCheckFloatConstrains(vec_t val,dc_p dc,u32_t table_pos)
{
	vec3_t accept_arg;
	u32_t count;
	linear_constrain_p ptr;
	count = stackCount(&dc->linear_constrains);
	if(!count)
		return false;
	ptr = (linear_constrain_p)stackGetStorage(&dc->linear_constrains);
	while(count--)
	{
		if(val < ptr->min || val > ptr->max)
			return false;
		ptr++;
	}
	if(dc->accept_extra)
	{
		vec3Set(accept_arg,val,0,0);
		return (*dc->accept_extra)(dc->accept_extra_arg,accept_arg,table_pos,NULL);
	}
	return true;
}

V_LOCAL vboolean
icCheckVec3Constrains(vec3_t val,dc_p dc,u32_t table_pos)
{
	CALLER_NAME(icCheckVec3Constrains)
	vec_t plane_dist, delta;
	vboolean ok;
	u32_t count;
	spatial_constrain_p ptr;
	vec_t radius = 0;
	count = stackCount(&dc->spatial_constrains);
	if(!count)
		return false;
	if(dc->accept_extra)
		ok = (*dc->accept_extra)(dc->accept_extra_arg,val,table_pos,&radius);
	else
		ok = true;
	if(!ok)
		return false;
	ptr = (spatial_constrain_p)stackGetStorage(&dc->spatial_constrains);
	for(;count--;ptr++)
	{
		delta = radius + ptr->spacing;
		switch(ptr->type)
		{
			case plane_constrain:
				plane_dist = vec3Dot(ptr->constrain.plane.normal,val) -
					ptr->constrain.plane.dist;
				if(!ptr->solid) /* must be in negative halfspace */
					plane_dist = -plane_dist;
				ok = ok && (plane_dist > delta);
				break;
			case box_constrain:
				if(ptr->solid)
					ok = ok && icIsSphereOutsideBox(val,delta,
						ptr->constrain.box.mins,
						ptr->constrain.box.maxs);
				else
					ok = ok && icIsSphereInsideBox(val,delta,
						ptr->constrain.box.mins,
						ptr->constrain.box.maxs);
				break;
			case sphere_constrain:
				if(ptr->solid)
					ok = ok && icIsSphereOutsideSphere(val,delta,
						ptr->constrain.sphere.center,
						ptr->constrain.sphere.radius);
				else
					ok = ok && icIsSphereInsideSphere(val,delta,
						ptr->constrain.sphere.center,
						ptr->constrain.sphere.radius);
				break;
			case cylinder_constrain:
				if(ptr->solid)
					ok = ok && icIsSphereOutsideCylinder(val,delta,
					ptr->constrain.cylinder.lb_center_and_dir,
					ptr->constrain.cylinder.lb_center_and_dir+3,
					ptr->constrain.cylinder.radius,
					ptr->constrain.cylinder.height);
				else
					ok = ok && icIsSphereInsideCylinder(val,delta,
					ptr->constrain.cylinder.lb_center_and_dir,
					ptr->constrain.cylinder.lb_center_and_dir+3,
					ptr->constrain.cylinder.radius,
					ptr->constrain.cylinder.height);
				break;
			default:
				ERROR_GENERIC("unknown constrain");
				return false;
		}
		if(!ok)
			return false;
	}
	return true;
}

V_LOCAL vboolean
float_uniform(dist_arg_p setup)
{
	CALLER_NAME(float_uniform)
	u32_t i;
	vec_t val;
	vec_p ptr = setup->ptr;
	u32_t count, try_count, starttime = timeTickCount();
	for(i=0;i < setup->num_elems;i++)
	{
		for(count=try_count=0;;count++,try_count++)
		{
			val = ranNextFloatRange(random_generator,setup->mins[0],setup->maxs[0]);
			if(icCheckFloatConstrains(val,setup->dc,i))
				break;
			if(count & 1024)
			{
				count = 0;
				if((timeTickCount()-starttime) >= setup->dc->timeout_ms)
				{
					conlPrintf(LOG_ERROR,
						"%s(): timeout reached after %u trials.\n",
						caller_name,try_count);
					return false;
				}
			}
		}
		INC_VPTR(ptr,setup->elem_size);
	}
	return true;
}

V_LOCAL vboolean
vec3_uniform(dist_arg_p setup)
{
	CALLER_NAME(vec3_uniform)
	u32_t i;
	vec3_t val;
	vec_p ptr = setup->ptr;
	u32_t count, try_count, starttime = timeTickCount();
	for(i=0;i < setup->num_elems;i++)
	{
		for(count=try_count=0;;count++,try_count++)
		{
			ranNextVec3RangeV(random_generator,val,setup->mins,setup->maxs);
			if(icCheckVec3Constrains(val,setup->dc,i))
				break;
			if(count & 1024)
			{
				count = 0;
				if((timeTickCount()-starttime) >= setup->dc->timeout_ms)
				{
					conlPrintf(LOG_ERROR,
						"%s(): timeout reached after %u trials.\n",
						caller_name,try_count);
					return false;
				}
			}
		}
		INC_VPTR(ptr,setup->elem_size);
	}
	return true;
}

V_LOCAL vboolean
float_file_input(dist_arg_p setup)
{
	CALLER_NAME(float_file_input)
	if(!lexNext(setup->script))
	{
		SYN_UNEXPECTED_EOF(setup->script);
		return false;
	}
	return icReadFloatFile(lexLast(setup->script),setup->ptr,
		setup->elem_size,setup->num_elems);
}

V_LOCAL vboolean
vec3_file_input(dist_arg_p setup)
{
	CALLER_NAME(vec3_file_input)
	if(!lexNext(setup->script))
	{
		SYN_UNEXPECTED_EOF(setup->script);
		return false;
	}
	return icReadVec3File(lexLast(setup->script),setup->ptr,
		setup->elem_size,setup->num_elems);
}

vboolean
icReadFloatFile(char *file_name,void_p dest,u32_t elem_size,u32_t nelem)
{
	CALLER_NAME(icReadFloatFile)
	unsigned i;
	vhandle_t infile;
	V_ASSERT(dest && elem_size && nelem);
	infile = lexParseFile(file_name,";");
	if(infile == LIBV_INVALID_HANDLE)
	{
		ERROR_INVALID_STR("unable to open file",file_name);
		return false;
	}
	for(i=0;;)
	{
		/* read next float */
		if(!lexNextFloat(infile,(vec_p)dest))
		{
			SYN_EXPECTED(infile,"float");
			break;
		}
		i++;
		if(i >= nelem)
			break;
		/* if not last, skip semicolon */
		if(!lexNextIs(infile,";"))
		{
			SYN_EXPECTED(infile,"semicolon");
			break;
		}
		INC_VPTR(dest,elem_size);
	}
	lexDestroy(infile);
	return (i >= nelem) ? true : false;
}

vboolean
icWriteFloatFile(char *file_name,void_p src,u32_t elem_size,u32_t nelem)
{
	CALLER_NAME(icWriteFloatFile)
	unsigned i;
	vhandle_t outfile = fileOpenSafeOverwrite(file_name);
	if(outfile == LIBV_INVALID_HANDLE)
	{
		ERROR_INVALID_STR("unable to open file",file_name);
		return false;
	}
	for(i=0;;)
	{
		filePrintf(outfile,"%g",(vec_p)src);
		i++;
		if(i >= nelem)
			break;
		filePuts(outfile,";\n");
		INC_VPTR(src,elem_size);
	}
	fileClose(outfile);
	return true;
}

vboolean
icReadVec3File(char *file_name,void_p dest,u32_t elem_size,u32_t nelem)
{
	CALLER_NAME(icReadVec3File)
	unsigned i;
	vec3_t val;
	vhandle_t infile;
	V_ASSERT(dest && elem_size && nelem);
	infile = lexParseFile(file_name,";");
	if(infile == LIBV_INVALID_HANDLE)
	{
		ERROR_INVALID_STR("unable to open file",file_name);
		return false;
	}
	for(i=0;;)
	{
		/* read three floats delimited by ',' */
		if(!lexNextFloat(infile,&val[0]))
		{
			SYN_EXPECTED(infile,"1st float");
			break;
		}
		if(!lexNextIs(infile,","))
		{
			SYN_EXPECTED(infile,"comma between 1st and 2nd floats");
			break;
		}
		if(!lexNextFloat(infile,&val[1]))
		{
			SYN_EXPECTED(infile,"2nd float");
			break;
		}
		if(!lexNextIs(infile,","))
		{
			SYN_EXPECTED(infile,"comma between 2nd and 3rd floats");
			break;
		}
		if(!lexNextFloat(infile,&val[2]))
		{
			SYN_EXPECTED(infile,"3rd float");
			break;
		}
		vec3Copy((vec_p)dest,val);
		i++;
		if(i >= nelem)
			break;
		/* if not last, skip semicolon */
		if(!lexNextIs(infile,";"))
		{
			SYN_EXPECTED(infile,"semicolon");
			break;
		}
		INC_VPTR(dest,elem_size);
	}
	lexDestroy(infile);
	return (i >= nelem) ? true : false;
}

vboolean
icWriteVec3File(char *file_name,void_p src,u32_t elem_size,u32_t nelem)
{
	CALLER_NAME(icWriteVec3File)
	unsigned i;
	vec3_t val;
	vhandle_t outfile = fileOpenSafeOverwrite(file_name);
	if(outfile == LIBV_INVALID_HANDLE)
	{
		ERROR_INVALID_STR("unable to open file",file_name);
		return false;
	}
	for(i=0;;)
	{
		vec3Copy(val,(vec_p)src);
		filePrintf(outfile,"%g,%g,%g",val[0],val[1],val[2]);
		i++;
		if(i >= nelem)
			break;
		filePuts(outfile,";\n");
		INC_VPTR(src,elem_size);
	}
	fileClose(outfile);
	return true;
}

typedef struct
{
	char *name;
	vboolean (*entry)(dist_arg_p setup);
	vboolean requires_constrains;
	char *desc;
}	dist_entry_p;

V_LOCAL dist_entry_p float_dists[]=
{
	{"uniform",float_uniform,true,"1D uniform distribution"},
	{"file",float_file_input,false,"string file_name: 1D file input"},
	{NULL,0,false,NULL}
};

V_LOCAL dist_entry_p vec3_dists[]=
{
	{"uniform",vec3_uniform,true,"3D uniform distribution"},
	{"file",vec3_file_input,false,"string file_name: 3D file input"},
	{NULL,0,false,NULL}
};

V_LOCAL void
icHelpTables(void)
{
	unsigned i;
	conPuts("Known float table fillers:\n");
	for(i=0;float_dists[i].entry;i++)
		conPrintf("%s\t - %s.\n",float_dists[i].name,float_dists[i].desc);
	conPuts("Known vec3 table fillers:\n");
	for(i=0;vec3_dists[i].entry;i++)
		conPrintf("%s\t - %s.\n",vec3_dists[i].name,vec3_dists[i].desc);
}

V_LOCAL vboolean
icFloatDistribute(vec_p ptr,vec_p endptr,u32_t elem_size,dc_p dc,
	vhandle_t script)
{
	unsigned i;
	vec3_t mins, maxs;
	CALLER_NAME(icFloatDistribute)
	dist_arg_t setup;
	for(i=0;float_dists[i].entry;i++)
	{
		if(!lexLastWasEx(script,float_dists[i].name,STR_COMP))
			continue;
		if(float_dists[i].requires_constrains)
		{
			if(!icComputeGlobalRange(mins,maxs,dc))
			{
				ERROR_GENERIC("unable to compute global range");
				return false;
			}
			vec3Copy(setup.mins,mins);
			vec3Copy(setup.maxs,maxs);
			setup.dc = dc;
		}
		else
		{
			vec3Zero(setup.mins);
			vec3Zero(setup.maxs);
			setup.dc = NULL;
		}
		setup.ptr = ptr;
		setup.endptr = endptr;
		setup.elem_size = elem_size;
		setup.num_elems = 1 + ((unsigned)endptr - (unsigned)ptr) / elem_size;
		setup.script = script;
		return (*(float_dists[i].entry))(&setup);
	}
	ERROR_INVALID_STR("unknown float distribution",lexLast(script));
	return false;
}

V_LOCAL vboolean
icVec3Distribute(vec_p ptr,vec_p endptr,u32_t elem_size,dc_p dc,
	vhandle_t script)
{
	unsigned i;
	vec3_t mins, maxs;
	CALLER_NAME(icVec3Distribute)
	dist_arg_t setup;
	for(i=0;vec3_dists[i].entry;i++)
	{
		if(!lexLastWasEx(script,vec3_dists[i].name,STR_COMP))
			continue;
		if(vec3_dists[i].requires_constrains)
		{
			if(!icComputeGlobalBoundingBox(mins,maxs,dc))
			{
				ERROR_GENERIC("unable to compute global range");
				return false;
			}
			vec3Copy(setup.mins,mins);
			vec3Copy(setup.maxs,maxs);
			setup.dc = dc;
		}
		else
		{
			vec3Zero(setup.mins);
			vec3Zero(setup.maxs);
			setup.dc = NULL;
		}
		setup.ptr = ptr;
		setup.endptr = endptr;
		setup.elem_size = elem_size;
		setup.num_elems = 1 + ((unsigned)endptr - (unsigned)ptr) / elem_size;
		setup.script = script;
		return (*(vec3_dists[i].entry))(&setup);
	}
	ERROR_INVALID_STR("unknown vec3 distribution",lexLast(script));
	return false;
}

vboolean
icParseFloatTable(void_p dest,u32_t elem_size,u32_t num_elems_override,
	dc_p dc,vhandle_t script)
{
	CALLER_NAME(icParseFloatTable)
	vec_t val;
	static char *table_delim = "[;]";
	char old_delim[256];
	u32_t min, max;
	vec_p ptr, endptr;
	if(num_elems_override)
	{
		min = 0;
		max = num_elems_override - 1;
	}
	else
	{
		if(!dc || !icGetOperationRange(dc,&min,&max))
		{
			ERROR_GENERIC("wrong DC for num_elems_override==0");
			return false;
		}
	}
	ptr = (vec_p)((u8_p)dest + min * elem_size);
	endptr = (vec_p)((u8_p)dest + max * elem_size);
	strCopy(old_delim,lexGetString(script,LEX_DELIMITER_STRING),256);
	lexSetString(script,LEX_DELIMITER_STRING,table_delim);
	if(!lexNextIs(script,"["))
	{
		SYN_EXPECTED(script,"[");
		lexSetString(script,LEX_DELIMITER_STRING,old_delim);
		return false;
	}
	if(!lexNextFloat(script,NULL)) /* name of distrubution ? */
	{
		vboolean result = icFloatDistribute(ptr,endptr,elem_size,dc,script);
		lexSetString(script,LEX_DELIMITER_STRING,old_delim);
		return result;
	}
	for(;;)
	{
		/* get next float */
		if(!lexLastFloat(script,&val))
		{
			SYN_EXPECTED(script,"float");
			break;
		}
		*ptr = val;
		ptr = (vec_p)((u8_p)ptr + elem_size);
		if(ptr > endptr)
			break;
		if(!lexNextIs(script,";"))
		{
			SYN_EXPECTED(script,";");
			break;
		}
		lexNext(script);
	}
	lexSetString(script,LEX_DELIMITER_STRING,old_delim);
	if(ptr > endptr)
	{
		if(lexNextIs(script,"]"))
			return true;
		SYN_EXPECTED(script,"]");
	}
	return false;
}

vboolean
icParseVec3Table(void_p dest,u32_t elem_size,u32_t num_elems_override,
	dc_p dc,vhandle_t script)
{
	CALLER_NAME(icParseVec3Table)
	vec3_t val;
	static char *table_delim = "[;,]";
	char old_delim[256];
	u32_t min, max;
	vec_p ptr, endptr;
	if(num_elems_override)
	{
		min = 0;
		max = num_elems_override - 1;
	}
	else
	{
		if(!dc || !icGetOperationRange(dc,&min,&max))
		{
			ERROR_GENERIC("wrong DC for num_elems_override==0");
			return false;
		}
	}
	ptr = (vec_p)((u8_p)dest + min * elem_size);
	endptr = (vec_p)((u8_p)dest + max * elem_size);
	strCopy(old_delim,lexGetString(script,LEX_DELIMITER_STRING),256);
	lexSetString(script,LEX_DELIMITER_STRING,table_delim);
	if(!lexNextIs(script,"["))
	{
		SYN_EXPECTED(script,"[");
		lexSetString(script,LEX_DELIMITER_STRING,old_delim);
		return false;
	}
	if(!lexNextFloat(script,NULL)) /* name of distrubution ? */
	{
		vboolean result = icVec3Distribute(ptr,endptr,elem_size,dc,script);
		lexSetString(script,LEX_DELIMITER_STRING,old_delim);
		return result;
	}
	for(;;)
	{
		/* get three floats delimited by ',' */
		if(!lexLastFloat(script,&val[0]))
		{
			SYN_EXPECTED(script,"1st float");
			break;
		}
		if(!lexNextIs(script,","))
		{
			SYN_EXPECTED(script,"comma between 1st and 2nd floats");
			break;
		}
		if(!lexNextFloat(script,&val[1]))
		{
			SYN_EXPECTED(script,"2nd float");
			break;
		}
		if(!lexNextIs(script,","))
		{
			SYN_EXPECTED(script,"comma between 2nd and 3rd floats");
			break;
		}
		if(!lexNextFloat(script,&val[2]))
		{
			SYN_EXPECTED(script,"3rd float");
			break;
		}
		vec3Copy(ptr,val);
		ptr = (vec_p)((u8_p)ptr + elem_size);
		if(ptr > endptr)
			break;
		if(!lexNextIs(script,";"))
		{
			SYN_EXPECTED(script,";");
			break;
		}
		lexNext(script);
	}
	lexSetString(script,LEX_DELIMITER_STRING,old_delim);
	if(ptr > endptr)
	{
		if(lexNextIs(script,"]"))
			return true;
		SYN_EXPECTED(script,"]");
	}
	return false;
}

void
icHelp(void)
{
	icHelpDC();
	icHelpTables();
}
