#include <v_lib.h>
#include "draw.h"
#include "ms_wr.h"
#include "monitor.h"
#include "psys.h"
#include "util.h"
#include "iset.h"
#include "phasesp.h"

#define	PRINT_FLOAT(out,val)	writeVec(out,val,DOT_CHAR)

V_LOCAL void
monitorProgress(particle_system_p ps)
{
	printf("Current time: %g, real time: %g.\n",ps->cur_time,timerTime(ps->rt_timer));
}

/*
 * Writes average and standard deviation of "len" samples "X" to "out".
 */
V_LOCAL void
writeAvgAndStdev(vhandle_t out,vec_p X,u32_t len)
{
	vec_t av, std_dev, deviation;
	vec_p ptr, end = X + len;
	av = std_dev = 0;
	for(ptr = X;ptr < end;ptr++)
		av += *ptr;
	av /= (vec_t)len;
	for(ptr = X;ptr < end;ptr++)
	{
		deviation = *ptr - av;
		std_dev += deviation * deviation;
	}
	std_dev = sqrt(std_dev / (vec_t)(len - 1));
	PRINT_FLOAT(out, av);
	fileWrite(out,CSV_COMMA,1);
	PRINT_FLOAT(out, std_dev);
}

V_LOCAL void
move_mins_to_start_cols(matrix_p src,u32_t num_nearest)
{
	vec_t temp;
	u32_t r, c, k, idx;
	for(r = 0;r < src->rows;r++)
	{
		for(k = 0;k < num_nearest;k++)
		{
			idx = k;
			for(c = k + 1;c < src->cols;c++)
				if(src->cell[r][c] < src->cell[r][idx])
					idx = c;
			temp = src->cell[r][idx];
			src->cell[r][idx] = src->cell[r][k];
			src->cell[r][k] = temp;
		}
	}
}

/*
 * Writes average and standard deviation of distances (between surfaces)
 * from particles to ps->num_nearest their closest neighbors.
 */
V_LOCAL void
monitorWriteRnStats(particle_system_p ps)
{
	vec_t s;
	u32_t i, j;
	vec_p sums = ps->sums;
	matrix_p dists = ps->mon_dist;
	matCopy(dists, ps->surf_dist);
	/* move ps->num_nearest minimal values to start columns */
	move_mins_to_start_cols(dists, ps->num_nearest);
	for(i = 0;i < ps->num_particles;i++)
	{
		s = 0;
		for(j = 0;j < ps->num_nearest;j++)
			s += dists->cell[i][j];
		s /= ps->num_nearest;
		sums[i] = s;
	}
	writeAvgAndStdev(ps->kinetics_outfile,sums,ps->num_particles);
}

V_LOCAL void
connmatFill(particle_system_p ps)
{
	matrix_p dist = ps->surf_dist;
	u32_t i, j;
	vec_t d1;
	for(i = 0;i < ps->num_particles - 1;i++)
	{
		d1 = ps->percolation_distance[i];
		for(j = i + 1;j < ps->num_particles;j++)
		{
			if(dist->cell[i][j] <= (d1 + ps->percolation_distance[j]))
				ps->connmatrix[i][j] = 1;
			else
				ps->connmatrix[i][j] = 0;
			ps->connmatrix[j][i] = ps->connmatrix[i][j];
		}
	}
}

/*
 * Writes ps->connmat in graphviz format.
 * Uses ps->connmat, which is initialized only if ps->percolation.
 */
