#include "psmod.h"

/*
 * Half-space enumeration
 */
typedef enum
{
	icsg_inside,
	icsg_outside
}	icsg_halfspace_t;

/*
 * Ancestor for all geomobjects used while applying initial conditions
 */
typedef struct icsg_geomobject_s
{
	/* returns 'true' if 'pos' is deeper than 'radius' in half-space 'halfspace' */
	vboolean (*check)(void_p self,vec3_t pos,vec_t radius,icsg_halfspace_t halfspace);
	/* frees geomobject */
	void (*destroy)(void_p self);
}	icsg_geomobject_t, *icsg_geomobject_p;

/*
 * Factory function for all descendants; it determines the type
 * of geomobject and than calls appropriate constructor.
 */
V_LOCAL icsg_geomobject_p
icsg_parse_geomobject(vhandle_t script);

/*
 * Valid nodes for spatial constrains
 */
typedef enum
{
	icsg_union,			/* CSG boolean */
	icsg_intersection,	/* CSG boolean */
	icsg_subtraction,	/* CSG boolean */
	
	icsg_geomobject,	/* Geomobject */
	icsg_max_operation
}	icsg_operation_t;

/*
 * Following structure incapsulates name, description and
 * constructor for CSG operation.
 */
typedef struct icsg_operation_parse_entry_s
{
	icsg_operation_t operation;
	char *name;
	char *description;
}	icsg_operation_parse_entry_t, *icsg_operation_parse_entry_p;

/*
 * Spatial constrain node
 */
typedef struct icsg_node_info_s
{
	icsg_operation_t op;
	icsg_geomobject_p geomobject;
}	icsg_node_info_t, *icsg_node_info_p;

#define	ICSG_OP(name,desc)	{icsg_##name,#name,desc}

/*
 * Known CSG operations
 */
V_LOCAL icsg_operation_parse_entry_t icsg_ops[] =
{
	ICSG_OP(union,"union of two objects"),
	ICSG_OP(intersection,"intersection of two objects"),
	ICSG_OP(subtraction,"subtraction of operand #2 from operand #1"),
	{icsg_max_operation,NULL,NULL}
};

#undef	ICSG_OP

/*
 * Reads next token from 'script', stores operation type in op_ret.
 */

V_LOCAL vboolean
icsg_get_operand_type(vhandle_t script,icsg_operation_t *op_ret)
{
	int i;
	char *token = lexNext(script);
	if(!token)
		return false;
	for(i=0;icsg_ops[i].name;i++)
		if(STR_EQ(icsg_ops[i].name,token))
		{
			*op_ret = icsg_ops[i].operation;
			return true;
		}
	*op_ret = icsg_geomobject;
	return true;
}

V_LOCAL void
icsg_operation_help(void)
{
	int i;
	conPrintf("Known CSG operations:\n");
	for(i=0;icsg_ops[i].name;i++)
		conPrintf("\t\"%s\": %s.\n",icsg_ops[i].name,icsg_ops[i].description);
}

void V_CALL treenode_destroy(void *data)
{
	icsg_node_info_p node_info = (icsg_node_info_p)data;
	if(node_info && node_info->geomobject)
		node_info->geomobject->destroy(node_info->geomobject);
}

V_LOCAL btree_p
icsgParseOperand(btree_p root_node,int child_index,vhandle_t script)
{
	CALLER_NAME(icsgParseOperand)
	btree_p node = NULL;
	icsg_node_info_t node_info;
	if(!icsg_get_operand_type(script,&node_info.op))
		return NULL;
	if(node_info.op == icsg_geomobject)
	{
		/* This becomes a leaf node */
		node_info.geomobject = icsg_parse_geomobject(script);
		if(node_info.geomobject)
			return btStore(&node_info,sizeof(icsg_node_info_t),root_node,child_index);
		return NULL;
	}
	node_info.geomobject = NULL;
	node = btStore(&node_info,sizeof(icsg_node_info_t),root_node,child_index);
	/* parse child nodes */
	if(lexNextIs(script,"{"))
	{
		if(icsgParseOperand(node,0,script) && icsgParseOperand(node,1,script))
		{
			if(lexNextIs(script,"}"))
				return node;
			else
				SYNTAX_EXPECTED("}",script);
		}
		else
			ERROR_GENERIC("Error parsing CSG operands");
	}
	else
		SYNTAX_EXPECTED("{",script);
	btDeleteAllNodes(node,treenode_destroy);
	return NULL;
}

