#include "psmod.h"

#define	FIELD_OFF(struct_id,field_id)	\
	(unsigned)(&((*((struct_id*)NULL)).field_id))

#define FIELD_ACCESS(storage,offset,field_type) \
	(*((field_type*)((unsigned char*)(&(storage))+(offset))))

/*
 * Section 'global'
 */

vboolean global_alias(vhandle_t script,particle_system_p ps)
{
	CALLER_NAME(global_alias)
	char alias[MAX_ID], replacement[MAX_ID];
	V_ASSERT(ps);
	if(!lexCopyNextToken(script,alias,MAX_ID) ||
		!lexCopyNextToken(script,replacement,MAX_ID))
	{
		ERROR_EOF();
		return false;
	}
	lexDefineAlias(script,alias,replacement);
	return true;
}

vboolean global_random_seed(vhandle_t script,particle_system_p ps)
{
	CALLER_NAME(global_random_seed)
	s32_t seed;
	V_ASSERT(ps);
	if(!lexNextInteger(script,&seed))
	{
		SYNTAX_EXPECTED("int seed",script);
		return false;
	}
	if(random_generator != LIBV_INVALID_HANDLE)
	{
		ERROR_GENERIC("random_seed already set");
		return false;
	}
	random_generator = ranCreateGenerator(seed);
	return (random_generator != LIBV_INVALID_HANDLE) ? true : false;
}

static parse_entry_t global_ops[] = {
	{"alias",global_alias,"string alias,string replacement; defines alias"},
	{"random_seed",global_random_seed,"int seed;initializes random number generator"},
	{"",NULL,""}
};

/*
 * Section 'particles'
 */

V_LOCAL vboolean
psdlCheckParticleStack(particle_system_p ps)
{
	CALLER_NAME(psdlCheckParticleStack)
	u32_t min, max;
	if(!icGetOperationRange(&ps->dc,&min,&max))
	{
		ERROR_GENERIC("no range defined");
		return false;
	}
	if(stackCount(&ps->particles) <= max)
		stackResize(&ps->particles,max+1);
	return true;
}

V_LOCAL vboolean
particles_radius(vhandle_t script,particle_system_p ps)
{
	u8_p ptr = (u8_p)stackGetStorage(&ps->particles);
	if(!psdlCheckParticleStack(ps))
		return false;
	return icParseFloatTable(
		ptr + FIELD_OFF(particle_parameters_t,radius),
		sizeof(particle_parameters_t),
		0,
		&ps->dc,
		script);
}

V_LOCAL vboolean
particles_density(vhandle_t script,particle_system_p ps)
{
	u8_p ptr = (u8_p)stackGetStorage(&ps->particles);
	if(!psdlCheckParticleStack(ps))
		return false;
	return icParseFloatTable(
		ptr + FIELD_OFF(particle_parameters_t,density),
		sizeof(particle_parameters_t),
		0,
		&ps->dc,script);
}

V_LOCAL vboolean
particles_dc(vhandle_t script,particle_system_p ps)
{
	return icParseDC(script,&ps->dc);
}

static parse_entry_t particles_ops[] = {
	{"distribution_constrains",particles_dc,"section: constrains for further operations"},
	{"radius",particles_radius,"float table: radiuses of the particles"},
	{"density",particles_density,"float table: densities of the particles"},
	{"",NULL,""}
};

/*
 * Section 'initial'
 */

V_LOCAL vboolean 
accept_next_pos(void_p arg,vec3_t pos,u32_t table_idx,vec_t *idx_radius_ret)
{
	u32_t i;
	vec3_t dist;
	vec_t min_dist;
	particle_parameters_p table;
	particle_system_p ps = (particle_system_p)arg;
	table = (particle_parameters_p)stackGetStorage(&ps->particles);
	min_dist = ps->dc.spacing_3d;
	if(idx_radius_ret)
		*idx_radius_ret = table[table_idx].radius;
	for(i=0;i<table_idx;i++)
	{
		vec3Sub(dist,pos,table[i].position);
		if((vec3Abs(dist) - table[i].radius) < min_dist)
			return false;
	}
	return true;
}

V_LOCAL vboolean
initial_dc(vhandle_t script,particle_system_p ps)
{
	return icParseDC(script,&ps->dc);
}

