#include <../source/TAL/objbase.h>

/* Returns memAlloc()'ed block. */
scene_p V_GLOBAL
meshAlloc(
	u32_t num_vertices,
	u32_t num_texcoords,
	u32_t num_normals,
	u32_t num_faces,
	u32_t num_objects,
	u32_t num_colors,
	u32_t num_maps,
	u32_t num_mtls)
{
	size_t size;
	scene_t *obj;
	size = sizeof(scene_t)+
		num_vertices*sizeof(vec3_t)+
		num_texcoords*sizeof(vec2_t)+
		num_normals*sizeof(vec3_t)+
		num_faces*sizeof(mesh_face_t)+
		num_faces*sizeof(vec3_t)+
		num_faces*sizeof(plane3_t)+
		num_objects*sizeof(mesh_object_t)+
		num_colors*sizeof(color4b_t)+
		num_maps*sizeof(mesh_map_t)+
		num_mtls*sizeof(mesh_mtl_t);
	obj=(scene_t*)memAlloc(size);
	if(!obj)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"meshAlloc(): can't allocate %d bytes.\n",
			size);
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	memZero(obj,size);
	obj->num_vertices=num_vertices;
	obj->num_texcoords=num_texcoords;
	obj->num_normals=num_normals;
	obj->num_faces=num_faces;
	obj->num_objects=num_objects;
	obj->num_colors=num_colors;
	obj->num_maps=num_maps;
	obj->num_mtls=num_mtls;
	obj->vertices=(vec3_t*)(obj+1);
	obj->texcoords=(vec2_t*)(obj->vertices+num_vertices);
	obj->normals=(vec3_t*)(obj->texcoords+num_texcoords);
	obj->faces=(mesh_face_t*)(obj->normals+num_normals);
	obj->face_centers=(vec3_t*)(obj->faces+num_faces);
	obj->face_planes=(plane3_t*)(obj->face_centers+num_faces);
	obj->objects=(mesh_object_t*)(obj->face_planes+num_faces);
	obj->colors=(color4b_t*)(obj->objects+num_objects);
	obj->maps=(mesh_map_t*)(obj->colors+num_colors);
	obj->mtls=(mesh_mtl_t*)(obj->maps+num_maps);
	return obj;
}

/* Sets all counters to zero, but NOT frees any memory. */
void	V_GLOBAL
meshClear(scene_t *mesh)
{
	V_ASSERT(mesh);
	mesh->vertex_count=0;
	mesh->texcoord_count=0;
	mesh->normal_count=0;
	mesh->color_count=0;
	mesh->face_count=0;
	mesh->object_count=0;
	mesh->color_count=0;
	mesh->map_count=0;
	mesh->mtl_count=0;
}

/*
 *	Storing procedures.
 */
void	V_GLOBAL
meshAddVertex(scene_t *mesh,vec3_t vert)
{
	V_ASSERT(mesh && mesh->num_vertices>mesh->vertex_count);
	vec3Copy(mesh->vertices[mesh->vertex_count++],vert);
}

void	V_GLOBAL
meshAddTexcoord(scene_t *mesh,vec2_t texcoord)
{
	V_ASSERT(mesh && mesh->num_texcoords>mesh->texcoord_count);
	vec2Copy(mesh->texcoords[mesh->texcoord_count++],texcoord);
}

void	V_GLOBAL
meshAddNormal(scene_t *mesh,vec3_t normal)
{
	V_ASSERT(mesh && mesh->num_normals>mesh->normal_count);
	vec3Copy(mesh->normals[mesh->normal_count++],normal);
}

void	V_GLOBAL
meshAddColor(scene_t *mesh,color4b_t color)
{
	V_ASSERT(mesh && mesh->num_colors>mesh->color_count);
	rgbaByteCopy(mesh->colors[mesh->color_count++],color);
}

void	V_GLOBAL
meshAddObject(scene_t *mesh,mesh_object_t *object)
{
	V_ASSERT(mesh && mesh->num_objects>mesh->object_count);
	memCopy(&mesh->objects[mesh->object_count++],object,sizeof(mesh_object_t));
}