V_LOCAL void
connmatDumpGraph(particle_system_p ps, vboolean print_isolated)
{
	CALLER_NAME(connmatDumpGraph)
	u32_t i, j, num_particles = ps->num_particles;
	vhandle_t out;
	if(!ps->percolation)
	{
		conlPrintf(LOG_ERROR,"%s(): ps->percolation must be true.\n",caller_name);
		return;
	}
	if(!print_isolated)
	{
		vboolean no_clusters = true;
		for(i = 0;i < num_particles && no_clusters;i++)
		{
			for(j = 0;j < num_particles;j++)
			{
				if(ps->connmatrix[i][j])
				{
					no_clusters = false;
					break;
				}
			}
		}
		if(no_clusters)
			return;
	}
	logFormatString(ps->tmpname,"%s/connmat%04u.dot",ps->outdir,++ps->connmatrix_dump_count);
	out = fileOpenWrite(ps->tmpname);
	filePrintf(out, "graph connmat {\n\tlayout=fdp\n\tnode [shape=ellipse]\n\tlabelloc=\"t\"\n\t");
    filePrintf(out, "label=\"Connectivity matrix, time = %g\"\n", ps->cur_time);
	if(print_isolated)
	{
		for(i = 0;i < num_particles;i++)
			filePrintf(out, "\t%u [label=\"P%04u\"]\n", i, i);
	}
	else
	{
		for(i = 0;i < num_particles;i++)
		{
			for(j = 0;j < num_particles;j++)
			{
				if(ps->connmatrix[i][j])
				{
					filePrintf(out, "\t%u [label=\"P%04u\"]\n", i, i);
					break;
				}
			}
		}
	}
	for(i = 0;i < num_particles - 1;i++)
	{
		for(j = i;j < num_particles;j++)
		{
			if(ps->connmatrix[i][j])
				filePrintf(out, "\t%u -- %u\n", i, j);
		}
	}
	filePrintf(out, "}\n");
	fileClose(out);
}

V_LOCAL void
monitorWriteConnmatrix(particle_system_p ps)
{
	connmatDumpGraph(ps, false);
}

/*
 * Writes average and standard deviation of number of neighbors
 * that are closer (from surface) than ps->percolation_distance[i].
 * Uses ps->connmat, which is initialized only if ps->percolation.
 */
V_LOCAL void
monitorWriteNnStats(particle_system_p ps)
{
	CALLER_NAME(monitorWriteNnStats)
	u32_t i, j, count;
	vec_p sums = ps->sums;
	if(!ps->percolation)
	{
		conlPrintf(LOG_ERROR,"%s(): ps->percolation must be true.\n",caller_name);
		return;
	}
	for(i = 0;i < ps->num_particles;i++)
	{
		for(count = j = 0;j < ps->num_particles;j++)
		{
			if(ps->connmatrix[i][j])
				count++;
		}
		sums[i] = (vec_t)count;
	}
	writeAvgAndStdev(ps->kinetics_outfile,sums,ps->num_particles);
}

/*
 * Writes average and standard deviation of modulus of velocity.
 */
V_LOCAL void
monitorWriteVelStats(particle_system_p ps)
{
	u32_t i;
	vec_p vel;
	vec_p sums = ps->sums;
	for(i = 0;i < ps->num_particles;i++)
	{
		vel = PS_VEL(ps->cur_phase,i);
		sums[i] = sqrt(vec3Dot(vel, vel));
	}
	writeAvgAndStdev(ps->kinetics_outfile,sums,ps->num_particles);
}

/*
 * Writes average and standard deviation of kinetic energy.
 */
V_LOCAL void
monitorWriteTStats(particle_system_p ps)
{
	u32_t i;
	vec_p vel;
	vec_p sums = ps->sums;
	for(i = 0;i < ps->num_particles;i++)
	{
		vel = PS_VEL(ps->cur_phase,i);
		sums[i] = ps->particle_mass[i] * vec3Dot(vel, vel);
	}
	writeAvgAndStdev(ps->kinetics_outfile,sums,ps->num_particles);
}

/*
 * Writes average and standard deviation of potential energy.
 */
V_LOCAL void
monitorWriteUStats(particle_system_p ps)
{
	vec_p sums = ps->sums;
	vec_t dist, energy;
	float_controller_entry_t get_energy = ps->binary_interaction->energy;
	u32_t i, j;
	if(!get_energy)
		return;
	for(i = 0;i < ps->num_particles;i++)
	{
		energy = 0;
		for(j = 0;j < ps->num_particles;j++)
		{
			if(i == j)
				continue;
			dist = ps->surf_dist->cell[i][j];
			if(IsZero(dist))
				dist = ps->surf_dist->cell[j][i];
			energy += (get_energy)(ps->binary_interaction, dist);
		}
		sums[i] = energy;
	}
	writeAvgAndStdev(ps->kinetics_outfile,sums,ps->num_particles);
}

/*
 * Writes average and standard deviation of particles' shell intersection area.
 * Uses ps->contact[][] that is calculated for ps->dglue[] in case of
 * tangent_forces == true.
 */