V_LOCAL vboolean
initial_position(vhandle_t script,particle_system_p ps)
{
	vboolean result;
	particle_parameters_p table;
	if(!psdlCheckParticleStack(ps))
		return false;
	table = (particle_parameters_p)stackGetStorage(&ps->particles);
	ps->dc.accept_extra = accept_next_pos;
	ps->dc.accept_extra_arg = ps;
	result = icParseVec3Table(table,sizeof(particle_parameters_t),0,&ps->dc,script);
	ps->dc.accept_extra = NULL;
	ps->dc.accept_extra_arg = NULL;
	return result;
}

V_LOCAL vboolean
initial_interaction(vhandle_t script,particle_system_p ps)
{
	CALLER_NAME(initial_interaction)
	if(ps->binary_interaction)
	{
		WARNING_GENERIC("duplicate definition of interaction");
		ctlFree(ps->binary_interaction);
		ps->binary_interaction = NULL;
	}
	ps->binary_interaction = ctlParseFloat(script);
	return (ps->binary_interaction != NULL) ? true : false;
}

static parse_entry_t initial_ops[] = {
	{"distribution_constrains",initial_dc,"section: constrains for further operations"},
	{"position",initial_position,"vec3 table: positions of the particles"},
	{"interaction",initial_interaction,"float controller: binary interaction"},
	{"",NULL,""}
};

/*
 * Section 'boundary'
 */
/* TODO */
static parse_entry_t boundary_ops[] = {
	{"",NULL,""}
};

/*
 * Section 'environment'
 */
static parse_entry_t environment_ops[] = {
	{"",NULL,""}
};

/*
 * Section 'extra'
 */
static parse_entry_t extra_ops[] = {
	{"",NULL,""}
};

/*
 * Section 'pss_monitor'
 */
static parse_entry_t pss_monitor_ops[] = {
	{"",NULL,""}
};

/*
 * Section 'mxs_monitor'
 */
static parse_entry_t mxs_monitor_ops[] = {
	{"",NULL,""}
};

/*
 * Section 'drw_monitor'
 */
static parse_entry_t drw_monitor_ops[] = {
	{"",NULL,""}
};

/*
 * Section 'solution'
 */
static parse_entry_t solution_ops[] = {
	{"",NULL,""}
};

/*
 * List of sections
 */

static parse_section_t psdl_sections[] = {
	{"global",0,"global parameters",global_ops},
	{"particles",1,"description of the particles' parameters",particles_ops},
	{"initial",2,"description of the initial conditions",initial_ops},
	{"boundary",2,"description of the boundary conditions",boundary_ops},
	{"environment",2,"description of the environment",environment_ops},
	{"extra",2,"specification(s) of external force(s) and/or acceleration(s)",extra_ops},
	{"pss_monitor",2,"setup for PSS file output",pss_monitor_ops},
	{"mxs_monitor",2,"setup for MAXScript file output",mxs_monitor_ops},
	{"drw_monitor",2,"setup for realtime graphics output",drw_monitor_ops},
	{"solution",3,"solve block",solution_ops},
	{"",-1,""}
};

void psdlDescribeSections(void)
{
	unsigned i;
	for(i=0;psdl_sections[i].name[0];i++)
		conPrintf("%s\t - %s.\n",psdl_sections[i].name,psdl_sections[i].desc);
}

vboolean psdlDescribeSubsections(char *section_name)
{
	CALLER_NAME(psdlDescribeSubsections)
	parse_entry_p ptr = NULL;
	unsigned i;
	for(i=0;psdl_sections[i].name[0];i++)
		if(!STR_EQ(psdl_sections[i].name,section_name))
		{
			ptr = psdl_sections[i].subsections;
			break;
		}
	if(!ptr)
	{
		ERROR_INVALID_STR("no such section",section_name);
		return false;
	}
	for(i=0;ptr[i].entry;i++)
		conPrintf("%s\t - %s.\n",ptr[i].name,ptr[i].desc);
	return true;
}

