#include "ProxyHeaders.hpp"

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
FindErrorsProcess::FindErrorsProcess():
	enableDuplicateCheck(true),
	enableDegenerateCheck(true),
	enableCoplanarCheck(true),
	enableColinearCheck(true),
	enableConvexCheck(true)
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
FindErrorsProcess::FindErrorsProcess(			
	Stuff::NotationFile *data_file, 
	bool bSuppress,
	void* fcn
	):
	Process(data_file,bSuppress,fcn),
	enableDuplicateCheck(true),
	enableDegenerateCheck(true),
	enableCoplanarCheck(true),
	enableColinearCheck(true),
	enableConvexCheck(true)
{
	Check_Object(data_file);

	Page *page = data_file->FindPage("FindErrors");
	if (page)
	{
		page->GetEntry("DuplicateCheck", &enableDuplicateCheck);
		page->GetEntry("DegenerateCheck", &enableDegenerateCheck);
		page->GetEntry("CoplanarCheck", &enableCoplanarCheck);
		page->GetEntry("ColinearCheck", &enableColinearCheck);
		page->GetEntry("ConvexCheck", &enableConvexCheck);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	PolygonProxy::FindErrors(FindErrorsProcess *process)
{
	Check_Object(this);
	Check_Object(process);

	//
	//---------------------------------------------------------------------
	// Get the area of the polygon.  This will be used to scale some of the
	// error process so they fit with larger polygons
	//---------------------------------------------------------------------
	//
	Scalar area = GetArea();
	Scalar area_edge = Sqrt(area);
	Scalar plane_tolerance = process->planeThicknessTolerance * area_edge;
	Scalar duplicate_tolerance = process->duplicateVertexTolerance;
	Scalar colinear_tolerance = process->colinearTolerance;
	int total = 0;

	//
	//--------------------------------------------
	// Delete all the error groups on this polygon
	//--------------------------------------------
	//
	MStringChain group_list(NULL);
	GetCollections(&group_list);
	MStringChainIterator group_itr(&group_list);
	PlugOf<MString> *group;
	while ((group = group_itr.ReadAndNext()) != NULL)
	{
		Check_Object(group);
		const char* name = group->GetItem();
		Check_Pointer(name);
		if (!_strnicmp(name, "_ERROR_", 7))
			RemoveFromCollection(name);
		Unregister_Object(group);
		delete group;
	}

	//
	//------------------------------------------------------------------------
	// The first task we have is to find the two longest consecutive edges.
	// We will use these edges to compute the desired polygon normal and plane
	// equation
	//------------------------------------------------------------------------
	//
	Point3D
		position_a,
		position_b = Point3D::Identity,
		position_c = Point3D::Identity;
	VertexProxy
		*vertex_a = NULL,
		*vertex_b = NULL,
		*vertex_c = NULL;
	Vector3D
		edge_1,
		edge_2 = Vector3D::Identity;
	Plane
		plane;

	//
	//-----------------------------------------------------------------
	// Put in checks to make sure that the colors, normals, and uvs are
	// compatible for all the vertices in this polygon
	//-----------------------------------------------------------------
	//
	bool
		uv_there = false,
		color_there = false,
		normal_there = false;

	//
	//-----------------------------------
	// Spin through, testing the vertices
	//-----------------------------------
	//
	DynamicArrayOf<IndexProxy*> indices;
	unsigned index_count = UseIndexArray(&indices);
	Verify(index_count == indices.GetLength());
	Verify(index_count >= 3);
	unsigned end=2;
	for (unsigned i=0; i<index_count; ++i)
	{
		//
		//-----------------------------------------------
		// Generate all the information on the first pass
		//-----------------------------------------------
		//
		if (!i)
		{
			Check_Object(indices[0]);
			vertex_a = indices[0]->GetVertexProxy();
			Check_Object(vertex_a);

			Check_Object(indices[1]);
			vertex_b = indices[1]->GetVertexProxy();
			Check_Object(vertex_b);

			Check_Object(indices[2]);
			vertex_c = indices[2]->GetVertexProxy();
			Check_Object(vertex_c);

			vertex_a->GetPosition(&position_a);
			vertex_b->GetPosition(&position_b);
			vertex_c->GetPosition(&position_c);

			edge_1.Subtract(position_b, position_a);
			edge_2.Subtract(position_c, position_b);
			Normal3D normal;
			if (vertex_c->GetNormal(&normal))
			{
				if (!Close_Enough(normal.Vector3D::GetLengthSquared(), 1.0f, 2e-5f))
				{
					AddToCollection("_ERROR_Bad_Normal");
					++total;
					process->FindErrorsCallback(
						this,
						FindErrorsProcess::ERROR_Bad_Normal
					);
					break;
				}
			}

			//
			//---------------------------------
			// Set up the additional attributes
			//---------------------------------
			//
			RGBAColor color;
			color_there = vertex_c->GetColor(&color);
			DynamicArrayOf<Vector2DOf<Scalar> > uv;
			uv_there = vertex_c->GetUVs(&uv);
			normal_there = vertex_c->GetNormal(&normal);
		}

		//
		//--------------------------------------------------------------
		// Get the index info.  If this is not the first pass, copy the
		// information from last pass
		//--------------------------------------------------------------
		//
		else
		{
			Check_Object(vertex_b);
			vertex_a = vertex_b;
			Check_Object(vertex_c);
			vertex_b = vertex_c;
			if (++end >= index_count)
				end -= index_count;
			Check_Object(indices[end]);
			vertex_c = indices[end]->GetVertexProxy();
			Check_Object(vertex_c);

			position_a = position_b;
			position_b = position_c;
			vertex_c->GetPosition(&position_c);

			edge_1 = edge_2;
			edge_2.Subtract(position_c, position_b);

			//
			//--------------------------------
			// Check the additional attributes
			//--------------------------------
			//
			RGBAColor color;
			if (color_there != vertex_c->GetColor(&color))
			{
				AddToCollection("_ERROR_Mismatched_Vertex_Colors");
				++total;
				process->FindErrorsCallback(
					this,
					FindErrorsProcess::ERROR_Mismatched_Vertex_Colors
				);
				break;
			}
			DynamicArrayOf<Vector2DOf<Scalar> > uv;
			if (uv_there != vertex_c->GetUVs(&uv))
			{
				AddToCollection("_ERROR_Mismatched_UVs");
				++total;
				process->FindErrorsCallback(
					this,
					FindErrorsProcess::ERROR_Mismatched_UVs
				);
				break;
			}
			Normal3D normal;
			if (normal_there != vertex_c->GetNormal(&normal))
			{
				AddToCollection("_ERROR_Mismatched_Normals");
				++total;
				process->FindErrorsCallback(
					this,
					FindErrorsProcess::ERROR_Mismatched_Normals
				);
				break;
			}
		}

		//
		//-----------------------------------------------------------------
		// Check the forward leg length to see if the polygon has duplicate
		// points
		//-----------------------------------------------------------------
		//
		Scalar length = edge_2.GetLengthSquared();
		if (
			Small_Enough(length, duplicate_tolerance)
			 && process->enableDuplicateCheck
		)
		{
			AddToCollection("_ERROR_Duplicate_Vertex");
			++total;
			process->FindErrorsCallback(
				this,
				FindErrorsProcess::ERROR_Duplicate_Vertex
			);
			break;
		}

		//
		//---------------------------------------------------------------------
		// Compute the cross-product of the two legs and check for colinearness.
		// This is not necessarily bad, as long as we are coplanar
		//---------------------------------------------------------------------
		//
		Vector3D v;
		v.Cross(edge_1, edge_2);
		Scalar cross_len = v.GetLength();
		if (Small_Enough(cross_len, colinear_tolerance))
		{
			if (!i && process->enableDegenerateCheck)
			{
				AddToCollection("_ERROR_Degenerate_Polygon");
				++total;
				process->FindErrorsCallback(
					this,
					FindErrorsProcess::ERROR_Degenerate_Polygon
				);
				break;
			}
			else if (
				!Small_Enough(plane.GetDistanceTo(position_b), plane_tolerance)
				 && process->enableCoplanarCheck
			)
			{
				AddToCollection("_ERROR_Noncoplanar_Polygon");
				++total;
				process->FindErrorsCallback(
					this,
					FindErrorsProcess::ERROR_Noncoplanar_Polygon
				);
				break;
			}
			continue;
		}

		//
		//-------------------------------------------------------------------
		// Normalize the cross.  If this is the first pass, compute the plane
		// equation for the polygon
		//-------------------------------------------------------------------
		//
		cross_len = 1.0f / cross_len;
		Normal3D edge_normal(v.x*cross_len, v.y*cross_len, v.z*cross_len);
		if (!i)
		{
			plane.normal = edge_normal;
			plane.offset = plane.normal * position_b;
		}
		else
		{
			//
			//--------------------------
			// Check for non-coplanarity
			//--------------------------
			//
			if (
				!Small_Enough(plane.GetDistanceTo(position_b), plane_tolerance)
				 && process->enableCoplanarCheck
			)
			{
				AddToCollection("_ERROR_Noncoplanar_Polygon");
				++total;
				process->FindErrorsCallback(
					this,
					FindErrorsProcess::ERROR_Noncoplanar_Polygon
				);
				break;
			}

			//
			//----------------------
			// Check for concaveness
			//----------------------
			//
			Scalar cosine = plane.normal*edge_normal;
			if (cosine < colinear_tolerance && process->enableConvexCheck)
			{
				AddToCollection("_ERROR_Nonconvex_Polygon");
				++total;
				process->FindErrorsCallback(
					this,
					FindErrorsProcess::ERROR_Noncoplanar_Polygon
				);
				break;
			}
		}
	}

#if 0
	//
	//--------------------------------
	// Check for polygons w/o textures
	//--------------------------------
	//
	if(stateArray.GetLength() < 1)
	{
		AddToCollection("_ERROR_Nontextured_Polygon");
		++total;
		process->FindErrorsCallback(
			this,
			FindErrorsProcess::ERROR_Nontextured_Polygon
		);
	}
#endif

	//
	//-----------------------------
	// Delete the remaining proxies
	//-----------------------------
	//
	DetachArrayReferences(&indices);
	return total;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	TextureProxy::FindErrors(FindErrorsProcess *process)
{
	Check_Object(this);
	Check_Object(process);

	//
	//------------------------------------------------------------
	// Make sure that the process says its OK to check the texture
	//------------------------------------------------------------
	//
	process->FindErrorsCallback(this, FindErrorsProcess::StatusCheck);
	if (!process->continueProcess)
		return 1;

	//
	//---------------------------------------------------------
	// Make sure that the texture is between 32 and 256 in size
	//---------------------------------------------------------
	//
	Vector2DOf<int> size;
	GetImageSize(&size);
	if (size.x<32 || size.x>512 || size.y<32 || size.y>512)
	{
		process->FindErrorsCallback(
			this,
			FindErrorsProcess::ERROR_Bad_Texture_Size
		);
		return 1;
	}

	//
	//--------------------------------------------------------
	// Make sure that we are dealing with a power of 2 texture
	//--------------------------------------------------------
	//
	size.x ^= size.x&(-size.x);
	size.y ^= size.y&(-size.y);
	if (size.x || size.y)
	{
		process->FindErrorsCallback(
			this,
			FindErrorsProcess::ERROR_Bad_Texture_Size
		);
		return 1;
	}
	return 0;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	TextureLibrary::FindErrors(FindErrorsProcess *process)
{
	Check_Object(this);
	Check_Object(process);

	//
	//---------------------------
	// Handle the texture library
	//---------------------------
	//
	int total = 0;
	TextureProxy *texture = UseFirstTextureProxy();
	while (texture)
	{
		Check_Object(texture);
		total += texture->FindErrors(process);
		TextureProxy *next = texture->UseNextTextureProxyInLibrary();
		texture->DetachReference();
		if (!process->continueProcess)
		{
			if (next)
				next->DetachReference();
			return total;
		}
		texture = next;
	}
	return total;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ChildProxy::FindErrors(FindErrorsProcess *process)
{
	Check_Object(this);
	Check_Object(process);
	process->FindErrorsCallback(this, FindErrorsProcess::StatusCheck);
	return (!process->continueProcess) ? 1 : 0;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	GroupProxy::FindErrors(FindErrorsProcess *process)
{
	Check_Object(this);
	Check_Object(process);

	//
	//---------------------------------------------------
	// Make sure that the process says its OK to continue
	//---------------------------------------------------
	//
	process->FindErrorsCallback(this, FindErrorsProcess::StatusCheck);
	if (!process->continueProcess)
		return 1;

	//
	//-----------------------------------------------------
	// Go through each child and count the number of errors
	//-----------------------------------------------------
	//
	int total = 0;
	ChildProxy *child = UseFirstChildProxy();
	while (child)
	{
		Check_Object(child);
		total += child->FindErrors(process);
		ChildProxy *next = child->UseNextSiblingProxy();
		child->DetachReference();
		if (!process->continueProcess)
		{
			if (next)
				next->DetachReference();
			return total;
		}
		child = next;
	}
	return total;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	PolygonMeshProxy::FindErrors(FindErrorsProcess *process)
{
	Check_Object(this);
	Check_Object(process);

	//
	//---------------------------------------------------
	// Make sure that the process says its OK to continue
	//---------------------------------------------------
	//
	process->FindErrorsCallback(this, FindErrorsProcess::StatusCheck);
	if (!process->continueProcess)
		return 1;

	//
	//-----------------------------------
	// Count the number of polygon errors
	//-----------------------------------
	//
	int total = 0;
	DynamicArrayOf<PolygonProxy*> polygons;
	unsigned polygon_count = UsePolygonArray(&polygons);
	Verify(polygon_count == polygons.GetLength());
	if (polygon_count)
	{
		for (unsigned i=0; i<polygon_count; ++i)
		{
			PolygonProxy *polygon = polygons[i];
			Check_Object(polygon);
			total += polygon->FindErrors(process);
		}
	}
	else
	{
		process->FindErrorsCallback(
			this,
			FindErrorsProcess::ERROR_Empty_Mesh
		);
		total = 1;
	}
	DetachArrayReferences(&polygons);
	return total;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	SceneProxy::FindErrors(FindErrorsProcess *process)
{
	Check_Object(this);
	Check_Object(process);

	//
	//---------------------------------------------------
	// Make sure that the process says its OK to continue
	//---------------------------------------------------
	//
	process->FindErrorsCallback(this, FindErrorsProcess::StatusCheck);
	if (!process->continueProcess)
		return 1;

	//
	//-------------------
	// Check the textures
	//-------------------
	//
	int total=0;
	StateLibrary *states = GetStateLibrary();
	Check_Object(states);
	TextureLibrary *textures = states->GetTextureLibrary();
	Check_Object(textures);
	total += textures->FindErrors(process);
	if (!process->continueProcess)
		return total;

	//
	//-----------------
	// Check each child
	//-----------------
	//
	ChildProxy *child = UseFirstChildProxy();
	while (child)
	{
		Check_Object(child);
		total += child->FindErrors(process);
		ChildProxy *next = child->UseNextSiblingProxy();
		child->DetachReference();
		if (!process->continueProcess)
		{
			if (next)
				next->DetachReference();
			break;
		}
		child = next;
	}

	return total;
}