void	V_GLOBAL
meshAddFace(scene_t *mesh,mesh_face_t *face)
{
	V_ASSERT(mesh && mesh->num_faces>mesh->face_count);
	memCopy(mesh->faces + mesh->face_count++,face,sizeof(mesh_face_t));
}

void	V_GLOBAL
meshAddMap(scene_t *mesh,mesh_map_t *map)
{
	V_ASSERT(mesh && mesh->num_maps>mesh->map_count);
	memCopy(&mesh->maps[mesh->map_count++],map,sizeof(mesh_map_t));
}

void	V_GLOBAL
meshAddMtl(scene_t *mesh,mesh_mtl_t *mtl)
{
	V_ASSERT(mesh && mesh->num_mtls>mesh->mtl_count);
	memCopy(&mesh->mtls[mesh->mtl_count++],mtl,sizeof(mesh_mtl_t));
}

/*
 *	Saves mesh to Wavefront OBJ file.
 */
vboolean	V_GLOBAL
objSave(char *filename,scene_t *obj)
{
	u32_t i, j, fc;
	vhandle_t out;
	if(!filename || !obj)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"objSave(%s,%X): invalid parameters.\n",
			filename ? filename : "null", obj);
#endif	/* CHECKED_BUILD */
		return false;
	}
	out=fileOpenWrite(filename);
	if(!out)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"objSave(%s): can't create file.\n",filename);
#endif	/* CHECKED_BUILD */
		return false;
	}
	filePrintf(out,"# %d vertices, %d texcoords, %d normals, "
		"%d faces, %d objects\n",obj->vertex_count,obj->texcoord_count,
		obj->normal_count,obj->face_count,obj->object_count);
	filePrintf(out,"# Scene bounding box [%f,%f,%f]-[%f,%f,%f]\n",
		obj->mins[0],obj->mins[1],obj->mins[2],
		obj->maxs[0],obj->maxs[1],obj->maxs[2]);
	/* Print all vertices */
	filePrintf(out,"# Vertex list\n");
	for(i=0;i<obj->vertex_count;i++)
		filePrintf(out,"v %g %g %g\n",obj->vertices[i][0],
			obj->vertices[i][1],obj->vertices[i][2]);
	/* Print all texture coords */
	filePrintf(out,"# Texture coord list\n");
	for(i=0;i<obj->texcoord_count;i++)
		filePrintf(out,"vt %g %g 0\n",obj->texcoords[i][0],
			obj->texcoords[i][1]);
	/* Print all normals */
	filePrintf(out,"# Normal list\n");
	for(i=0;i<obj->normal_count;i++)
		filePrintf(out,"vn %g %g %g\n",obj->normals[i][0],
			obj->normals[i][1],obj->normals[i][2]);
	/* Print all objects */
	filePrintf(out,"# Object list\n");
	for(i=fc=0;i<obj->object_count;i++)
	{
		filePrintf(out,"# Bounding box of object %d: [%g,%g,%g]-[%g,%g,%g]\n",
			i,
			obj->objects[i].mins[0],
			obj->objects[i].mins[1],
			obj->objects[i].mins[2],
			obj->objects[i].maxs[0],
			obj->objects[i].maxs[1],
			obj->objects[i].maxs[2]);
		filePrintf(out,"# Pivot point of object %d: [%g,%g,%g]\n",
			i,
			obj->objects[i].pivot[0],
			obj->objects[i].pivot[1],
			obj->objects[i].pivot[2]);
		filePrintf(out,"usemtl %s\n",obj->objects[i].material);
		filePrintf(out,"o %s\n",obj->objects[i].name);
		/* Print object faces */
		if(obj->objects[i].faces_flipped)
		{
			for(j=0;j<obj->objects[i].num_faces;j++,fc++)
				filePrintf(out,"f %d/%d/%d %d/%d/%d %d/%d/%d\n",
					obj->faces[fc][0].vertex,
					obj->faces[fc][0].texcoord,
					obj->faces[fc][0].normal,
	
					obj->faces[fc][2].vertex,
					obj->faces[fc][2].texcoord,
					obj->faces[fc][2].normal,

					obj->faces[fc][1].vertex,
					obj->faces[fc][1].texcoord,
					obj->faces[fc][1].normal
				);
		}
		else
		{
			for(j=0;j<obj->objects[i].num_faces;j++,fc++)
				filePrintf(out,"f %d/%d/%d %d/%d/%d %d/%d/%d\n",
					obj->faces[fc][0].vertex,
					obj->faces[fc][0].texcoord,
					obj->faces[fc][0].normal,
	
					obj->faces[fc][1].vertex,
					obj->faces[fc][1].texcoord,
					obj->faces[fc][1].normal,
	
					obj->faces[fc][2].vertex,
					obj->faces[fc][2].texcoord,
					obj->faces[fc][2].normal
				);
		}
		filePrintf(out,"# %d triangles\n",obj->objects[i].num_faces);
	}
	fileClose(out);
	return true;
}

