#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 /* WTF? */

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.
 */
void
ms_create_spheres(vhandle_t out, /* script file */
	particle_system_p ps /* data source */)
{
	u32_t i, segs = 3;
	u32_t npts = ps->num_particles;
	vec_p psize = ps->particle_radius;
	vec_p phase = ps->cur_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 *= ps->ms_global_scale;
	dr *= ps->ms_global_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,ps->ms_global_scale);
		radius = psize[i] * ps->ms_global_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 * 0.8,1,1);
		rgbFromHsv(rgbv,hsv);
		rgbByteFromFloat(rgb,rgbv);
		sprintf(buf,"\tappend SceneSpheres "
			"(GeoSphere pos:[%g,%g,%g] radius:%g segments:%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)
{
	CALLER_NAME(init_ms_context)
	char outfile_name[_MAX_PATH] = "";
	ms_output_p ctx;
	vboolean noflush = false;
	vec_t scale = -1;
	vhandle_t sc = lexParseString(ps->monitor_setup,"");
	while(lexNext(sc))
	{
		if(lexLastWas(sc,"filename"))
		{
			strCopy(outfile_name,lexNext(sc),_MAX_PATH);
			continue;
		}
		if(lexLastWas(sc,"noflush"))
		{
			noflush = true;
			continue;
		}
		if(lexLastWas(sc,"scale")) /* fixme: global scale */
		{
			if(!lexNextFloat(sc,&scale))
				SYN_EXPECTED(sc,"float scale");
			continue;
		}
		conlPrintf(LOG_WARNING,"init_ms_context(): unknown token '%s'.\n",
			lexLast(sc));
	}
	lexDestroy(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 */ /* WTF? */
		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[vec3MaxAbsInd(extents)];
		if(IsZero(scale))
			scale = 1;
		else
			scale = (vec_t)MAX_SCENE_SIZE / scale;
	}
	ms_create_spheres(ctx->outfile,ps);
	return true;
}

void WriteCreateFalloffMap(vhandle_t out,char *fn_name,vec3_t c1,vec3_t c2)
{
	filePrintf(out,"fn %s =\n(\n",fn_name);
	filePrintf(out,"\tlocal m = Falloff();\n");
	filePrintf(out,"\tm.color1 = (color %g %g %g)\n",c1[0],c1[1],c1[2]);
	filePrintf(out,"\tm.color2 = (color %g %g %g)\n",c2[0],c2[1],c2[2]);
	filePrintf(out,"\tm.type = 0\n\tm.direction = 0\n\treturn m;\n)\n");
}

void WriteCreateCellularMap(vhandle_t out,char *fn_name,
	vec3_t dc1,vec3_t dc2,vec3_t dc3,vboolean chips,
	vec_t size,vec_t spread,
	vec_t low,vec_t mid,vec_t high)
{
	filePrintf(out,"fn %s =\n(\n",fn_name);
	filePrintf(out,"\tlocal m = Cellular();\n");
	filePrintf(out,"\tm.cellColor = (color %g %g %g);\n",dc1[0],dc1[1],dc1[2]);
	filePrintf(out,"\tm.divColor1 = (color %g %g %g);\n",dc2[0],dc2[1],dc2[2]);
	filePrintf(out,"\tm.divColor2 = (color %g %g %g);\n",dc3[0],dc3[1],dc3[2]);
	filePrintf(out,"\tm.variation = 0;\n");
	filePrintf(out,"\tm.size = %g;\n",size);
	filePrintf(out,"\tm.spread = %g;\n",spread);
	filePrintf(out,"\tm.lowThresh = %g;\n",low);
	filePrintf(out,"\tm.midThresh = %g;\n",mid);
	filePrintf(out,"\tm.highThresh = %g;\n",high);
	filePrintf(out,"\tm.type = %d;\n\tm.fractal = false;\n\treturn m;\n)\n",
		chips ? 1 : 0);
}

void WriteCreateMaterial(vhandle_t outfile,
	char *fn_name,
	vec3_t diffuse,
	vec3_t specular,
	vec_t specular_level,
	vec_t glossiness,
	vec_t self_illum,
	vec_t opacity,
	vec_t falloff,
	vboolean falloff_to_center,
	vboolean additive_opacity,
	vboolean target_R4)
{
	specular_level *= 100;
	glossiness *= 100;
	self_illum *= 100;
	opacity *= 100;
	falloff *= 100;
	filePrintf(outfile,"fn %s =\n(\n\t"
		"local m = StandardMaterial();\n\t",fn_name);
	/*
	 * R2 shaders:
	 * 1 - Constant
	 * 2 - Phong
	 * 3 - Metal
	 * 4 - Blinn
	 *
	 * R4 shaders:
	 * 0 - Anisotropic
	 * 1 - Blinn
	 * 2 - Metal
	 * 3 - Multi-Layer
	 * 4 - Oren-Nayar-Blinn
	 * 5 - Phong
	 * 6 - Strauss
	 *
	 * Specular parameters:
	 * R2: .shinestrength; R4: .specularlevel
	 * R2: .shininess; R4: .glossiness
	 *
	 * R2 opacityType
	 * 3 - filter
	 * 1 - subtractive
	 * 2 - additive
	 *
	 * R4 opacityType
	 * 0 - filter
	 * 1 - subtractive
	 * 2 - additive
	 *
	 * R2 falloff
	 * 1 - In (opaque boundary)
	 * 2 - Out (opaque center)
	 *
	 * R4 falloff (opacityFallOffType)
	 * 0 - In (opaque boundary)
	 * 1 - Out (opaque center)
	 */
	if(target_R4)
		filePrintf(outfile,"m.shaderType = 1;\n\t");
	else
		filePrintf(outfile,"m.shading = 4;\n\t");
	filePrintf(outfile,"m.Diffuse = (color %g %g %g);\n\t",
		256 * diffuse[0],256 * diffuse[1],256 * diffuse[2]);
	filePrintf(outfile,"m.Specular = (color %g %g %g);\n\t",
		256 * specular[0],256 * specular[1],256 * specular[2]);

	filePrintf(outfile,"m.Opacity = %g;\n\t",opacity);
	filePrintf(outfile,"m.OpacityFalloff = %g;\n\t",falloff);

	if(target_R4)
	{
		filePrintf(outfile,"m.SpecularLevel = %g;\n\t",specular_level);
		filePrintf(outfile,"m.Glossiness = %g;\n\t",glossiness);
		filePrintf(outfile,"m.SelfIllumAmount = %g;\n\t",self_illum);
		filePrintf(outfile,"m.OpacityFallOffType = %d;\n\t",
			falloff_to_center ? 0 : 1);
		filePrintf(outfile,"m.OpacityType = %d;\n\t",
			additive_opacity ? 2 : 0);
	}
	else
	{
		filePrintf(outfile,"m.ShineStrength = %g;\n\t",specular_level);
		filePrintf(outfile,"m.Shininess = %g;\n\t",glossiness);
		filePrintf(outfile,"m.SelfIllum = %g;\n\t",self_illum);
		filePrintf(outfile,"m.FallOff = %d;\n\t",
			falloff_to_center ? 1 : 2);
		filePrintf(outfile,"m.OpacityType = %d;\n\t",
			additive_opacity ? 2 : 3);
	}
	filePuts(outfile,"return m;\n)\n\n");

	/*
	 *   3DS MAX material maps for 'Blinn' & 'Metal' shaders
	 * -------------------------------------------------------
	 * |	   map		 |	  R2/R4		|	R4	   |
	 * -------------------------------------------------------
	 * | ambient		   | .ambientmap	   | .maps[1]	|
	 * | diffuse		   | .diffusemap	   | .maps[2]	|
	 * | specular color	| .specularmap	  | .maps[3]	|
	 * | specular level	| .shinestrengthmap | .maps[4]	|
	 * | glossiness		| .shininessmap	 | .maps[5]	|
	 * | self-illumination | .selfillummap	 | .maps[6]	|
	 * | opacity		   | .opacitymap	   | .maps[7]	|
	 * | filter color	  | .filtermap		| .maps[8]	|
	 * | bump			  | .bumpmap		  | .maps[9]	|
	 * | reflection		| .reflectionmap	| .maps[10]   |
	 * | refraction		| .refractionmap	| .maps[11]   |
	 * | displacement	  | -				 | .maps[12]   |
	 * -------------------------------------------------------
	 * Map amounts / enables:
	 * R2/R4: .<map_name>amount / .<map_name>enable
	 * R4: .mapAmounts[idx] / .mapEnables[idx]
	 */
}

void WriteCreateBoundaries(vhandle_t outfile,particle_system_p ps)
{
	u32_t i;
	filePrintf(outfile,"BSM = Standard();\n");
	filePrintf(outfile,"BSM.Wire = true;\n");
	filePrintf(outfile,"BSM.WireSize = 1.3;\n");
	filePrintf(outfile,"BSM.DiffuseColor = (color 0 0 0);\n");
	filePrintf(outfile,"BSM.Opacity = 35;\n");
	filePrintf(outfile,"BSM.TwoSided = true;\n");
	for(i=0;i<ps->num_boundary_spheres;i++)
	{
		vec4_t v;
		vec4Copy(v,ps->boundary_sphere[i]);
		vec4Scale(v,v,ps->ms_global_scale);
		filePrintf(outfile,
			"S = GeoSphere pos:[%g,%g,%g] radius:%g segments:3;\n",
			v[0],v[1],v[2],v[3]);
		filePrintf(outfile,"S.material = BSM;\n");
	}
/*
	for(i = 0;i < ps->num_boundary_planes;i++)
	{
		
	}
*/
}

void ms_save_frame_hack(particle_system_p ps)
{
	vec3_t dc = {15.0/255,47.0/255,41.0/255};
	vec3_t sc = {0.9,0.9,0.9};
	vec3_t dc1 = {0,0,0};
	vec3_t dc2 = {127,127,127};
	vec3_t dc3 = {255,255,255};
	vhandle_t outfile;
	logFormatString(ps->tmpname,"%s/frame%04u.ms",ps->outdir,++ps->ms_frame_count);
	outfile = fileOpenWrite(ps->tmpname);
	assert(outfile);
	filePrintf(outfile,"-- time %g\n", ps->cur_time);
	filePrintf(outfile,"-- ms_global_scale %g\n", ps->ms_global_scale);
	filePuts(outfile,"select $*\nmax delete\n");
	ms_create_spheres(outfile,ps);
	WriteCreateMaterial(outfile,"CreateParticleMaterial",dc,sc,
		0,0,0.2,0.7,1,false,false,true);
	WriteCreateCellularMap(outfile,"CreateBumpMap",dc1,dc2,dc3,true,
		3,0.7,0,0.5,1);
	WriteCreateBoundaries(outfile,ps);
	filePuts(outfile,"BackgroundColor = (color 255 255 255);");
	filePuts(outfile,"SceneSpheres = CreateAllSpheres();");
	filePuts(outfile,"ParticleMaterial = CreateParticleMaterial();");
	filePuts(outfile,"ParticleMaterial.BumpMap = CreateBumpMap();");
	filePuts(outfile,"ParticleMaterial.BumpMap.bumpSmoothing = 0.5;");
	filePuts(outfile,"for i in SceneSpheres do i.material = ParticleMaterial;");
	fileClose(outfile);
}
