#include "ProxyHeaders.hpp"
typedef int (*LPERROR_CALLBACKFN)(char *,bool);

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CoalesceTexturesProcess::CoalesceTexturesProcess(
	Stuff::NotationFile *megatexture_file,
	const char *megatexture_list,
	bool bSuppress,
	void* fcn
) :
	Process(NULL,bSuppress,fcn),
	megatextureFile(megatexture_file)
{
	if (megatexture_list)
	{ 
		megatextureList = MakeMegatextureChain(megatexture_list);
		Register_Object(megatextureList);
		Check_Object(megatextureFile);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CoalesceTexturesProcess::CoalesceTexturesProcess(
	Stuff::NotationFile *megatexture_file,
	const char *megatexture_list,
	Stuff::NotationFile *config_file,
	bool bSuppress,
	void* fcn
):
	Process(config_file,bSuppress,fcn),
	megatextureFile(megatexture_file)
{
	if (megatexture_list)
	{
		megatextureList = MakeMegatextureChain(megatexture_list);
		Register_Object(megatextureList);
		Check_Object(megatextureFile);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CoalesceTexturesProcess::~CoalesceTexturesProcess()
{
	if (megatextureList)
	{
		MStringChainIterator iterator(megatextureList);
		iterator.DeletePlugs();
		Unregister_Object(megatextureList);
		delete megatextureList;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
MStringChain*
	CoalesceTexturesProcess::MakeMegatextureChain(const char* list)
{
	Check_Pointer(list);

	//
	//--------------------------------------------
	// If the string is empty, don't make anything
	//--------------------------------------------
	//
	MStringChain *chain = NULL;
	const char* begin = list;
	do
	{
		//
		//-------------------------------------------------------------------
		// If we can't find a comma, the remainder of the string is the token
		//-------------------------------------------------------------------
		//
		const char* end = strchr(begin, ',');
		MString token = begin;
		if (!end)
			begin = NULL;

		//
		//------------------------------------------------------
		// Otherwise, clip the token to where we found the comma
		//------------------------------------------------------
		//
		else
		{
			int len = end - begin;
			token[len] = '\0';
			begin = end;
			while (*begin == ',')
				++begin;
			if (!*begin)
				begin = NULL;
		}

		//
		//----------------------------
		// Put this token in the chain
		//----------------------------
		//
		if (!chain)
			chain = new MStringChain(NULL);
		PlugOf<MString> *plug = new PlugOf<MString>(token);
		Register_Object(plug);
		chain->Add(plug);

		//
		//----------------------------------
		// Skip to the next token until done
		//----------------------------------
		//
	} while (begin);
	return chain;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	PolygonMeshProxy::CoalesceTextures(CoalesceTexturesProcess *process)
{
	Check_Object(this);
	Check_Object(process);

	//
	//------------------------------------------------------------
	// Make sure that the process says its OK to check the texture
	//------------------------------------------------------------
	//
	process->CoalesceTexturesCallback(this);
	if (!process->continueProcess)
		return;

	//
	//--------------------------------------
	// See if this will be a trivial process
	//--------------------------------------
	//
	if (!process->megatextureList)
		return;
	Check_Object(process->megatextureFile);
	Check_Object(process->megatextureFile);

	//
	//------------------------------------------------------------------------
	// Get the state arrays for this mesh and make sure that they have already
	// been split
	//------------------------------------------------------------------------
	//
	DynamicArrayOf<PolygonProxy*> polygons;
	unsigned polygon_count = UsePolygonArray(&polygons);
	if (!polygon_count)
		return;
	DynamicArrayOf<MultiState*> states;
	DynamicArrayOf<unsigned> index;
	DynamicArrayOf<unsigned> count;
	#if defined(_ARMOR)
		unsigned unique_states =
	#endif
		UseMultiStateArray(&states, &index, &count, polygons);
	Verify(unique_states==1 && count[0]==polygon_count);

	//
	//----------------------------------------------------------------------
	// If this mesh is not textured, we can just return.  Otherwise, get the
	// texture name
	//----------------------------------------------------------------------
	//
	Check_Object(states[0]);
	unsigned state_count = states[0]->GetLength();

	for(unsigned state_loop=0;state_loop<state_count;++state_loop)
	{
		TextureProxy *texture = (*states[0])[state_loop]->UseTextureProxy();
		if (!texture)
		{
Unwind_1:
			DetachArrayReferences(&states);
			DetachArrayReferences(&polygons);
			return;
#undef UNWIND
#define UNWIND() goto Unwind_1
		}

		MString name;
		texture->GetName(&name);
		Vector2DOf<int> texture_size;
		texture->GetImageSize(&texture_size);
		texture->DetachReference();

		//
		//------------------------------------------------------------------------
		// Look through each of the listed megatextures to try and find where this
		// texture should be rerouted
		//------------------------------------------------------------------------
		//
		MStringChainIterator megatextures(process->megatextureList);
		PlugOf<MString> *string;
		Vector2DOf<Scalar> offset;
		offset.x = offset.y = 0.0f;
		const char* mega_name = NULL;
		while ((string = megatextures.ReadAndNext()) != NULL)
		{
			mega_name = string->GetItem();
			Check_Pointer(mega_name);
			const char *offset_string;
			Page *page = process->megatextureFile->FindPage(mega_name);
			if (page)
			{
				if (page->GetEntry(name, &offset_string))
				{
					Check_Pointer(offset_string);
					#if defined(_ARMOR)
						int count =
					#endif
					sscanf(offset_string, "%f %f", &offset.x, &offset.y);
					Verify(count == 2);
					break;
				}
			}
			// Look for alias
			page = process->megatextureFile->FindPage(name);
			if (page)
			{
				const char *alias_name;
				if (page->GetEntry("alias",&alias_name))
				{
					page = process->megatextureFile->FindPage(alias_name);
					if (page)
					{
						if (page->GetEntry(name, &offset_string))
						{
							Check_Pointer(offset_string);
							#if defined(_ARMOR)
								int count =
							#endif
							sscanf(offset_string, "%f %f", &offset.x, &offset.y);
							Verify(count == 2);
							break;
						}
					}

				}
			}

		}

		//
		//-----------------------------------------------------------------------
		// If no megatexture has this texture in its entries, this texture should
		// be left alone
		//-----------------------------------------------------------------------
		//
		if (!string)
			UNWIND();

		//
		//-----------------------------------------------------------------------
		// Now, get the megatexture from the library.  If it isn't already there,
		// an empty one will be created
		//-----------------------------------------------------------------------
		//
		SceneProxy *scene = GetSceneProxy();
		Check_Object(scene);
		StateLibrary *state_library = scene->GetStateLibrary();
		Check_Object(state_library);
		TextureLibrary *textures = state_library->GetTextureLibrary();
		Check_Object(textures);
		TextureProxy *mega_texture = textures->UseTextureProxy(mega_name);

		//
		//----------------------------------------
		// Read the size from the megatexture page
		//----------------------------------------
		//
		const char* size_string;
		Page *page = process->megatextureFile->GetPage(mega_name);
		Check_Object(page);
		page->GetEntry("PageSize", &size_string);
		Vector2DOf<int> mega_size;
		#if defined(_ARMOR)
			int param_count =
		#endif
		sscanf(size_string, "%d %d", &mega_size.x, &mega_size.y);
		Verify(param_count == 2);

		//
		//---------------------------------------------
		// Compute the new scale and offset for our uvs
		//---------------------------------------------
		//
		Vector2DOf<Scalar>
			pixel_size(1.0f/mega_size.x, 1.0f/mega_size.y),
			pixel_count(
				static_cast<Scalar>(texture_size.x),
				static_cast<Scalar>(texture_size.y)
			);

		//
		//--------------------------------------------------------------------
		// Get the array of vertices, then go through each one and set the UVs
		// according to the coalesced values
		//--------------------------------------------------------------------
		//
		DynamicArrayOf<VertexProxy*> vertices;
		unsigned vertex_count = UseVertexArray(&vertices);
		for (unsigned i=0; i<vertex_count; ++i)
		{
			Check_Object(vertices[i]);
			DynamicArrayOf<Vector2DOf<Scalar> > uv;

			vertices[i]->GetUVs(&uv);
			uv[state_loop].x = (uv[state_loop].x*pixel_count.x + offset.x) * pixel_size.x;
			uv[state_loop].y = (uv[state_loop].y*pixel_count.y + offset.y) * pixel_size.y;
			vertices[i]->SetUVs(uv);

			// If uv are wrapped for more than 4 pixels
			if (uv[state_loop].x < -4.0/texture_size.x || uv[state_loop].x > 1+4.0/texture_size.x ||
				uv[state_loop].y < -4.0/texture_size.y || uv[state_loop].y > 1+4.0/texture_size.y )
			{
				if (process->errorfn)
				{
					char buffer[200];
					sprintf(buffer, "CoalesceTextures: %s has UV's wrapped (%f %f)!", (char*)name, uv[state_loop].x, uv[state_loop].y);
					LPERROR_CALLBACKFN fcn = (LPERROR_CALLBACKFN)process->errorfn;
					fcn(buffer,process->suppress);
				}
			}
		}
		DetachArrayReferences(&vertices);

		//
		//--------------------------------------------------------------------
		// Now change the states of the polygons so that they point at the new
		// megatextures
		//--------------------------------------------------------------------
		//
		(*states[0])[state_loop]->SetToMatchTextureProxy(mega_texture);

		//
		//---------
		// Clean up
		//---------
		//
		mega_texture->DetachReference();
		DetachArrayReferences(&polygons);
	}
	SetToMatchMultiState(states[0]);
	DetachArrayReferences(&states);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	SceneProxy::CoalesceTextures(CoalesceTexturesProcess *process)
{
	Check_Object(this);
	Check_Object(process);

	//
	//---------------------------------------
	// Make sure that the process says its OK
	//---------------------------------------
	//
	process->CoalesceTexturesCallback(this);
	if (!process->continueProcess)
		return;

	//
	//--------------------------------------
	// See if this will be a trivial process
	//--------------------------------------
	//
	if (!process->megatextureList)
		return;

	//
	//------------------------------------------------------------------
	// Coalesce the children.  At this point, they must all be flattened
	//------------------------------------------------------------------
	//
	unsigned child_count = GetChildCount();
	ChildProxy *child = UseFirstChildProxy();
	for (unsigned i=0; i<child_count; ++i)
	{
		Check_Object(child);
		ChildProxy *next = child->UseNextSiblingProxy();
		PolygonMeshProxy *mesh = Cast_Object(PolygonMeshProxy*, child);
		mesh->CoalesceTextures(process);
		child->DetachReference();
		child = next;
		if (!process->continueProcess)
		{
			if (child)
				child->DetachReference();
			break;
		}
	}

	//
	//-------------------------------------------
	// Make sure to discard any remaining proxies
	//-------------------------------------------
	//
	if (child)
		child->DetachReference();
}