int psdlHelp(char *self,char *help_op)
{
	CALLER_NAME(psdlHelp)
	if(!strcasecmp(help_op,"--help"))
	{
		conPuts("Particle system simulation program ver. 0.49\n");
		conPrintf("Usage: %s <scriptfile>, where scriptfile is an PSDL script.\n",
			self);
		conPuts("For brief description of PSDL, try --help-psdl.\n");
		conPuts("For brief description of distributions, try --help-dist.\n");
		conPuts("For brief description of controllers, try --help-controllers.\n");
		return 0;
	}
	if(!strcasecmp(help_op,"--help-psdl"))
	{
		conPuts("Layout of the PSDL script file:\n");
		conPuts("# <comment>\n");
		conPuts("<section_name>\n{\n");
		conPuts("\toperation1 <parameters>\n");
		conPuts("\toperation2 <parameters>\n");
		conPuts("\t...\n");
		conPuts("\toperationN <parameters>\n");
		conPuts("}\n\n");
		conPuts("...\n\n");
		conPuts("<section_name>\n{\n");
		conPuts("\toperation1 <parameters>\n");
		conPuts("\toperation2 <parameters>\n");
		conPuts("\t...\n");
		conPuts("\toperationN <parameters>\n");
		conPuts("}\n\n");
		conPuts("Valid sections:");
		psdlDescribeSections();
		conPuts("For description of them, try --help-<section_name>");
		return 0;
	}
	if(!strcasecmp(help_op,"--help-controllers"))
	{
		ctlHelp();
		return 0;
	}
	if(!strcasecmp(help_op,"--help-dist"))
	{
		icHelp();
		return 0;
	}
	if(help_op[6] == '-')
	{
		/* assume we get section name */
		return psdlDescribeSubsections(help_op + 7) ? 0 : -1;
	}
	ERROR_INVALID_STR("help topic",help_op);
	return -1;
}

vboolean
psdlSectionOperation(vhandle_t script,particle_system_p ps,char *op,parse_entry_p ops)
{
	CALLER_NAME(psdlSectionOperation)
	u32_t i;
	for(i=0;ops[i].entry;i++)
	{
		if(STR_COMP(op,ops[i].name))
			continue;
		if(!(*(ops[i].entry))(script,ps))
		{
			SYNTAX_EXPECTED(ops[i].desc,script);
			return false;
		}
		return true;
	}
	ERROR_INVALID_STR("invalid section operation",op);
	return false;
}

vboolean
psdlParseSection(vhandle_t script,particle_system_p ps,parse_entry_p ops)
{
	char op[MAX_ID];
	icResetDC(&ps->dc);
	icResetRange(&ps->dc);
	while(lexCopyNextToken(script,op,MAX_ID))
	{
		if(STR_EQ(op,"}"))
			break;
		if(!psdlSectionOperation(script,ps,op,ops))
			return false;
	}
	return true;
}

vboolean
psdlParseSections(vhandle_t script,particle_system_p ps)
{
	CALLER_NAME(psdlParseSections)
	u32_t i;
	s32_t highest_order = 0;
	char section[MAX_ID], op[MAX_ID];
	vboolean result = false;
	while(lexCopyLastToken(script,section,MAX_ID))
	{
		for(i=0;psdl_sections[i].name[0];i++)
		{
			if(STR_COMP(psdl_sections[i].name,section))
				continue;
			if(psdl_sections[i].order >= 0)
			{
				if(psdl_sections[i].order <  highest_order)
				{
					ERROR_INVALID_STR("misordered section",section);
					return false;
				}
			}
			if(!lexCopyNextToken(script,op,MAX_ID))
			{
				ERROR_EOF();
				return false;
			}
			if(STR_EQ(op,"{"))
				result = psdlParseSection(script,ps,psdl_sections[i].subsections);
			else
				result = psdlSectionOperation(script,ps,op,psdl_sections[i].subsections);
			break;
		}
		if(!psdl_sections[i].name[0])
		{
			ERROR_INVALID_STR("wrong section name",section);
			return false;
		}
		/* store section number if greater */
		if(psdl_sections[i].order >= 0)
			highest_order = psdl_sections[i].order;
		if(!result)
			break;
	}
	return true;
}

vboolean
psdlParseScript(char *file_name,particle_system_p ps)
{
	CALLER_NAME(psdlParseScript)
	vboolean result;
	vhandle_t script = lexParseFile(file_name,"{}()");
	if(script == LIBV_INVALID_HANDLE)
	{
		ERROR_INVALID_STR("unable to read file",file_name);
		return false;
	}
	result = psdlParseSections(script,ps);
	lexDestroy(script);
	return result;
}