V_LOCAL void
monitorWriteShellIntersectionStats(particle_system_p ps)
{
	u32_t i, j;
	vec_t area;
	vec_p sums = ps->sums;
	if(!ps->tangent_forces)
	{
		filePrintf(ps->kinetics_outfile,"0%s0",CSV_COMMA);
		return;
	}
	for(i=0;i < ps->num_particles;i++)
	{
		area = 0;
		for(j = 0;j < ps->num_particles;j++)
		{
			if(i == j)
				continue;
			area += ps->contact->cell[i][j];
		}
		sums[i] = area / (4 * M_PI * ps->particle_radius[i]); /* FIXME WTF */
	}
	writeAvgAndStdev(ps->kinetics_outfile,sums,ps->num_particles);
}

/*
 * Writes average and standard deviation of one of the coordinates.
 */
V_LOCAL void
monitorWriteCoordStats(particle_system_p ps, u32_t nc)
{
	u32_t i;
	vec_p sums = ps->sums;
	V_ASSERT(nc < 3);
	for(i = 0;i < ps->num_particles;i++)
	{
		sums[i] = PS_POS(ps->cur_phase,i)[nc];
	}
	writeAvgAndStdev(ps->kinetics_outfile,sums,ps->num_particles);
}

V_LOCAL void
monitorWriteXStats(particle_system_p ps)
{
	monitorWriteCoordStats(ps, 0);
}

V_LOCAL void
monitorWriteYStats(particle_system_p ps)
{
	monitorWriteCoordStats(ps, 1);
}

V_LOCAL void
monitorWriteZStats(particle_system_p ps)
{
	monitorWriteCoordStats(ps, 2);
}

V_LOCAL vboolean
pt_inside(vec_p pt,vec3_t mins,vec3_t maxs)
{
	int i = 2;
	do {
		if(pt[i] < mins[i] || pt[i] > maxs[i])
			return false;
	} while (i--);
	return true;
}

/*
 * Writes average and standard deviation of number of particles in cuboidal
 * subdomains of cuboidal hull of all particles. Number of subromains are
 * controlled by ps->cuboid_subdiv[0...2].
 */
V_LOCAL void
monitorWriteNSStats(particle_system_p ps)
{
	vec3_t mins, maxs, ext, dim, p1, p2;
	unsigned i, j, k, l, m, count;
	vec_p phase = ps->cur_phase;
	unsigned maxc = ps->cuboid_subdiv[0] * ps->cuboid_subdiv[1] * ps->cuboid_subdiv[2];
	vec_p sums = (maxc > ps->num_particles) ? memAllocVecs(maxc) : ps->sums;
	bboxClear(mins,maxs);
	for(i = 0;i < ps->num_particles;i++)
		bboxAddPoint(mins,maxs,PS_POS(phase,i));
	vec3Sub(ext,maxs,mins);
	dim[0] = ext[0] / ps->cuboid_subdiv[0];
	dim[1] = ext[1] / ps->cuboid_subdiv[1];
	dim[2] = ext[2] / ps->cuboid_subdiv[2];
	count = 0;
	for(i = 0;i < ps->cuboid_subdiv[0];i++)
	{
		for(j = 0;j < ps->cuboid_subdiv[1];j++)
		{
			for(k = 0;k < ps->cuboid_subdiv[2];k++)
			{
				p1[0] = mins[0] + dim[0] * i;
				p1[1] = mins[1] + dim[1] * j;
				p1[2] = mins[2] + dim[2] * k;
				vec3Add(p2,p1,dim);
				m = 0;
				for(l = 0;l < ps->num_particles;l++)
				{
					if(pt_inside(PS_POS(phase,l),p1,p2))
						m++;
				}
				sums[count] = m;
				count++;
			}
		}
	}
	writeAvgAndStdev(ps->kinetics_outfile,sums,maxc);
	if(sums != ps->sums)
		memFree(sums);
}

V_LOCAL void
clustersClear(particle_system_p ps)
{
	ISET_LIST_TYPE i;
	ps->num_clusters = 0;
	isetClear(ps->isolated);
	isetsClear(ps->clusters, ps->num_particles);
	for(i = 0;i < ps->num_particles;i++)
		isetAdd(ps->isolated, i);
	isetUpdateList(ps->isolated);
}