void V_LOCAL
calculate_scene_bounding_boxes(scene_t *obj)
{
	u32_t i,j;
	u32_t sf, f_e;
	/* Calculate bounding boxes for all objects */
	for(i=0;i<obj->num_objects;i++)
	{
		bboxClear(obj->objects[i].mins,obj->objects[i].maxs);
		sf = obj->objects[i].startface;
		f_e = sf + obj->objects[i].num_faces;
		for(j=sf;j<f_e;j++)
		{
			bboxAddPoint(obj->objects[i].mins,
				obj->objects[i].maxs,
				obj->vertices[obj->faces[j][0].vertex]);
			bboxAddPoint(obj->objects[i].mins,
				obj->objects[i].maxs,
				obj->vertices[obj->faces[j][1].vertex]);
			bboxAddPoint(obj->objects[i].mins,
				obj->objects[i].maxs,
				obj->vertices[obj->faces[j][2].vertex]);
		}
		/* Set pivot to center of object */
		vec3Add(obj->objects[i].pivot,obj->objects[i].mins,obj->objects[i].maxs);
		vec3Scale(obj->objects[i].pivot,obj->objects[i].pivot,0.5);
	}
	/* Calculate bounding box for all geometry */
	bboxClear(obj->mins,obj->maxs);
	for(i=0;i<obj->num_objects;i++)
	{
		bboxAddPoint(obj->mins,obj->maxs,obj->objects[i].mins);
		bboxAddPoint(obj->mins,obj->maxs,obj->objects[i].maxs);
	}
}

void V_LOCAL
calculate_scene_face_parameters(scene_t *obj)
{
	int i,num;
	vec3_t v1,v2,v3,c;
	num = obj->num_faces;
	for(i=0;i<num;i++)
	{
		vec3Copy(v1,obj->vertices[obj->faces[i][0].vertex]);
		vec3Copy(v2,obj->vertices[obj->faces[i][1].vertex]);
		vec3Copy(v3,obj->vertices[obj->faces[i][2].vertex]);
		/* Calculate center of face */
		vec3Add(c,v1,v2);
		vec3Add(c,c,v3);
		vec3Scale(c,c,(vec_t)0.333333333);
		vec3Copy(obj->face_centers[i],c);
		/* Calculate plane equation */
		plane3FromPoints(obj->face_planes+i,v1,v2,v3);
	}
}

V_LOCAL vboolean
parse_vec3(vhandle_t lexer,vec3_t vec_ret)
{
	vboolean res;
	while(lexNextIs(lexer," "));
	res = lexLastFloat(lexer,vec_ret);
	while(lexNextIs(lexer," "));
	res = res && lexLastFloat(lexer,vec_ret + 1);
	while(lexNextIs(lexer," "));
	res = res && lexLastFloat(lexer,vec_ret + 2);
	return res;
}

