#include <ElementRenderer\GroupElement.hpp>
#include <ElementRenderer\ListElement.hpp>
#include <ElementRenderer\ShapeElement.hpp>
#include <MLR\MLRShape.hpp>
#include <MLR\MLRIndexedPrimitiveBase.hpp>
#include <Adept\Tile.hpp>

//#define BSP_BUG "jmalbert"
#undef BSP_BUG

extern bool Debug_Tile;

enum {
	Inside_Plane = 1,
	On_Plane = 2,
	Outside_Plane = 4,
	Split_Plane = Inside_Plane|Outside_Plane
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	FindPlanes(
		ElementRenderer::Element *element,
		Stuff::DynamicArrayOf<Stuff::Plane> *planes,
		int *planes_filled
	)
{
	Check_Object(element);
	Check_Object(planes);
	Check_Pointer(planes_filled);

	//
	//-----------------------
	// Spin through the group
	//-----------------------
	//
	if (element->IsDerivedFrom(ElementRenderer::GroupElement::DefaultData))
	{
		ElementRenderer::GroupElement *group =
			Cast_Object(ElementRenderer::GroupElement *, element);
		Stuff::ChainIteratorOf<ElementRenderer::Element*> *children	=
			group->MakeIterator();
		Register_Object(children);
		ElementRenderer::Element *child;
		while ((child = children->ReadAndNext()) != NULL)
		{
			Check_Object(child);
			FindPlanes(child, planes, planes_filled);
		}
		Unregister_Object(children);
		delete children;
	}

	//
	//----------------------
	// Spin through the list
	//----------------------
	//
	else if (element->IsDerivedFrom(ElementRenderer::ListElement::DefaultData))
	{
		ElementRenderer::ListElement *list =
			Cast_Object(ElementRenderer::ListElement *, element);
		for (unsigned i=0; i<list->GetActiveCount(); ++i)
		{
			ElementRenderer::Element *child = list->GetIndexedElement(i);
			Check_Object(child);
			FindPlanes(child, planes, planes_filled);
		}
	}

	//
	//-----------------------
	// Spin through the shape
	//-----------------------
	//
	else if (element->IsDerivedFrom(ElementRenderer::ShapeElement::DefaultData))
	{
		ElementRenderer::ShapeElement *shape =
			Cast_Object(ElementRenderer::ShapeElement *, element);
		MidLevelRenderer::MLRShape *mlr_shape = shape->GetMLRShape();
		Check_Object(mlr_shape);
		for (int p=0; p<mlr_shape->GetNum(); ++p)
		{
			MidLevelRenderer::MLRPrimitiveBase *primitive = mlr_shape->Find(p);
			Check_Object(primitive);

			//
			//----------------------------------------------------------------
			// We have gotten the given primitive, now we need to loop through
			// the faces
			//----------------------------------------------------------------
			//
			const Stuff::Point3D *points;
			int point_count;
			primitive->GetCoordData(&points, &point_count);
			MidLevelRenderer::MLRIndexedPrimitiveBase *indexed_primitive =
				Cast_Object(
					MidLevelRenderer::MLRIndexedPrimitiveBase *,
					primitive
				);
			const unsigned short *indices;
			int index_count;
			indexed_primitive->GetIndexData(&indices, &index_count);
			Verify(!(index_count%3));
			int face_count = index_count/3;
			Verify(face_count == primitive->GetNumPrimitives());

			//
			//----------------------------------------------------------------
			// We have gotten the given primitive, now we need to loop through
			// the faces and get the points of each triangle
			//----------------------------------------------------------------
			//
			int index=0;
			for (int f=0; f<face_count; ++f)
			{
				Verify(index < index_count);
				Verify(static_cast<unsigned>(indices[index]) < point_count);
				const Stuff::Point3D *point1 = &points[indices[index++]];
				Verify(index < index_count);
				Verify(static_cast<unsigned>(indices[index]) < point_count);
				const Stuff::Point3D *point2 = &points[indices[index++]];
				Verify(index < index_count);
				Verify(static_cast<unsigned>(indices[index]) < point_count);
				const Stuff::Point3D *point3 = &points[indices[index++]];

				#if defined(BSP_BUG)
					if (Debug_Tile)
					{
						SPEW((
							BSP_BUG,
							"Tri %d\tv0=<%f,%f,%f>",
							f,
							point1->x,
							point1->y,
							point1->z
						));
						SPEW((
							BSP_BUG,
							"\t v1=<%f,%f,%f>",
							point2->x,
							point2->y,
							point2->z
						));
						SPEW((
							BSP_BUG,
							"\t  v1=<%f,%f,%f>",
							point3->x,
							point3->y,
							point3->z
						));
					}
				#endif

				//
				//---------------------------------------------
				// Figure out the plane equation of the polygon
				//---------------------------------------------
				//
				WORD plane = (*planes_filled)++;
				(*planes)[plane].BuildPlane(
					*point1,
					*point2,
					*point3
				);
				Verify((*planes)[plane].normal.y > Stuff::SMALL);

				//
				//-----------------------------------------------------------
				// Now figure out the plane equation of a vertical plane thru
				// each edge
				//-----------------------------------------------------------
				//
				Stuff::Point3D temp = *point1;
				temp.y += 10.0f;
				(*planes)[(*planes_filled)++].BuildPlane(
					*point1,
					*point2,
					temp
				);
				(*planes)[(*planes_filled)++].BuildPlane(
					*point1,
					temp,
					*point3
				);
				temp = *point2;
				temp.y += 10.0f;
				(*planes)[(*planes_filled)++].BuildPlane(
					temp,
					*point2,
					*point3
				);
			}
		}
	}

	else
		STOP(("Didn't handle this case"));
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	TestTriangles(
		ElementRenderer::Element *element,
		Stuff::DynamicArrayOf<Stuff::Plane> &planes,
		Stuff::DynamicArrayOf<BYTE> *scoreboard,
		int *triangles_tested
	)
{
	Check_Object(element);
	Check_Object(&planes);
	Check_Object(scoreboard);
	Check_Pointer(triangles_tested);

	//
	//-----------------------
	// Spin through the group
	//-----------------------
	//
	if (element->IsDerivedFrom(ElementRenderer::GroupElement::DefaultData))
	{
		ElementRenderer::GroupElement *group =
			Cast_Object(ElementRenderer::GroupElement *, element);
		Stuff::ChainIteratorOf<ElementRenderer::Element*> *children	=
			group->MakeIterator();
		Register_Object(children);
		ElementRenderer::Element *child;
		while ((child = children->ReadAndNext()) != NULL)
		{
			Check_Object(child);
			TestTriangles(child, planes, scoreboard, triangles_tested);
		}
		Unregister_Object(children);
		delete children;
	}

	//
	//----------------------
	// Spin through the list
	//----------------------
	//
	else if (element->IsDerivedFrom(ElementRenderer::ListElement::DefaultData))
	{
		ElementRenderer::ListElement *list =
			Cast_Object(ElementRenderer::ListElement *, element);
		for (unsigned i=0; i<list->GetActiveCount(); ++i)
		{
			ElementRenderer::Element *child = list->GetIndexedElement(i);
			Check_Object(child);
			TestTriangles(child, planes, scoreboard, triangles_tested);
		}
	}

	//
	//-----------------------
	// Spin through the shape
	//-----------------------
	//
	else if (element->IsDerivedFrom(ElementRenderer::ShapeElement::DefaultData))
	{
		ElementRenderer::ShapeElement *shape =
			Cast_Object(ElementRenderer::ShapeElement *, element);
		MidLevelRenderer::MLRShape *mlr_shape = shape->GetMLRShape();
		Check_Object(mlr_shape);
		for (int p=0; p<mlr_shape->GetNum(); ++p)
		{
			MidLevelRenderer::MLRPrimitiveBase *primitive = mlr_shape->Find(p);
			Check_Object(primitive);

			//
			//----------------------------------------------------------------
			// We have gotten the given primitive, now we need to loop through
			// the faces
			//----------------------------------------------------------------
			//
			const Stuff::Point3D *points;
			int point_count;
			primitive->GetCoordData(&points, &point_count);
			MidLevelRenderer::MLRIndexedPrimitiveBase *indexed_primitive =
				Cast_Object(
					MidLevelRenderer::MLRIndexedPrimitiveBase *,
					primitive
				);
			const unsigned short *indices;
			int index_count;
			indexed_primitive->GetIndexData(&indices, &index_count);
			Verify(!(index_count%3));
			int face_count = index_count/3;
			Verify(face_count == primitive->GetNumPrimitives());

			//
			//----------------------------------------------------------------
			// We have gotten the given primitive, now we need to loop through
			// the faces and get the points of each triangle
			//----------------------------------------------------------------
			//
			int index=0;
			int score_index = *triangles_tested * planes.GetLength();
			for (int f=0; f<face_count; ++f, ++(*triangles_tested))
			{
				Verify(index < index_count);
				Verify(static_cast<unsigned>(indices[index]) < point_count);
				const Stuff::Point3D *point1 = &points[indices[index++]];
				Verify(index < index_count);
				Verify(static_cast<unsigned>(indices[index]) < point_count);
				const Stuff::Point3D *point2 = &points[indices[index++]];
				Verify(index < index_count);
				Verify(static_cast<unsigned>(indices[index]) < point_count);
				const Stuff::Point3D *point3 = &points[indices[index++]];

				#if defined(BSP_BUG)
					if (Debug_Tile)
						SPEW((BSP_BUG, "Tri %d\t+", f));
				#endif

				//
				//-----------------------------------------------------
				// Now we need to test these points against every plane
				//-----------------------------------------------------
				//
				for (int t=0; t<planes.GetLength(); ++t, ++score_index)
				{
					Stuff::Scalar dist = planes[t].GetDistanceTo(*point1);
					BYTE &entry = (*scoreboard)[score_index];
					if (dist > 0.02)
						entry = Outside_Plane;
					else if (dist < -0.02)
						entry = Inside_Plane;
					else
						entry = On_Plane;
					dist = planes[t].GetDistanceTo(*point2);
					if (dist > 0.02)
						entry |= Outside_Plane;
					else if (dist < -0.02)
						entry |= Inside_Plane;
					else
						entry |= On_Plane;
					dist = planes[t].GetDistanceTo(*point3);
					if (dist > 0.02)
						entry |= Outside_Plane;
					else if (dist < -0.02)
						entry |= Inside_Plane;
					else
						entry |= On_Plane;

					//
					//---------------------------------------------------------
					// If we are on both sides of the plane, count us as on the
					// plane
					//---------------------------------------------------------
					//
					if ((entry&Split_Plane) == Split_Plane)
						entry |= On_Plane;
					Verify(entry != On_Plane || !(t&3));

					#if defined(BSP_BUG)
						if (Debug_Tile)
							if ((t&3) == 3)
								SPEW((BSP_BUG, "%d +", entry));
							else
								SPEW((BSP_BUG, "%d+", entry));
					#endif
				}
				#if defined(BSP_BUG)
					if (Debug_Tile)
						SPEW((BSP_BUG, ""));
				#endif
			}
		}
	}

	else
		STOP(("Didn't handle this case"));
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
float
	ComputeScore(
		int outsides,
		int insides,
		int splits,
		int total
	)
{
	//
	//-----------------------------------------------------------------------
	// The plane is useless if no triangles are reduced on either side of the
	// plane, or if this is a real polygon plane and it splits another
	// polygon
	//-----------------------------------------------------------------------
	//
	float score;
	if (
		total == outsides || total == insides
		 || (splits>0 && total>outsides+insides-splits)
	)
		score = 3.0f * total;

	//
	//---------------------------------------------------------------------
	// Otherwise, base the score on the sum of the child triangle count and
	// how balanced it is, giving a slight bias towards load over balance
	//---------------------------------------------------------------------
	//
	else
	{
		float balance = Stuff::Fabs(static_cast<float>(outsides - insides));
		score = outsides + insides + 0.9f*balance;
	}

	#if defined(BSP_BUG)
		if (Debug_Tile)
			SPEW((
				BSP_BUG,
				"o:%d i:%d s:%d -> %f",
				outsides,
				insides,
				splits,
				score
			));
	#endif
	return score;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	SortTriangles(
		Stuff::DynamicArrayOf<Adept::TerrainBSP> *bsp,
		WORD *bsp_count,
		Stuff::DynamicArrayOf<Stuff::Plane> *used_planes,
		WORD *used_plane_count,
		Stuff::DynamicArrayOf<BYTE> &scoreboard,
		Stuff::DynamicArrayOf<Stuff::Plane> &reference_planes,
		Stuff::DynamicArrayOf<int> &triangles
	)
{
	Check_Object(bsp);
	Check_Pointer(bsp_count);
	Check_Object(&scoreboard);
	Check_Object(&reference_planes);
	Check_Object(&triangles);

	//
	//---------------------------------------------------------------------
	// Our first task is to compile the statistics for each of the possible
	// splitting planes
	//---------------------------------------------------------------------
	//
	int plane_count = reference_planes.GetLength();
	Stuff::DynamicArrayOf<int>
		insides(plane_count),
		outsides(plane_count),
		splits(plane_count);

	unsigned triangle_count = triangles.GetLength();
	int p,t;

	#if defined(BSP_BUG)
		if (Debug_Tile)
		{
			SPEW((BSP_BUG, "BSP %d:", *bsp_count));
			SPEW((BSP_BUG, "\t+"));
			for (t=0; t<triangles.GetLength(); ++t)
				SPEW((BSP_BUG, "%d +", triangles[t]));
			SPEW((BSP_BUG, ""));
		}
	#endif

	for (p=0; p<plane_count; ++p)
	{
		insides[p] = 0;
		outsides[p] = 0;
		splits[p] = 0;

		//
		//--------------------------------------------------------------------
		// Test each of the test triangles against the chosen plane and sum up
		// the results
		//--------------------------------------------------------------------
		//
		for (t=0; t<triangle_count; ++t)
		{
			switch (scoreboard[triangles[t]*plane_count + p])
			{
			case Outside_Plane:
			case Outside_Plane|On_Plane:
				++outsides[p];
				break;

			case Inside_Plane:
			case Inside_Plane|On_Plane:
				++insides[p];
				break;

			case On_Plane:
				Verify(!(p&3));
				break;

			case Split_Plane|On_Plane:
				++outsides[p];
				++insides[p];
				++splits[p];
				break;

			default:
				STOP(("Shouldn't happen"));
				break;
			}
		}
	}

	//
	//------------------------------------------------------------------------
	// Now that we have our results, we need to spin through the table looking
	// for the best splitting plane
	//------------------------------------------------------------------------
	//
	int best_plane = -1;
	float best_score = 3.0f*triangle_count;
	int b_o, b_i, b_s;
	for (p=0; p<plane_count; ++p)
	{
		#if defined(BSP_BUG)
			if (Debug_Tile)
				SPEW((BSP_BUG, "\t\t%d -> +", p));
		#endif
		float score =
			ComputeScore(outsides[p], insides[p], splits[p], triangle_count);
		if (score < best_score)
		{
			best_plane = p;
			best_score = score;
			b_o = outsides[p];
			b_i = insides[p];
			b_s = splits[p];
		}
	}
	#if defined(BSP_BUG)
		if (Debug_Tile)
			SPEW((
				BSP_BUG,
				"\t%d -> O:%d I:%d S:%d = %f ====",
				best_plane,
				b_o,
				b_i,
				b_s,
				best_score
			));
	#endif

	//
	//-----------------------------------------------------
	// We couldn't sort the rest so best plane will be zero
	//-----------------------------------------------------
	//
	if (best_plane < 0)
	{
		best_plane = 0;
		WORD new_plane;
		for (new_plane=0; new_plane<*used_plane_count; ++new_plane)
		{
			if (
				!memcmp(
					&(*used_planes)[new_plane],
					&reference_planes[best_plane],
					sizeof(Stuff::Plane)
				)
			)
				break;
		}
		if (new_plane == *used_plane_count)
		{
			new_plane = (*used_plane_count)++;
			(*used_planes)[new_plane] = reference_planes[best_plane];
		}
		int new_bsp = (*bsp_count)++;
		(*bsp)[new_bsp].m_planeIndex = new_plane;
		(*bsp)[new_bsp].m_innerIndex = 0;
		(*bsp)[new_bsp].m_outerIndex = 0;
		return;
	}

	Verify(best_plane>=0 && best_plane<plane_count);
	if (best_score == 3.0f*triangle_count)
		STOP(("Can't sort this tree!"));

	//
	//-------------------------------------------------------
	// Search the used plane array to see if the plane exists
	//-------------------------------------------------------
	//
	WORD new_plane;
	for (new_plane=0; new_plane<*used_plane_count; ++new_plane)
	{
		if (
			!memcmp(
				&(*used_planes)[new_plane],
				&reference_planes[best_plane],
				sizeof(Stuff::Plane)
			)
		)
			break;
	}
	if (new_plane == *used_plane_count)
	{
		new_plane = (*used_plane_count)++;
		(*used_planes)[new_plane] = reference_planes[best_plane];
	}

	//
	//-------------------------------------------
	// Copy this plane into the used planes array
	//-------------------------------------------
	//
	int new_bsp = (*bsp_count)++;
	(*bsp)[new_bsp].m_planeIndex = new_plane;
	(*bsp)[new_bsp].m_innerIndex = 0;
	(*bsp)[new_bsp].m_outerIndex = 0;

	//
	//----------------------------------------------------------------
	// If all of the triangles are contained in the plane, we are done
	//----------------------------------------------------------------
	//
	if (!insides[best_plane] && !outsides[best_plane])
		return;

	//
	//--------------------------------------
	// Split up the triangles into two lists
	//--------------------------------------
	//
	int inside_count = insides[best_plane];
	Stuff::DynamicArrayOf<int> inside_triangles(inside_count);
	inside_count = 0;
	int outside_count = outsides[best_plane];
	Stuff::DynamicArrayOf<int> outside_triangles(outside_count);
	outside_count = 0;
	for (t=0; t<triangles.GetLength(); ++t)
	{
		BYTE flag = scoreboard[triangles[t]*plane_count + best_plane];
		if (flag&Outside_Plane)
			outside_triangles[outside_count++] = triangles[t];
		if (flag&Inside_Plane)
			inside_triangles[inside_count++] = triangles[t];
	}
	Verify(outside_triangles.GetLength() == outside_count);
	Verify(inside_triangles.GetLength() == inside_count);

	//
	//--------------------------
	// Recurse the inside branch
	//--------------------------
	//
	if (inside_count > 0)
	{
		(*bsp)[new_bsp].m_innerIndex = *bsp_count;
		#if defined(BSP_BUG)
			if (Debug_Tile)
				SPEW((BSP_BUG, "\tBSP %d.innerIndex = %d", new_bsp, *bsp_count));
		#endif
		SortTriangles(
			bsp,
			bsp_count,
			used_planes,
			used_plane_count,
			scoreboard,
			reference_planes,
			inside_triangles
		);
	}
	else
	{
		Verify(outside_count < triangles.GetLength());
		Verify(!(best_plane&3));
	}

	//
	//--------------------------
	// Recurse the outside branch
	//--------------------------
	//
	if (outside_count > 0)
	{
		(*bsp)[new_bsp].m_outerIndex = *bsp_count;
		#if defined(BSP_BUG)
			if (Debug_Tile)
				SPEW((BSP_BUG, "\tBSP %d.outerIndex = %d", new_bsp, *bsp_count));
		#endif
		SortTriangles(
			bsp,
			bsp_count,
			used_planes,
			used_plane_count,
			scoreboard,
			reference_planes,
			outside_triangles
		);
	}
	else
	{
		Verify(inside_count < triangles.GetLength());
		Verify(!(best_plane&3));
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MakeBSP(
		Stuff::DynamicArrayOf<Stuff::Plane> *planes,
		WORD *plane_count,
		ElementRenderer::GroupElement *tile,
		Stuff::MemoryStream *stream
	)
{
	Check_Object(tile);
	Check_Object(stream);

	//
	//------------------------------------------------------------------------
	// Count the number of triangles in the tile.  We will end up computing
	// four planes per triangle, one for the face, and one vertical plane thru
	// each edge
	//------------------------------------------------------------------------
	//
	int tri_count = tile->CountTriangles();
	int ref_plane_count = 4*tri_count;
	Stuff::DynamicArrayOf<Stuff::Plane> reference_planes(ref_plane_count);
	int planes_filled = 0;
	FindPlanes(tile, &reference_planes, &planes_filled);
	Verify(planes_filled == ref_plane_count);

	//
	//-----------------------------------------------------------------------
	// We now need to fill the scoreboard up with the poly to plane test data
	//-----------------------------------------------------------------------
	//
	int total_tests = tri_count * ref_plane_count;
	Stuff::DynamicArrayOf<BYTE> scoreboard(total_tests);
	int triangles_tested = 0;
	TestTriangles(tile, reference_planes, &scoreboard, &triangles_tested);
	Verify(triangles_tested = tri_count);

	//
	//------------------------------------------------------------------------
	// Now build the triangle index list in order to prime the sorting process
	//------------------------------------------------------------------------
	//
	Stuff::DynamicArrayOf<int> triangles(tri_count);
	for (int t=0; t<tri_count; ++t)
		triangles[t] = t;
	Stuff::DynamicArrayOf<Adept::TerrainBSP> bsp(ref_plane_count);
	WORD bsp_count = 0;

	//
	//---------------
	// Sort the puppy
	//---------------
	//
	SortTriangles(
		&bsp,
		&bsp_count,
		planes,
		plane_count,
		scoreboard,
		reference_planes,
		triangles
	);
	Verify(bsp_count < bsp.GetLength());

	//
	//-------------------
	// Write out the data
	//-------------------
	//
	*stream << bsp_count;
	stream->WriteBytes(
		bsp.GetData(),
		bsp_count*sizeof(Adept::TerrainBSP)
	);
}