V_LOCAL void
addParticleToCluster_r(u8_p *connmat,iset_p dst,iset_p src,u32_t particle_no)
{
	ISET_LIST_TYPE *src_copy;
	ISET_LIST_TYPE src_len, i;
	isetAdd(dst, particle_no);
	isetDel(src, particle_no);
	isetUpdateList(src);
	src_len = src->num_elems;
	if(!src_len)
		return;
	/* make a copy of src->list */
	src_copy = (ISET_LIST_TYPE*)memAlloc(src_len * sizeof(ISET_LIST_TYPE));
	memCopy(src_copy, src->list, src_len * sizeof(ISET_LIST_TYPE));
	/* find all particles in 'src' which are connected to the just added particle */
	for(i = 0;i < src_len;i++)
		if(connmat[particle_no][src_copy[i]])
			addParticleToCluster_r(connmat,dst,src,src_copy[i]);
	memFree(src_copy);
}

V_LOCAL void
clustersFill(particle_system_p ps)
{
	CALLER_NAME(clustersFill)
	ISET_LIST_TYPE next_no;
	if(!ps->percolation)
	{
		conlPrintf(LOG_ERROR,"%s(): ps->percolation must be true.\n",caller_name);
		return;
	}
	for(ps->num_clusters = 0;isetGetNext(ps->isolated,&next_no);ps->num_clusters++)
	{
		addParticleToCluster_r(ps->connmatrix,ps->clusters[ps->num_clusters],ps->isolated,next_no);
		isetUpdateList(ps->clusters[ps->num_clusters]);
	}
}

V_LOCAL void
clustersDump(particle_system_p ps)
{
	CALLER_NAME(clustersDump)
	ISET_LIST_TYPE i, j, count;
	/*
	SET_LIST_TYPE check = 0;
	 */
	if(!ps->percolation)
	{
		conlPrintf(LOG_ERROR,"%s(): ps->percolation must be true.\n",caller_name);
		return;
	}
	PRINT_FLOAT(ps->clusterdump_outfile,ps->cur_time);
	for(i = 1;i <= ps->num_particles;i++)
	{
		/* count clusters with i particles */
		for(count = j = 0;j < ps->num_clusters;j++)
			if(ps->clusters[j]->num_elems == i)
				count++;
		filePrintf(ps->clusterdump_outfile,"%s%u", CSV_COMMA, count);
		/* check += i * count; */
	}
	fileWrite(ps->clusterdump_outfile,"\n",1);
	fileControl(ps->clusterdump_outfile,pctl_flush,NULL);
	/* conPrintf("%s(): check %u\n", caller_name, check); */
}

V_LOCAL void
monitorClusterStats(particle_system_p ps)
{
	if(!ps->clusters_updated)
	{
		clustersClear(ps);
		clustersFill(ps);
		ps->clusters_updated = true;
	}
	clustersDump(ps);
}

/*
 * Outputs maximal number of clustered particles
 */
V_LOCAL void
monitorMaxCluster(particle_system_p ps)
{
	u32_t i, max = 0;
	if(!ps->clusters_updated)
	{
		clustersClear(ps);
		clustersFill(ps);
		ps->clusters_updated = true;
	}
	for(max = ps->num_particles - 1;max > 1;max--)
	{
		for(i = 0;i < ps->num_clusters;i++)
		{
			/* find at least one cluster with "max" elements  */
			if(ps->clusters[i]->num_elems == max)
				goto outer_break;
		}
		if(i == ps->num_clusters)
			continue;
		break;
	}
outer_break:;
	filePrintf(ps->kinetics_outfile,"%u%s0",max,CSV_COMMA);
}

V_LOCAL void
monitorSaveView(particle_system_p ps)
{
	ps->make_snapshot = true;
}

