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

/*
 * -----------------------------------------------------------------------
 * MAXSCRIPT output stuff
 */

#define MAX_SCENE_SIZE  200

typedef struct ms_output_s
{
	u32_t       framenum;
	vboolean    noflush;
	vhandle_t   outfile;
}   ms_output_t, *ms_output_p;

/*
 * The routine writes code of the function 'CreateAllSpheres()'.
 * This function must be called at the scene creation time to
 * add 'nptr' GeoSphere primitives to MAX scene.
 */
V_LOCAL void
ms_create_spheres(vhandle_t out, /* script file */
	particle_system_p ps, /* data source */
	vec_t scale /* zoom factor from phase to 3DS MAX script */)
{
	u32_t i, segs;
	u32_t npts = ps->num_particles;
	vec_p psize = ps->particle_radius;
	vec_p phase = ps->phase;
	vec_t rmin, dr;
	char buf[512];
	filePrintf(out,
		"fn CreateAllSpheres =\n"
		"(\n"
		"\tlocal SceneSpheres = #();\n");
	rmin = dr = psize[0];
	for(i=1;i<npts;i++)
	{
		if(rmin > psize[i])
			rmin = psize[i];
		else if(dr < psize[i])
			dr = psize[i];
	}
	rmin *= scale;
	dr *= scale;
	dr -= rmin;
	for(i=0;i<npts;i++)
	{
		vec_t radius, t;
		vec3_t pos, rgbv, hsv;
		color3b_t rgb;
		vec3Copy(pos,PS_POS(phase,i));
		vec3Scale(pos,pos,scale);
		radius = psize[i] * scale;
		for(segs = 1;segs < 10;segs++)
		{
			if(radius < (1 << segs))
				break;
		}
		t = (IsZero(dr)) ? (vec_t)0.5 : (radius - rmin) / dr;
		vec3Set(hsv,t * 290,1,1);
		rgbFromHsv(rgbv,hsv);
		rgbByteFromFloat(rgb,rgbv);
		sprintf(buf,"\tappend SceneSpheres "
			"(GeoSphere pos:[%g,%g,%g] radius:%g segs:%u"
			" wirecolor:(color %d %d %d));\n",
			pos[0],pos[1],pos[2],radius,(unsigned int)segs,
			rgb[0],rgb[1],rgb[2]);
		filePrintf(out,buf);
	}
	filePrintf(out,"\tfor i in SceneSpheres do\n"
		"\t\ti.mapCoords = true;\n");
	filePrintf(out,"\treturn SceneSpheres;\n)\n");
}

V_LOCAL char *AnimateSpheres_body =
	"fn AnimateSpheres SceneSpheres SpherePositions FramesPerPos =\n"
	"(\n"
	"\tlocal i, j;\n"
	"\tlocal npos = SpherePositions.count;\n"
	"\tAnimationRange = (interval 0 FramesPerPos * npos)\n"
	"\tanimate on\n"
	"\t(\n"
	"\t\tfor i=1 to npos do\n"
	"\t\t(\n"
	"\t\t\tat time (i * FramesPerPos)\n"
	"\t\t\t(\n"
	"\t\t\t\tfor j=1 to SceneSpheres.count do\n"
	"\t\t\t\t\tSceneSpheres[j].position = SpherePositions[i][j];\n"
	"\t\t\t)\n"
	"\t\t)\n"
	"\t)\n"
	")\n\n";

V_LOCAL void
ms_write_helpers(vhandle_t out)
{
	filePuts(out,AnimateSpheres_body);
}

V_LOCAL void
ms_write_init(vhandle_t out)
{
	filePuts(out,"select $*\nmax delete\n\n");
	filePuts(out,"SceneSpheres = #()\nSpherePositions = #()\n\n");
	ms_write_helpers(out);
}

V_LOCAL void
ms_append_frame(vhandle_t out,particle_system_p ps)
{
	vec_p pos;
	u32_t i, npts = ps->num_particles;
	vec_p phase = ps->cur_phase;
	filePuts(out,"append SpherePositions #(\n");
	npts -= 1; /* min npts == 1 */
	for(i=0;i<npts;i++)
	{
		pos = PS_POS(phase,i);
		filePrintf(out,"\t[%g,%g,%g],\n",pos[0],pos[1],pos[2]);
	}
	pos = PS_POS(phase,i);
	filePrintf(out,"\t[%g,%g,%g]);\n",pos[0],pos[1],pos[2]);
}

V_LOCAL vboolean V_CALL
ms_save_frame(particle_system_p ps)
{
	ms_output_p ctx = (ms_output_p)ps->monitoring_context;
	conlPrintf(LOG_TRACE,
		"ms_save_frame(): last frame %u (realtime %g,rt_limit %g)\n",
		ctx->framenum,ps->cur_time,ps->solution_realtime_limit);
	ms_append_frame(ctx->outfile,ps);
	if(!ctx->noflush)
		fileControl(ctx->outfile,pctl_flush,NULL);
	return true;
}

V_LOCAL vboolean V_CALL
ms_end_monitoring(particle_system_p ps)
{
	ms_output_p ctx = (ms_output_p)ps->monitoring_context;
	fileClose(ctx->outfile);
	memFree(ctx);
	return true;
}

vboolean
init_ms_context(particle_system_p ps)
{
	char outfile_name[_MAX_PATH] = "";
	ms_output_p ctx;
	vboolean noflush = false;
	vec_t scale = -1;
	scanner_t sc = tokParseString(ps->monitor_setup,"");
	while(tokNext(sc))
	{
		if(!strcmp(tokLast(sc),"filename"))
		{
			strCopy(outfile_name,tokNext(sc),_MAX_PATH);
			continue;
		}
		if(!strcmp(tokLast(sc),"noflush"))
		{
			noflush = true;
			continue;
		}
		if(!strcmp(tokLast(sc),"scale"))
		{
			if(!tokNextFloat(sc,&scale))
				tok_print_what_expected("init_ms_context",
					sc,NULL,"float scale");
			continue;
		}
		conlPrintf(LOG_WARNING,"init_ms_context(): unknown token '%s'.\n",
			tokLast(sc));
	}
	tokDestroy(sc);
	ctx = (ms_output_p)memAlloc(sizeof(ms_output_t));
	memZero(ctx,sizeof(ms_output_t));
	ctx->outfile = fileOpenWrite(outfile_name);
	if(ctx->outfile == LIBV_INVALID_HANDLE)
	{
		conlPrintf(LOG_ERROR,"init_ms_context(): unable to open '%s'.\n",
			outfile_name);
		memFree(ctx);
		return false;
	}
	ctx->framenum = 0;
	ctx->noflush = noflush;
	ps->monitor = ms_save_frame;
	ps->monitoring_context = ctx;
	ps->end_monitoring = ms_end_monitoring;
	ms_write_init(ctx->outfile);
	if(IsZero(scale+1))
	{
		/* no scale in setup; compute it */
		u32_t i;
		vec_p p;
		vec3_t mins, maxs, extents;
		bboxClear(mins,maxs);
		for(i=0;i<ps->num_particles;i++)
		{
			p = PS_POS(ps->phase,i);
			bboxAddPoint(mins,maxs,p);
		}
		vec3Sub(extents,maxs,mins);
		scale = extents[vec3MaxInd(extents)];
		if(IsZero(scale))
			scale = 1;
		else
			scale = (vec_t)MAX_SCENE_SIZE / scale;
	}
	ms_create_spheres(ctx->outfile,ps,scale);
	return true;
}