V_LOCAL vboolean
parse_face_vertex(vhandle_t lexer,s32_t indices[3])
{
	/*
	 * Valid faces:
	 * "num/num/num";
	 * "num//num";
	 * "num//";
	 */
	while(lexNextIs(lexer," ")); /* skip leading spaces */
	if(!lexLastInteger(lexer,indices) || !lexNextIs(lexer,"/"))
		return false;
	if(!lexNextIs(lexer,"/"))
	{
		if(!lexLastInteger(lexer,indices + 1) || !lexNextIs(lexer,"/"))
			return false;
	}
	else
		indices[1] = 0; /* no texture */
	if(!lexNext(lexer) || lexLastWas(lexer," "))
	{
		indices[2] = 0; /* no vertex normal */
	}
	else
		if(!lexLastInteger(lexer,indices + 2))
			return false;
	return true;
}

V_LOCAL vboolean
parse_obj_face(vhandle_t lexer,bstack_p faces)
{
	u32_t count = 0;
	s32_t face[9]; /* only triangular faces supported */
	lexDisable(lexer,LEX_LINE_ADVANCING);
	lexDisable(lexer,LEX_DEFAULT_DELIMITERS);
	while(parse_face_vertex(lexer,face + 3 * count))
	{
		if(count == 2) /* next triangle */
			stackPush(faces,face);
		else
			count++;
	}
	lexEnable(lexer,LEX_LINE_ADVANCING);
	lexEnable(lexer,LEX_DEFAULT_DELIMITERS);
	return (count == 2) ? true : false;
}

typedef struct obj_geomobject_s
{
	char	name[MAX_GEOMOBJECT_NAME];
	char	mtl[MAX_GEOMOBJECT_NAME];
	u32_t	first_face;
}	obj_geomobject_t, *obj_geomobject_p;

#define	STR_EQ(s1,s2)	(!strcmp(s1,s2))

/*
 *	Loads mesh from Wavefront OBJ file.
 */
