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

/* ms_wr */
extern vboolean
psy_load_pos(particle_system_p ps, char *filename);

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;
	char tmpname[1024];
	memZero(ctrl_args,MAX_CTRL_ARGS * sizeof(vec_t));
	*ctrl_ret = get_float_controller(lexNext(sc),&nargs);
	if(!*ctrl_ret)
		return false;
	strCopy(tmpname, lexLast(sc), 1024);
	for(i=0;i<nargs;i++)
		if(!lexNextFloat(sc,ctrl_args+i))
			return false;
	/* HACK */
	if(!strcmp(tmpname, "UR_modulus"))
	{
		/*
		 * U(r)=U_0(\frac{r_0}{r})^\alpha
		 * \frac{dU}{dr} = -\alpha U_0 r_0^\alpha r^{-(\alpha+1)}
		 * Precompute scale and power:
		 */
		vec_t U0 = ctrl_args[0];
		vec_t R0 = ctrl_args[1];
		vec_t Alpha = ctrl_args[2];
		ctrl_args[0] = - Alpha * U0 * pow(R0, Alpha); /* scale */
		ctrl_args[1] = - (Alpha + 1); /* power */
	}	
	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 1");
		lexDestroy(sc);
		return NULL;
	}
	ps = psAlloc(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->script_name,filename,_MAX_PATH);
	for(lexNext(sc);lexLast(sc);lexNext(sc))
	{
		if(lexLastWas(sc,"random_seed"))
		{
			if(!lexNextInteger(sc,&iarg))
			{
				SYN_EXPECTED(sc,"integer number greater than 1");
				break;
			}
			ps->random_seed = seed;
			seed = iarg;
			util_shutdown();
			util_init();
			continue;
		}
		if(lexLastWas(sc,"particle_shell_setup"))
		{
			vboolean dglue_defined = false;
			vboolean pd_defined = false;
			if(!lexNextIs(sc,"{"))
			{
				SYN_EXPECTED(sc,"{");
				break;
			}
			for(lexNext(sc);!lexLastWas(sc,"}");lexNext(sc))
			{
				if(lexLastWas(sc,"tangent_forces"))
				{
					if(lexNextIs(sc,"true"))
						ps->tangent_forces = true;
					else
					{
						if(lexLastWas(sc,"false"))
							ps->tangent_forces = false;
						else
						{
							SYN_EXPECTED(sc,"true or false");
							break;
						}
					}
					continue;
				}
				if(lexLastWas(sc,"shell_thickness"))
				{
					u32_t i;
					if(lexNextIs(sc,"radius_by"))
					{
						if(!lexNextFloat(sc,&farg))
						{
							SYN_EXPECTED(sc,"float shell_thickness_scale");
							break;
						}
						if(!got_radius)
						{
							conlPrintf(LOG_ERROR,"%s: proportional shells can only be set up after radii; specify particle_radius first\n",caller_name);
							break;
						}
						for(i = 0;i < ps->num_particles;i++)
							ps->dglue[i] = ps->particle_radius[i] * farg;
					}
					else
					{
						if(!lexLastFloat(sc,&farg))
						{
							SYN_EXPECTED(sc,"float shell_thickness");
							break;
						}
						for(i = 0;i < ps->num_particles;i++)
							ps->dglue[i] = farg;
					}
					dglue_defined = true;
					continue;
				}
				if(lexLastWas(sc,"percolation_distance"))
				{
					u32_t i;
					if(lexNextIs(sc,"radius_by"))
					{
						if(!lexNextFloat(sc,&farg))
						{
							SYN_EXPECTED(sc,"float percolation_distance_scale");
							break;
						}
						if(!got_radius)
						{
							conlPrintf(LOG_ERROR,"%s: proportional percolation_distance can only be set up after radii; specify particle_radius first\n",caller_name);
							break;
						}
						for(i = 0;i < ps->num_particles;i++)
							ps->percolation_distance[i] = ps->particle_radius[i] * farg;
					}
					else
					{
						if(!lexLastFloat(sc,&farg))
						{
							SYN_EXPECTED(sc,"float percolation_distance");
							break;
						}
						for(i = 0;i < ps->num_particles;i++)
							ps->percolation_distance[i] = farg;
					}
					pd_defined = true;
					continue;
				}
				if(lexLastWas(sc,"shell_viscosity"))
				{
					if(!lexNextFloat(sc,&farg))
					{
						SYN_EXPECTED(sc,"float shell_viscosity");
						break;
					}
					ps->hglue = farg;
					continue;
				}
				PARSE_ERROR(sc,"unknown token");
			}
			ps->percolation_distance_defined = pd_defined;
			if(ps->tangent_forces && !dglue_defined)
			{
				GENERIC_PARSE_WARNING(sc,"ps->tangent_forces == true, but shell_thickness is undefined");
			}
			if(lexLastWas(sc,"}"))
				continue;
			break;
		}
		if(lexLastWas(sc,"particle_radius"))
		{
			if(lexNextIs(sc,"uniform"))
			{
				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;
				}
				vecsUniform(ps->particle_radius,ps->num_particles,farg,farg2);
				got_radius = true;
				continue;
			}
			if(lexLastWas(sc,"table"))
			{
				PARSE_ERROR(sc, "NOT IMPLEMENTED");
			}
			break;
		}
		if(lexLastWas(sc,"particle_density"))
		{
			if(!lexNextIs(sc,"uniform"))
			{
				SYN_EXPECTED(sc,"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;
			}
			vecsUniform(ps->particle_density,ps->num_particles,farg,farg2);
			got_density = true;
			continue;
		}
		if(lexLastWas(sc,"environment_density"))
		{
			if(!lexNextFloat(sc,&ps->environment_density))
			{
				SYN_EXPECTED(sc,"float environment_density");
				break;
			}
			continue;
		}
		if(lexLastWas(sc,"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(lexLastWas(sc,"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;
			}
			if(got_position)
			{
				conlPrintf(LOG_ERROR,"parse_psdl_script(%s:%u): got "
					"'particle_distribution_domain' after loading phase"
					"from '%s'. Ingored.\n",filename,lexLastLine(sc),ps->initial_filename);
				continue;
			}
			dom = lexNext(sc);
			if(!strcmp(dom,"box"))
			{
				vec3_t mins, maxs;
				if(!tok_next_vec3(sc,varg))
				{
					SYN_EXPECTED(sc,"vec3 center");
					break;
				}
				if(!tok_next_vec3(sc,varg2))
				{
					SYN_EXPECTED(sc,"vec3 extents");
					break;
				}
				vec3Scale(varg2,varg2,(vec_t)0.5);
				vec3Sub(mins,varg,varg2);
				vec3Add(maxs,varg,varg2);
				conPrintf("Placing particles in box..."); fflush(stdout);
				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
					conPrintf(" done.\n");
			} else if(!strcmp(dom,"sphere"))
			{
				if(!tok_next_vec3(sc,varg))
				{
					SYN_EXPECTED(sc,"vec3 center");
					break;
				}
				if(!lexNextFloat(sc,&farg))
				{
					SYN_EXPECTED(sc,"floating-point number (with dot as separator)");
					break;
				}
				conPrintf("Placing particles in sphere..."); fflush(stdout);
				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
					conPrintf(" done.\n"); fflush(stdout);
			} else if(!strcmp(dom,"cylinder"))
			{
				if(!tok_next_vec3(sc,varg))
				{
					SYN_EXPECTED(sc,"vec3 center");
					break;
				}
				if(!lexNextFloat(sc,&farg))
				{
					SYN_EXPECTED(sc,"float radius");
					break;
				}
				if(!lexNextFloat(sc,&farg2))
				{
					SYN_EXPECTED(sc,"float height");
					break;
				}
				conPrintf("Placing particles in cylinder...");
				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
					conPrintf(" done.\n");
			} else
			{
				SYN_EXPECTED(sc,"box | sphere | cylinder");
				break;
			}
			got_position = true;
			continue;
		}
		if(lexLastWas(sc,"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]))
			{
				SYN_EXPECTED(sc,"vec3 plane_normal");
				break;
			}
			if(!lexNextFloat(sc,&(ps->boundary_plane[nbp][3])))
			{
				SYN_EXPECTED(sc,"float plane_dist");
				break;
			}
			if(!tok_next_float_controller(sc,&ps->plane_interaction[nbp],
				ps->plane_interaction_arg[nbp]))
			{
				SYN_EXPECTED(sc,"float_controller plane_interaction");
				break;
			}
			vec3Unit(ps->boundary_plane[nbp],ps->boundary_plane[nbp]);
			ps->num_boundary_planes++;
			continue;
		}
		if(lexLastWas(sc,"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]))
			{
				SYN_EXPECTED(sc,"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(lexLastWas(sc,"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(lexLastWas(sc,"binary_interaction_max_dist"))
		{
			if(!lexNextFloat(sc,&ps->binary_interaction_max_dist))
			{
				SYN_EXPECTED(sc,"float binary_interaction_max_dist");
				break;
			}
			continue;
		}
		if(lexLastWas(sc,"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))
			{
				SYN_EXPECTED(sc,"vec3_controller env_velocity");
				break;
			}
			continue;
		}
		if(lexLastWas(sc,"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))
			{
				SYN_EXPECTED(sc,"vec3_controller unary_force");
				break;
			}
			continue;
		}
		if(lexLastWas(sc,"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))
			{
				SYN_EXPECTED(sc,"vec3_controller unary_accel");
				break;
			}
			continue;
		}
		if(lexLastWas(sc,"solution_time_limit"))
		{
			if(!lexNextFloat(sc,&ps->solution_time_limit))
			{
				SYN_EXPECTED(sc,"float solution_time_limit");
				break;
			}
			continue;
		}
		if(lexLastWas(sc,"saves_per_second"))
		{
			if(!lexNextInteger(sc,(s32_p)&ps->saves_per_second))
			{
				SYN_EXPECTED(sc,"integer saves_per_second");
				break;
			}
			continue;
		}
		if(lexLastWas(sc,"ms_draw_skips"))
		{
			if(!lexNextInteger(sc,(s32_p)&ps->ms_draw_skips))
			{
				SYN_EXPECTED(sc,"integer ms_draw_skips");
				break;
			}
			continue;
		}
		if(lexLastWas(sc,"global_skips"))
		{
			if(!lexNextInteger(sc,(s32_p)&ps->global_skips))
			{
				SYN_EXPECTED(sc,"integer global_skips");
				break;
			}
			continue;
		}
		if(lexLastWas(sc,"ms_global_scale"))
		{
			if(!lexNextFloat(sc,&ps->ms_global_scale))
			{
				SYN_EXPECTED(sc,"float ms_global_scale");
				break;
			}
			continue;
		}
		if(lexLastWas(sc,"solution_realtime_limit"))
		{
			if(!lexNextFloat(sc,&ps->solution_realtime_limit))
			{
				SYN_EXPECTED(sc,"float solution_realtime_limit");
				break;
			}
			continue;
		}
		if(lexLastWas(sc,"solution_RK_tolerance"))
		{
			if(!lexNextFloat(sc,&ps->solution_RK_tolerance))
			{
				SYN_EXPECTED(sc,"float solution_RK_tolerance");
				break;
			}
			continue;
		}
		if(lexLastWas(sc,"solution_maximal_step"))
		{
			if(!lexNextFloat(sc,&ps->solution_maximal_step))
			{
				SYN_EXPECTED(sc,"float solution_maximal_step");
				break;
			}
			continue;
		}
		if(lexLastWas(sc,"solution_RK_timeout"))
		{
			if(!lexNextFloat(sc,&ps->solution_RK_timeout))
			{
				SYN_EXPECTED(sc,"float solution_RK_timeout");
				break;
			}
			continue;
		}
		if(lexLastWas(sc,"output_maxscript"))
		{
			ps->output_maxscript = true;
			ps->nodraw = true;
			continue;
		}
		if(lexLastWas(sc,"output_psy"))
		{
			ps->output_maxscript = false;
			ps->nodraw = true;
			continue;
		}
		if(lexLastWas(sc,"statistics"))
		{
			void_p ptr;
			monroutine_entry_t entry;
			char_p name = lexNext(sc);
			if(NULL == name)
			{
				SYN_EXPECTED(sc, "string statistics_name");
				return false;
			}
			entry = monitorEntryForName(name);
			if(NULL == entry)
			{
				PARSE_ERROR(sc,"unknown name of statistics routine");
				return false;
			}
			FUNC2PTR(ptr, entry);
			vpsPush(&ps->monroutines, ptr);
			continue;
		}
		if(lexLastWas(sc,"monitor_setup"))
		{
			char *setup = lexNext(sc);
			if(!setup)
			{
				SYN_EXPECTED(sc,"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;
		}
		if(lexLastWas(sc,"initial_filename"))
		{
			if(!lexNext(sc))
			{
				SYN_EXPECTED(sc,"string file name for initial phase & radiuses");
				break;
			}
			strCopy(ps->initial_filename, lexLast(sc), _MAX_PATH);
			if(psy_load_pos(ps,ps->initial_filename))
			{
				got_radius = got_position = true;
			}
			else
			{
				/* no error, keep scanning */
				conlPrintf(LOG_WARNING,"parse_psdl_script(%s:%u): "
					"unable to load phase from '%s', using domain information.\n",
					filename,lexLastLine(sc),ps->initial_filename);
			}
			continue;
		}
		if(lexLastWas(sc,"final_filename"))
		{
			if(!lexNext(sc))
			{
				SYN_EXPECTED(sc,"string file name for final phase & radiuses");
				break;
			}
			strCopy(ps->final_filename, lexLast(sc), _MAX_PATH);
			continue;
		}
		PARSE_ERROR(sc,"unknown token");
	}
	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, vol;
		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;
			}
			vol = k1 * r * r * r;
			ps->particle_mass[i] = vol * ps->particle_density[i];
			ps->particle_mass_delta[i] = vol * (ps->particle_density[i] - ps->environment_density);
			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)
		{
			psFree(ps);
			ps = NULL;
		}
	}
	else
	{
		conlPrintf(LOG_ERROR,"parse_psdl_script(%s:%u): not enought data "
			"to construct the model.\n",filename,lexLastLine(sc));
		psFree(ps);
		ps = NULL;
	}
	lexDestroy(sc);
	return ps;
}