V_LOCAL monroutine_t all_monroutines[] = {
	{
		"Prints current time to stdout",
		"progress",
		NULL,
		monitorProgress
	},
	{
		"Writes average and standard deviation of distances (between surfaces) from particles to ps->num_nearest their closest neighbors. Uses num_nearest (>=2) variable which can be defined in \"goal\" section.",
		"distance_to_neighbors",
		"DN",
		monitorWriteRnStats
	},
	{
		"Writes average and standard deviation of number of neighbors that are closer (from surface) than ps->percolation_distance[]",
		"number_of_percolating_neighbors",
		"Np",
		monitorWriteNnStats
	},
	{
		"Writes average and standard deviation of modulus of velocity",
		"velocity_modulus",
		"V",
		monitorWriteVelStats
	},
	{
		"Writes average and standard deviation of kinetic energy",
		"kinetic_energy",
		"T",
		monitorWriteTStats
	},
	{
		"Writes average and standard deviation of potential energy (only pairwise forces are taken into account)",
		"potential_energy",
		"U",
		monitorWriteUStats
	},
	{
		"Writes average and standard deviation of particles' shell intersection area; uses ps->contact[][] that is calculated for ps->dglue[] in case of tangent_forces == true",
		"shell_intersection_area",
		"SIA",
		monitorWriteShellIntersectionStats
	},
	{
		"Writes average and standard deviation of X coordinate",
		"x_coordinate",
		"X",
		monitorWriteXStats
	},
	{
		"Writes average and standard deviation of Y coordinate",
		"y_coordinate",
		"Y",
		monitorWriteYStats
	},
	{
		"Writes average and standard deviation of Z coordinate",
		"z_coordinate",
		"Z",
		monitorWriteZStats
	},
	{
		"Writes average and standard deviation of number of particles in cuboidal subdomains of cuboidal hull of all particles. Number of subromains are controlled by ps->cuboid_subdiv[0...2]",
		"number_in_subdomains",
		"NS",
		monitorWriteNSStats
	},
	{
		"Writes connection matrix in graphviz format",
		"write_connmatrix",
		NULL,
		monitorWriteConnmatrix
	},
	{
		"Writes cluster statistics in clusterdump.csv file. Format: time;N_0;N_1;...;N_n, where N_i, i=\\overline{1,n} -- number of clusters with i particles",
		"write_cluster_sizes",
		NULL,
		monitorClusterStats
	},
	{
		"Outputs maximal number of clustered particles",
		"largest_cluster",
		"L",
		monitorMaxCluster
	},
	{
		"Writes positions of particles in MaxScript file",
		"write_maxscript",
		NULL,
		msSaveFrame
	},
	{
		"Writes OGL framebuffer to raster image",
		"shot_periodic",
		NULL,
		monitorSaveView
	},
	{NULL,NULL,NULL,NULL}
};

void listMonitorRoutines()
{
	u32_t i;
	conPuts("Definition of monitor routine:\n\tmonitor <monitor_name>\n");
	conPuts("Valid values for monitor_name are:\n");
	for(i = 0;NULL != all_monroutines[i].name;i++)
		printf("\t\"%s\": %s\n", all_monroutines[i].name, all_monroutines[i].desc);
}

monroutine_entry_t
monitorEntryForName(char *name)
{
	CALLER_NAME(monitorEntryForName)
	u32_t i;
	for(i = 0;NULL != all_monroutines[i].name;i++)
		if(!strcmp(all_monroutines[i].name, name))
			return all_monroutines[i].entry;
	return NULL;
}

V_LOCAL char_p
monitorDenotationForEntry(void_p entry)
{
	u32_t i;
	void_p ptr;
	for(i = 0;NULL != all_monroutines[i].name;i++)
	{
		FUNC2PTR(ptr,all_monroutines[i].entry);
		if(entry == ptr)
			return all_monroutines[i].denotation;
	}
	return NULL;
}

V_LOCAL vboolean
haveMonroutine(particle_system_p ps, monroutine_entry_t monroutine)
{
	u32_t i;
	monroutine_entry_t entry;
	for(i = 0;i < vpsCount(&ps->monroutines);i++)
	{
		PTR2FUNC(entry,vpsGet(&ps->monroutines, i));
		if(entry == monroutine)
			return true;
	}
	return false;
}