scene_p V_GLOBAL
objLoad(char *filename)
{
	CALLER_NAME(objLoad)
	vec3_t vert;
	bstack_t v, vt, vn, faces, geomobjects;
	obj_geomobject_t geomobj, *objptr, *endptr;
	u32_t last_face, *faceptr;
	vhandle_t lexer;
	char *token;
	u32_t i, count;
	scene_t *obj;
	char last_mtl[MAX_GEOMOBJECT_NAME];
	lexer = lexParseFile(filename,"/ ");
	if(lexer == LIBV_INVALID_HANDLE)
	{
		ERROR_GENERIC(error_incorrect_parameters);
		return false;
	}
	WARNING_GENERIC("this code still was not debugged !");
	stackInit(&v,sizeof(vec3_t));
	stackInit(&vt,sizeof(vec2_t)); /* we use only s and t */
	stackInit(&vn,sizeof(vec3_t));
	stackInit(&faces,9 * sizeof(u32_t));
	stackInit(&geomobjects,sizeof(obj_geomobject_t));
	strcpy(last_mtl,"default");
	lexSetInteger(lexer,LEX_COMMENT_CHAR,'#');
	lexEnable(lexer,LEX_ONE_CHAR_COMMENTS);
	/*
	 * Load mesh data. Keep value of 'usemtl' because new object starts
	 * only when 'o' found, which is after 'usemtl'.
	 */
	while(lexNext(lexer))
	{
		token = lexLast(lexer);
		if(STR_EQ(token,"v"))
		{
			if(!parse_vec3(lexer,vert))
			{
				SYN_EXPECTED(lexer,"3 vertex coordinates");
				goto err_out;
			}
			stackPush(&v,vert);
			continue;
		}
		if(STR_EQ(token,"vt"))
		{
			if(!parse_vec3(lexer,vert))
			{
				SYN_EXPECTED(lexer,"3 texture coordinates");
				goto err_out;
			}
			stackPush(&vt,vert);
			continue;
		}
		if(STR_EQ(token,"vn"))
		{
			if(!parse_vec3(lexer,vert))
			{
				SYN_EXPECTED(lexer,"3 coordinates of vertex normal");
				goto err_out;
			}
			stackPush(&vn,vert);
			continue;
		}
		if(STR_EQ(token,"f"))
		{
			if(!parse_obj_face(lexer,&faces))
			{
				PARSE_ERROR(lexer,"expected three triples of integer indices");
				goto err_out;
			}
			continue;
		}
		if(STR_EQ(token,"o"))
		{
			if(!lexNext(lexer))
			{
				PARSE_ERROR(lexer,"expected geomobject name");
				goto err_out;
			}
			memZero(&geomobj,sizeof(geomobj));
			lexCopyLastToken(lexer,geomobj.name,MAX_GEOMOBJECT_NAME);
			geomobj.first_face = stackCount(&faces);
			strCopy(geomobj.mtl,last_mtl,MAX_GEOMOBJECT_NAME);
			stackPush(&geomobjects,&geomobj);
			continue;
		}
		if(STR_EQ(token,"usemtl"))
		{
			if(!lexCopyNextToken(lexer,last_mtl,MAX_GEOMOBJECT_NAME))
			{
				PARSE_ERROR(lexer,"expected material name");
				goto err_out;
			}
			continue;
		}
	}
	lexDestroy(lexer);
	if(!stackCount(&geomobjects))
	{
		ERROR_GENERIC("no geomobjects defined");
		goto err_out;
	}
	if(!stackCount(&v))
	{
		ERROR_GENERIC("no vertices defined");
		goto err_out;
	}
	if(!stackCount(&faces))
	{
		ERROR_GENERIC("no faces defined");
		goto err_out;
	}
	if(!stackCount(&vt))
	{
		vec2Zero(vert);
		stackPush(&vt,vert);
	}
	if(!stackCount(&vn))
	{
		vec3Zero(vert);
		stackPush(&vn,vert);
	}
	obj = meshAlloc(stackCount(&v),stackCount(&vt),stackCount(&vn),
		stackCount(&faces),stackCount(&geomobjects),1,1,1);
	obj->vertex_count = stackCount(&v);
	obj->texcoord_count = stackCount(&vt);
	obj->normal_count = stackCount(&vn);
	obj->face_count = stackCount(&faces);
	obj->object_count = stackCount(&geomobjects);
	obj->color_count = 1;
	obj->colors[0][0]=200;
	obj->colors[0][1]=200;
	obj->colors[0][2]=200;
	obj->colors[0][3]=128;
	memCopy(obj->vertices,stackGetStorage(&v),stackCount(&v) * sizeof(vec3_t));
	memCopy(obj->texcoords,stackGetStorage(&vt),stackCount(&vt) * sizeof(vec2_t));
	memCopy(obj->normals,stackGetStorage(&vn),stackCount(&vn) * sizeof(vec3_t));
	stackClear(&v);
	stackClear(&vt);
	stackClear(&vn);
	faceptr = (u32_p)stackGetStorage(&faces);
	for(count = 0;count < obj->num_faces;count++)
	{
		for(i = 0;i < 3;i++)
		{
			obj->faces[count][i].vertex = faceptr[0];
			obj->faces[count][i].texcoord = faceptr[1];
			obj->faces[count][i].normal = faceptr[2];
			faceptr += 3;
		}
	}
	stackClear(&faces);
	objptr = (obj_geomobject_p)stackGetStorage(&geomobjects);
	endptr = objptr + stackCount(&geomobjects);
	for(count = 0;objptr < endptr;count++)
	{
		strCopy(obj->objects[count].name,objptr->name,MAX_GEOMOBJECT_NAME);
		strCopy(obj->objects[count].material,objptr->mtl,MAX_GEOMOBJECT_NAME);
		obj->objects[count].startface = objptr->first_face;
		if((objptr + 1)  < endptr)
			last_face = (objptr+1)->first_face - 1;
		else
			last_face = obj->num_faces - 1;
		obj->objects[count].num_faces = last_face + 1 - obj->objects[count].startface;
		mat3x4Unit(obj->objects[count].xform);
		objptr++;
	}
	stackClear(&geomobjects);
	calculate_scene_bounding_boxes(obj);
	calculate_scene_face_parameters(obj);
	return obj;
err_out:;
	stackClear(&v);
	stackClear(&vt);
	stackClear(&vn);
	stackClear(&faces);
	stackClear(&geomobjects);
	return NULL;
}
