/*
 * Simulation of particle's interaction.
 * Version 0.2.alpha.2
 * #include <GPL-disclaimer>
 */

#include <v_lib.h>
#include "phasesp.h"
#include "icond.h"
#include "util.h"
#include "psys.h"

V_LOCAL void
print_what_expected(vhandle_t sc,char *filename,char *expected)
{
	tok_print_what_expected("parse_psdl_script",sc,filename,expected);
}

V_LOCAL vboolean
tok_next_vec3(vhandle_t sc,vec3_t dest)
{
	if(!lexNextIs(sc,"["))
		return false;
	if(!lexNextFloat(sc,dest))
		return false;
	if(!lexNextIs(sc,","))
		return false;
	if(!lexNextFloat(sc,dest+1))
		return false;
	if(!lexNextIs(sc,","))
		return false;
	if(!lexNextFloat(sc,dest+2))
		return false;
	if(!lexNextIs(sc,"]"))
		return false;
	return true;
}

V_LOCAL vboolean
tok_next_float_controller(vhandle_t sc,float_controller_t *ctrl_ret,vec_p ctrl_args)
{
	u32_t i, nargs;
	memZero(ctrl_args,MAX_CTRL_ARGS * sizeof(vec_t));
	*ctrl_ret = get_float_controller(lexNext(sc),&nargs);
	if(!*ctrl_ret)
		return false;
	for(i=0;i<nargs;i++)
		if(!lexNextFloat(sc,ctrl_args+i))
			return false;
	return true;
}

V_LOCAL vboolean
tok_next_vec3_controller(vhandle_t sc,vec3_controller_t *ctrl_ret,vec_p ctrl_args)
{
	u32_t i, nargs;
	memZero(ctrl_args,MAX_CTRL_ARGS * sizeof(vec_t));
	*ctrl_ret = get_vec3_controller(lexNext(sc),&nargs);
	if(!*ctrl_ret)
		return false;
	for(i=0;i<nargs;i++)
		if(!lexNextFloat(sc,ctrl_args+i))
			return false;
	return true;
}

#define DS_MIN_DIST     (vec_t)1e-7
#define DS_TIMEOUT      10000
#define DS_COVERAGE     (vec_t)0.2
#define COMMENT_CHAR    '#'