V_LOCAL vboolean
monitorMain(particle_system_p ps)
{
	char_p denotation;
	void_p monroutine;
	monroutine_entry_t entry;
	u32_t i, num_monroutines = vpsCount(&ps->monroutines);
	vboolean leave = ps->nonview_skip_count ? true : false;
	if(!num_monroutines)
		return true;
	ps->nonview_skip_count = (ps->nonview_skip_count + 1) % (ps->nonview_skips + 1);
	if(leave)
			return true;
	if(ps->percolation)
		connmatFill(ps);
	if(LIBV_INVALID_HANDLE != ps->kinetics_outfile)
	{
		PRINT_FLOAT(ps->kinetics_outfile, ps->cur_time);
	}
	ps->clusters_updated = false;
	for(i = 0;i < num_monroutines;i++)
	{
		monroutine = vpsGet(&ps->monroutines, i);
		denotation = monitorDenotationForEntry(monroutine);
		if(denotation && denotation[0])
			fileWrite(ps->kinetics_outfile,CSV_COMMA,1);
		PTR2FUNC(entry,monroutine);
		(entry)(ps);
	}
	if(LIBV_INVALID_HANDLE != ps->kinetics_outfile)
	{
		fileWrite(ps->kinetics_outfile,"\n",1);
		fileControl(ps->kinetics_outfile,pctl_flush,NULL);
	}
	return true;
}

/*
 * Callback function to call from solver
 */
vboolean V_CALL
odeMonitor(void_p arg,vec_t t,vec_p phase)
{
	CALLER_NAME(ode_monitor)
	vec_t rt;
	vboolean retval = true;
	particle_system_p ps = (particle_system_p)arg;
	ps->cur_time = t;
	ps->cur_phase = phase;
	if(ps->draw_monitor && ps->draw_context)
		retval = (ps->draw_monitor)(ps);
	else
		retval = true;
	retval = retval && monitorMain(ps); /* FIXME: all monitors should return booleans */
	if(retval)
	{
		rt = timerTime(ps->rt_timer);
		if(t >= ps->solution_time_limit)
		{
			conPrintf("%s(): time limit (%g) reached "
				"(realtime %g).\n",caller_name,ps->solution_time_limit,rt);
			retval = false;
		}
		else if(rt >= ps->solution_realtime_limit)
		{
			conPrintf("%s(): realtime limit (%g) reached "
				"(time %g).\n",caller_name,ps->solution_realtime_limit,t);
			retval = false;
		}
	}
	return retval;
}

vboolean
monitorInit(particle_system_p ps)
{
	u32_t i, num_monroutines, num_writeroutines;
	char_p denotation;
	if(!ps)
		return false;
	num_monroutines = vpsCount(&ps->monroutines);
	if(!num_monroutines)
		return true;
	for(num_writeroutines = i = 0;i < num_monroutines;i++)
	{
		denotation = monitorDenotationForEntry(vpsGet(&ps->monroutines, i));
		if(denotation && denotation[0])
			num_writeroutines++;
	}
	if(num_writeroutines)
	{
		logFormatString(ps->tmpname,"%s/kinetics.csv",ps->outdir);
		ps->kinetics_outfile = fileOpenWrite(ps->tmpname);
		if(LIBV_INVALID_HANDLE == ps->kinetics_outfile)
			return false;
		/*
		 * Print header to ps->kinetics_outfile
		 */
		filePrintf(ps->kinetics_outfile, "Time");
		for(i = 0;i < num_monroutines;i++)
		{
			denotation = monitorDenotationForEntry(vpsGet(&ps->monroutines, i));
			if(denotation && denotation[0])
				filePrintf(ps->kinetics_outfile, "%s%s_av%s%s_std", CSV_COMMA, denotation, CSV_COMMA, denotation);
		}
		fileWrite(ps->kinetics_outfile,"\n",1);
	}
	if(haveMonroutine(ps,monitorClusterStats))
	{
#ifdef	STATIC_CLUSTERS
		assert(ps->num_particles <= MAX_PARTICLES);
#endif
		logFormatString(ps->tmpname,"%s/clusterdump.csv",ps->outdir);
		ps->clusterdump_outfile = fileOpenWrite(ps->tmpname);
	}
	return true;
}

void
monitorShutdown(particle_system_p ps)
{
	if(!ps)
		return;
	if(ps->draw_cleanup)
		ps->draw_cleanup(ps);
	ps->draw_monitor = NULL;
	ps->draw_cleanup = NULL;
	ps->draw_context = NULL;
	if(LIBV_INVALID_HANDLE != ps->kinetics_outfile)
	{
		fileClose(ps->kinetics_outfile);
		ps->kinetics_outfile = LIBV_INVALID_HANDLE;
	}
	if(LIBV_INVALID_HANDLE != ps->clusterdump_outfile)
	{
		fileClose(ps->clusterdump_outfile);
		ps->clusterdump_outfile = LIBV_INVALID_HANDLE;
	}
}