/*
 * Main routine
 */
V_LOCAL vboolean
icsgCheckAgainstNode(btree_p root,icsg_halfspace_t halfspace,vec3_t pos,vec_t radius)
{
	CALLER_NAME(icsgCheckAgainstNode)
	icsg_node_info_p ni;
	if(!root) /* test always passes if no constrains defined */
		return true;
	ni = (icsg_node_info_p)(root->data);
	switch(ni->op)
	{
		case icsg_geomobject:
			return ni->geomobject->check(ni->geomobject,pos,radius,halfspace);
		case icsg_union:
			if(halfspace == icsg_inside)
			{
				return icsgCheckAgainstNode(root->children[0],icsg_inside,pos,radius) ||
					icsgCheckAgainstNode(root->children[1],icsg_inside,pos,radius);
			}
			else
			{
				return icsgCheckAgainstNode(root->children[0],icsg_outside,pos,radius) &&
					icsgCheckAgainstNode(root->children[1],icsg_outside,pos,radius);
			}
		case icsg_intersection:
			if(halfspace == icsg_inside)
			{
				return icsgCheckAgainstNode(root->children[0],icsg_inside,pos,radius) &&
					icsgCheckAgainstNode(root->children[1],icsg_inside,pos,radius);
			}
			else
			{
				return icsgCheckAgainstNode(root->children[0],icsg_outside,pos,radius) ||
					icsgCheckAgainstNode(root->children[1],icsg_outside,pos,radius);
			}
		case icsg_subtraction:
			if(halfspace == icsg_inside)
			{
				return icsgCheckAgainstNode(root->children[0],icsg_inside,pos,radius) &&
					icsgCheckAgainstNode(root->children[1],icsg_outside,pos,radius);
			}
			else
			{
				return icsgCheckAgainstNode(root->children[0],icsg_outside,pos,radius) ||
					icsgCheckAgainstNode(root->children[1],icsg_inside,pos,radius);
			}
		default:
			ERROR_GENERIC("Unknown CSG operation");
			break;
	}
	return false;
}

/*
 * "Plane" geomobject
 */

typedef struct icsg_plane_s
{
	icsg_geomobject_t base;
	vec3_t	normal;
	vec_t	dist;
}	icsg_plane_t, *icsg_plane_p;

V_LOCAL vboolean
plane_check(void_p self,vec3_t pos,vec_t radius,icsg_halfspace_t halfspace)
{
	icsg_plane_p plane = (icsg_plane_p)self;
	vec_t delta = vec3Dot(pos,plane->normal) - plane->dist;
	if(halfspace == icsg_inside) /* should be in positive halfspace */
		return (delta > radius) ? true : false;
	return (delta < -radius) ? true : false;
}

V_LOCAL void
plane_destroy(void_p self)
{
	memFree(self);
}

V_LOCAL icsg_geomobject_p
parse_plane(vhandle_t script)
{
	CALLER_NAME(parse_plane)
	icsg_plane_p plane;
	vec3_t normal;
	vec_t nlen, dist;
	if(!icParseVec3Table(normal,sizeof(vec3_t),1,NULL,script))
	{
		SYNTAX_EXPECTED("const vec3 table[1] normal",script);
		return NULL;
	}
	nlen = vec3Abs(normal);
	if(IsZero(nlen))
	{
		ERROR_GENERIC("degenerate normal");
		return NULL;
	}
	vec3Scale(normal,normal,1 / nlen);
	if(!lexNextFloat(script,&dist))
	{
		SYNTAX_EXPECTED("float dist",script);
		return NULL;
	}
	plane = (icsg_plane_p)memAllocZ(sizeof(icsg_plane_t));
	plane->base.check = plane_check;
	plane->base.destroy = plane_destroy;
	vec3Copy(plane->normal,normal);
	plane->dist = dist;
	return &plane->base;
}