particle_system_p
parse_psdl_script(char *filename)
{
	CALLER_NAME(parse_psdl_script)
	s32_t iarg;
	vec_t farg, farg2;
	vec3_t varg, varg2;
	vboolean got_radius = false;
	vboolean got_density = false;
	vboolean got_position = false;
	particle_system_p ps = NULL;
	vec_t min_dist = DS_MIN_DIST;
	vhandle_t sc = lexParseFile(filename,"[,]");
	if(sc == LIBV_INVALID_HANDLE)
		return NULL;
	lexSetInteger(sc,LEX_COMMENT_CHAR,COMMENT_CHAR);
	lexEnable(sc,LEX_ONE_CHAR_COMMENTS);
	if(strcmp(strSafe(lexNext(sc)),"number_of_particles"))
	{
		SYN_EXPECTED(sc,"number_of_particles");
		lexDestroy(sc);
		return NULL;
	}
	iarg = -1;
	if(!lexNextInteger(sc,&iarg) || iarg < 2)
	{
		SYN_EXPECTED(sc,"integer number greater than 2");
		lexDestroy(sc);
		return NULL;
	}
	ps = alloc_particle_system(iarg);
	if(!ps)
	{
		conlPrintf(LOG_ERROR,"parse_psdl_script(%s:%u): unable to allocate"
			"memory for '%d' particles.\n",filename,
			lexLastLine(sc),iarg);
		lexDestroy(sc);
		return NULL;
	}
	strCopy(ps->file_name,filename,_MAX_PATH);
	for(lexNext(sc);lexLast(sc);lexNext(sc))
	{
		char *tok = lexLast(sc);
		if(!strcmp(tok,"random_seed"))
		{
			if(!lexNextInteger(sc,&iarg))
			{
				SYN_EXPECTED(sc,"integer greater than 1");
				break;
			}
			ps->random_seed = seed;
			seed = iarg;
			util_shutdown();
			util_init();
			continue;
		}
		if(!strcmp(tok,"dglue"))
		{
			if(!lexNextFloat(sc,&farg))
			{
				SYN_EXPECTED(sc,"float gdlue");
				break;
			}
			ps->dglue = farg;
			/* printf("DGLUE %g\n",ps->dglue); */
			continue;
		}
		if(!strcmp(tok,"hglue"))
		{
			if(!lexNextFloat(sc,&farg))
			{
				SYN_EXPECTED(sc,"float hglue");
				break;
			}
			ps->hglue = farg;
			/* printf("HGLUE %g\n",ps->hglue); */
			continue;
		}
		if(!strcmp(tok,"deltaz"))
		{
			if(!lexNextFloat(sc,&farg))
			{
				SYN_EXPECTED(sc,"float deltaz");
				break;
			}
			ps->deltaz = farg;
			printf("deltaz %g\n",ps->deltaz);
			continue;
		}
		if(!strcmp(tok,"particle_radius"))
		{
			if(!lexNextIs(sc,"uniform"))
			{
				print_what_expected(sc,filename,"uniform");
				break;
			}
			if(!lexNextFloat(sc,&farg))
			{
				SYN_EXPECTED(sc,"floating-point number (with dot as separator)");
				break;
			}
			if(!lexNextFloat(sc,&farg2))
			{
				SYN_EXPECTED(sc,"floating-point number (with dot as separator)");
				break;
			}
			uniform_random_vector(ps->particle_radius,ps->num_particles,
				farg,farg2);
			got_radius = true;
			continue;
		}
		if(!strcmp(tok,"particle_density"))
		{
			if(!lexNextIs(sc,"uniform"))
			{
				print_what_expected(sc,filename,"uniform");
				break;
			}
			if(!lexNextFloat(sc,&farg))
			{
				SYN_EXPECTED(sc,"floating-point number (with dot as separator)");
				break;
			}
			if(!lexNextFloat(sc,&farg2))
			{
				SYN_EXPECTED(sc,"floating-point number (with dot as separator)");
				break;
			}
			uniform_random_vector(ps->particle_density,ps->num_particles,
				farg,farg2);
			got_density = true;
			continue;
		}
		if(!strcmp(tok,"particle_minimal_distance"))
		{
			if(got_position)
			{
				conlPrintf(LOG_WARNING,"parse_psdl_script(%s:%u): got "
					"'particle_minimal_distance' after "
					"'particle_distribution_domain'; ignored.\n",
					filename,lexLastLine(sc));
			}
			if(!lexNextFloat(sc,&min_dist))
			{
				SYN_EXPECTED(sc,"floating-point number (with dot as separator)");
				break;
			}
			continue;
		}
		if(!strcmp(tok,"particle_distribution_domain"))
		{
			char *dom;
			if(!got_radius)
			{
				conlPrintf(LOG_ERROR,"parse_psdl_script(%s:%u): got "
					"'particle_distribution_domain' before "
					"'particle_radius'\n",filename,lexLastLine(sc));
				break;
			}
			dom = lexNext(sc);
			if(!strcmp(dom,"box"))
			{
				vec3_t mins, maxs;
				if(!tok_next_vec3(sc,varg))
				{
					print_what_expected(sc,filename,"vec3 center");
					break;
				}
				if(!tok_next_vec3(sc,varg2))
				{
					print_what_expected(sc,filename,"vec3 extents");
					break;
				}
				vec3Scale(varg2,varg2,(vec_t)0.5);
				vec3Sub(mins,varg,varg2);
				vec3Add(maxs,varg,varg2);
				if(!distribute_in_box(mins,maxs,ps->num_particles,
					ps->particle_radius,min_dist,ps->phase,
					DS_TIMEOUT,DS_COVERAGE))
				{
					vec3Sub(varg2,maxs,mins);
					conlPrintf(LOG_ERROR,
						"parse_psdl_script(%s:%u): unable to distribute %u"
						" particles in box [%g,%g,%g] at [%g,%g,%g] (min_dist %g)\n",
						filename,lexLastLine(sc),
						ps->num_particles,varg[0],varg[1],varg[2],
						varg2[0],varg2[1],varg2[2],min_dist);
					break;
				}
			} else if(!strcmp(dom,"sphere"))
			{
				if(!tok_next_vec3(sc,varg))
				{
					print_what_expected(sc,filename,"vec3 center");
					break;
				}
				if(!lexNextFloat(sc,&farg))
				{
					SYN_EXPECTED(sc,"floating-point number (with dot as separator)");
					break;
				}
				if(!distribute_in_sphere(varg,farg,ps->num_particles,
					ps->particle_radius,min_dist,ps->phase,
					DS_TIMEOUT,DS_COVERAGE))
				{
					conlPrintf(LOG_ERROR,
						"parse_psdl_script(%s:%u): unable to distribute %u"
						" particles in sphere R=%g at [%g,%g,%g] (min_dist %g)\n",
						filename,lexLastLine(sc),
						ps->num_particles,farg,varg[0],varg[1],varg[2],min_dist);
					break;
				}
			} else if(!strcmp(dom,"cylinder"))
			{
				if(!tok_next_vec3(sc,varg))
				{
					print_what_expected(sc,filename,"vec3 center");
					break;
				}
				if(!lexNextFloat(sc,&farg))
				{
					print_what_expected(sc,filename,"float radius");
					break;
				}
				if(!lexNextFloat(sc,&farg2))
				{
					print_what_expected(sc,filename,"float height");
					break;
				}
				if(!distribute_in_cylinder(varg,farg,farg2,
					ps->num_particles,ps->particle_radius,
					min_dist,ps->phase,DS_TIMEOUT,DS_COVERAGE))
				{
					conlPrintf(LOG_ERROR,
						"parse_psdl_script(%s:%u): unable to distribute %u"
						" particles in cylinder R=%g H=%g at [%g,%g,%g] (min_dist %g)\n",
						filename,lexLastLine(sc),
						ps->num_particles,farg,farg2,
						varg[0],varg[1],varg[2],min_dist);
					break;
				}
			} else
			{
				print_what_expected(sc,filename,"box | sphere | cylinder");
				break;
			}
			got_position = true;
			continue;
		}
		if(!strcmp(tok,"boundary_plane"))
		{
			u32_t nbp = ps->num_boundary_planes;
			if(nbp == MAX_BOUNDARY_PLANES)
			{
				conlPrintf(LOG_ERROR,"parse_psdl_script(%s:%u): "
					"too many boundary planes (%u)\n",
					filename,lexLastLine(sc),MAX_BOUNDARY_PLANES);
				break;
			}
			if(!tok_next_vec3(sc,ps->boundary_plane[nbp]))
			{
				print_what_expected(sc,filename,"vec3 plane_normal");
				break;
			}
			if(!lexNextFloat(sc,&(ps->boundary_plane[nbp][3])))
			{
				print_what_expected(sc,filename,"float plane_dist");
				break;
			}
			if(!tok_next_float_controller(sc,&ps->plane_interaction[nbp],
				ps->plane_interaction_arg[nbp]))
			{
				print_what_expected(sc,filename,"float_controller plane_interaction");
				break;
			}
			vec3Unit(ps->boundary_plane[nbp],ps->boundary_plane[nbp]);
			ps->num_boundary_planes++;
			continue;
		}
		if(!strcmp(tok,"boundary_sphere"))
		{
			u32_t nbs = ps->num_boundary_spheres;
			if(nbs == MAX_BOUNDARY_SPHERES)
			{
				conlPrintf(LOG_ERROR,"parse_psdl_script(%s:%u): "
					"too many boundary spheres (%u)\n",
					filename,lexLastLine(sc),MAX_BOUNDARY_PLANES);
				break;
			}
			if(!tok_next_vec3(sc,ps->boundary_sphere[nbs]))
			{
				print_what_expected(sc,filename,"vec3 center");
				break;
			}
			if(!lexNextFloat(sc,&(ps->boundary_sphere[nbs][3])))
			{
				SYN_EXPECTED(sc,"floating-point number (with dot as separator)");
				break;
			}
			if(!tok_next_float_controller(sc,&ps->sphere_interaction[nbs],
				ps->sphere_interaction_arg[nbs]))
			{
				SYN_EXPECTED(sc,"float_controller sphere_interaction");
				break;
			}
			ps->num_boundary_spheres++;
			continue;
		}
		if(!strcmp(tok,"binary_interaction"))
		{
			if(ps->binary_interaction)
			{
				conlPrintf(LOG_ERROR,"parse_psdl_script(%s:%u): "
					"got another 'binary_interaction'\n",
					filename,lexLastLine(sc));
				break;
			}
			if(!tok_next_float_controller(sc,&ps->binary_interaction,
				ps->binary_interaction_arg))
			{
				SYN_EXPECTED(sc,"float_controller binary_interaction");
				break;
			}
			continue;
		}
		if(!strcmp(tok,"environmental_interaction"))
		{
			if(ps->env_velocity)
			{
				conlPrintf(LOG_ERROR,"parse_psdl_script(%s:%u): "
					"got another 'environmental_interaction'\n",
					filename,lexLastLine(sc));
				break;
			}
			if(!lexNextFloat(sc,&ps->env_eta))
			{
				SYN_EXPECTED(sc,"floating-point viscousity");
				break;
			}
			if(!tok_next_vec3_controller(sc,&ps->env_velocity,
				ps->env_velocity_arg))
			{
				print_what_expected(sc,filename,"vec3_controller env_velocity");
				break;
			}
			continue;
		}
		if(!strcmp(tok,"unary_force"))
		{
			if(ps->unary_force)
			{
				conlPrintf(LOG_ERROR,"parse_psdl_script(%s:%u): "
					"got another 'unary_force'\n",
					filename,lexLastLine(sc));
				break;
			}
			if(!tok_next_vec3_controller(sc,&ps->unary_force,
				ps->unary_force_arg))
			{
				print_what_expected(sc,filename,"vec3_controller unary_force");
				break;
			}
			continue;
		}
		if(!strcmp(tok,"unary_accel"))
		{
			if(ps->unary_accel)
			{
				conlPrintf(LOG_ERROR,"parse_psdl_script(%s:%u): "
					"got another 'unary_accel'\n",
					filename,lexLastLine(sc));
				break;
			}
			if(!tok_next_vec3_controller(sc,&ps->unary_accel,
				ps->unary_accel_arg))
			{
				print_what_expected(sc,filename,"vec3_controller unary_accel");
				break;
			}
			continue;
		}
		if(!strcmp(tok,"solution_time_limit"))
		{
			if(!lexNextFloat(sc,&ps->solution_time_limit))
			{
				print_what_expected(sc,filename,"float solution_time_limit");
				break;
			}
			continue;
		}
		if(!strcmp(tok,"saves_per_second"))
		{
			if(!lexNextInteger(sc,(s32_p)&ps->saves_per_second))
			{
				print_what_expected(sc,filename,"integer saves_per_second");
				break;
			}
			continue;
		}
		if(!strcmp(tok,"ms_draw_skips"))
		{
			if(!lexNextInteger(sc,(s32_p)&ps->ms_draw_skips))
			{
				print_what_expected(sc,filename,"integer ms_draw_skips");
				break;
			}
			continue;
		}
		if(!strcmp(tok,"global_skips"))
		{
			if(!lexNextInteger(sc,(s32_p)&ps->global_skips))
			{
				print_what_expected(sc,filename,"integer global_skips");
				break;
			}
			continue;
		}
		if(!strcmp(tok,"ms_global_scale"))
		{
			if(!lexNextFloat(sc,&ps->ms_global_scale))
			{
				print_what_expected(sc,filename,"float ms_global_scale");
				break;
			}
			continue;
		}
		if(!strcmp(tok,"solution_realtime_limit"))
		{
			if(!lexNextFloat(sc,&ps->solution_realtime_limit))
			{
				print_what_expected(sc,filename,"float solution_realtime_limit");
				break;
			}
			continue;
		}
		if(!strcmp(tok,"solution_RK_tolerance"))
		{
			if(!lexNextFloat(sc,&ps->solution_RK_tolerance))
			{
				print_what_expected(sc,filename,"float solution_RK_tolerance");
				break;
			}
			continue;
		}
		if(!strcmp(tok,"solution_maximal_step"))
		{
			if(!lexNextFloat(sc,&ps->solution_maximal_step))
			{
				print_what_expected(sc,filename,"float solution_maximal_step");
				break;
			}
			continue;
		}
		if(!strcmp(tok,"solution_RK_timeout"))
		{
			if(!lexNextFloat(sc,&ps->solution_RK_timeout))
			{
				print_what_expected(sc,filename,"float solution_RK_timeout");
				break;
			}
			continue;
		}
		if(!strcmp(tok,"output_maxscript"))
		{
			ps->output_maxscript = true;
			ps->nodraw = true;
			continue;
		}
		if(!strcmp(tok,"output_psy"))
		{
			ps->output_maxscript = false;
			ps->nodraw = true;
			continue;
		}
		if(!strcmp(tok,"monitor_setup"))
		{
			char *setup = lexNext(sc);
			if(!setup)
			{
				print_what_expected(sc,filename,"string monitor_setup");
				break;
			}
			if(strlen(setup) >= MAX_MONITOR_DATA)
				conlPrintf(LOG_WARNING,"parse_psdl_script(%s:%u): "
					"strlen(monitor_setup)==%u too long (max %u).\n",
					filename,lexLastLine(sc),strlen(setup),
					MAX_MONITOR_DATA);
			strCopy(ps->monitor_setup,setup,MAX_MONITOR_DATA);
			continue;
		}
		conlPrintf(LOG_WARNING,"parse_psdl_script(%s:%u): "
			"unknown token '%s'\n",
			filename,lexLastLine(sc),tok);
	}
	ps->cur_phase = ps->phase;
	if(got_radius && got_position && got_density)
	{
		/* setup reciprocal masses and viscousity coefficients */
		u32_t i;
		vec_t r, k1 = (vec_t)4.0 / (vec_t)3.0 * M_PI;
		vec_t k2 = 6 * M_PI * ps->env_eta;
		for(i=0;i<ps->num_particles;i++)
		{
			r = ps->particle_radius[i];
			if(r < 1e-16) /* smaller than 0.1fm */
			{
				conlPrintf(LOG_ERROR,"parse_psdl_script(%s:%u): "
					"particle[%u] radius (%g) too small.\n",filename,
					lexLastLine(sc),i,r);
				break;
			}
			ps->particle_mass[i] = k1 * r * r * r * ps->particle_density[i];
			ps->particle_reciprocal_mass[i] = (vec_t)1.0 / ps->particle_mass[i];
			ps->particle_6_pi_eta_r[i] = k2 * ps->particle_radius[i];
		}
		if(!ps->binary_interaction)
			conlPuts(LOG_WARNING,
				"parse_psdl_script(): no binary interaction defined!\n");
		if(!ps->unary_force)
			conlPuts(LOG_WARNING,
				"parse_psdl_script(): no unary force defined.\n");
		if(!ps->env_velocity)
			conlPuts(LOG_WARNING,"parse_psdl_script(): no "
				"environmental velocity defined.\n");
		if(i!=ps->num_particles)
		{
			free_particle_system(ps);
			ps = NULL;
		}
	}
	else
	{
		conlPrintf(LOG_ERROR,"parse_psdl_script(%s:%u): not enough data "
			"to construct the particle system.\n",filename,lexLastLine(sc));
		free_particle_system(ps);
		ps = NULL;
	}
	lexDestroy(sc);
	return ps;
}