/*
 * "Box" geomobject
 */

V_LOCAL vboolean
box_check(void_p self,vec3_t pos,vec_t radius,icsg_halfspace_t halfspace)
{
	return false;
}

V_LOCAL void
box_destroy(void_p self)
{
	memFree(self);
}

V_LOCAL icsg_geomobject_p
parse_box(vhandle_t script)
{
	CALLER_NAME(parse_box)
	ERROR_GENERIC("not implemented");
	return NULL;
}

/*
 * "Sphere" geomobject
 */

V_LOCAL vboolean
sphere_check(void_p self,vec3_t pos,vec_t radius,icsg_halfspace_t halfspace)
{
	return false;
}

V_LOCAL void
sphere_destroy(void_p self)
{
	memFree(self);
}

V_LOCAL icsg_geomobject_p
parse_sphere(vhandle_t script)
{
	CALLER_NAME(parse_sphere)
	ERROR_GENERIC("not implemented");
	return NULL;
}

/*
 * "Cylinder" geomobject
 */

V_LOCAL vboolean
cylinder_check(void_p self,vec3_t pos,vec_t radius,icsg_halfspace_t halfspace)
{
	return false;
}

V_LOCAL void
cylinder_destroy(void_p self)
{
	memFree(self);
}

V_LOCAL icsg_geomobject_p
parse_cylinder(vhandle_t script)
{
	CALLER_NAME(parse_cylinder)
	ERROR_GENERIC("not implemented");
	return NULL;
}

/*
 * Following structure incapsulates name, description and
 * constructor for geomobject.
 */
typedef struct geomobject_parse_entry_s
{
	char *name;
	icsg_geomobject_p (*parse)(vhandle_t script);
	char *description;
}	geomobject_parse_entry_t, *geomobject_parse_entry_p;

#define	GEOMOBJ(name,desc) {#name,parse_##name,desc}

/*
 * Known geomobjects
 */
V_LOCAL geomobject_parse_entry_t icsg_geomobjects[] =
{
	GEOMOBJ(plane,"plane constrain"),
	GEOMOBJ(box,"box constrain"),
	GEOMOBJ(sphere,"sphere constrain"),
	GEOMOBJ(cylinder,"cylinder constrain"),
	{NULL,NULL,NULL}
};

#undef	GEOMOBJ

V_LOCAL void
icsg_geomobjects_help(void)
{
	int i;
	conPrintf("Known geomobjects:\n");
	for(i=0;icsg_geomobjects[i].name;i++)
		conPrintf("\t\"%s\": %s.\n",icsg_geomobjects[i].name,
			icsg_geomobjects[i].description);
}

V_LOCAL icsg_geomobject_p
icsg_parse_geomobject(vhandle_t script)
{
	CALLER_NAME(icsg_parse_geomobject)
	int i;
	char *token;
	/* Assume we called after icsg_get_operand_type() */
	token = lexLast(script);
	for(i=0;icsg_geomobjects[i].name;i++)
		if(STR_EQ(icsg_geomobjects[i].name,token))
			return icsg_geomobjects[i].parse(script);
	return NULL;
}

/* DEBUG */

void icsg_main(void)
{
	icsg_operation_help();
	icsg_geomobjects_help();
	{
		char *test = 
		"spatial intersection\n"
		"{\n"
		"	subtraction\n"
		"	{\n"
		"		sphere [0,0,0] 10e-6\n"
		"		sphere [0,0,0] 3e-6\n"
		"	}\n"
		"	box [0,0,5e-6;10e-6,10e-6,10e-6]\n"
		"}";
		vhandle_t lexer = lexParseString(test,"{}[]()");
		// ...
		lexDestroy(lexer);
	}
}
