#include "ProxyHeaders.hpp"

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ArrangeMegatexturesProcess::ArrangeMegatexturesProcess(NotationFile *mega_file):
	megaFile(mega_file)
{
	Check_Pointer(this);
	Check_Object(megaFile);
	Check_Object(this);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ArrangeMegatexturesProcess::ArrangeMegatexturesProcess( 
	NotationFile *config_file,
	NotationFile *mega_file
):
	Process(config_file),
	megaFile(mega_file)
{
	Check_Pointer(this);
	Check_Object(config_file);
	Check_Object(megaFile);
	Check_Object(this);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ArrangeMegatexturesProcess::ProcessCallback(
		int error,
		const char* megatexture,
		const char* texture
	)
{
	Check_Object(this);
	Check_Pointer(megatexture);

	switch (error)
	{
	case BadPageSize:
		PAUSE(("[%s] has a bad PageSize!", megatexture));
		break;

	case BadTextureLine:
		Check_Pointer(texture);
		PAUSE(("[%s] has a bad Texture= specification!", megatexture));
		break;

	case BadTexture:
		Check_Pointer(texture);
		PAUSE(("[%s]%s is a bad texture!", megatexture, texture));
		break;

	case NotEnoughRoom:
		Check_Pointer(texture);
		PAUSE(("[%s] doesn't have enough room to hold %s!", megatexture, texture));
		break;

	case EmptyMegatexture:
		PAUSE(("[%s] is empty!", megatexture));
		break;
	}
}

const int Offsets[32]=
{
	  0,   1,   4,   5,
	 16,  17,  20,  21,
	 64,  65,  68,  69,
	 80,  81,  84,  85,
	256, 257, 260, 261,
	272, 273, 276, 277,
	320, 321, 324, 325,
	336, 337, 340, 341
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	TextureProxy::ArrangeMegatextures(ArrangeMegatexturesProcess *process)
{
	Check_Object(this);
	Check_Object(process);

	//
	//--------------------------
	// Get the mega texture data
	//--------------------------
	//
	NotationFile *mega_file = process->megaFile;
	Check_Object(mega_file);
	MString mega_name;
	GetName(&mega_name);
	Check_Object(&mega_name);
	Vector2DOf<int> mega_size;
	GetImageSize(&mega_size);
	Verify(
		mega_size.x >= 16 && mega_size.x <= 512
		 && mega_size.y >= 16 && mega_size.y <= 512
		 && mega_size.x == (mega_size.x & -mega_size.x)
		 && mega_size.y == (mega_size.y & -mega_size.y)
		 && mega_size.x == mega_size.y
	);
	TextureLibrary *library = GetTextureLibrary();
	Check_Object(library);

	//
	//-------------------------------
	// Create the various size arrays
	//-------------------------------
	//
	Vector2DOf<int> cells(mega_size.x>>4, mega_size.y>>4);
	DynamicArrayOf<bool> used(false, cells.x*cells.y);

	//
	//-------------------------------------------------
	// Tell the application if the megatexture is empty
	//-------------------------------------------------
	//
	Page *texture_page = mega_file->FindPage(mega_name);
	ChainOf<Note*> *textures = NULL;
	int texture_count = 0;
	if (texture_page)
	{
		textures = texture_page->MakeNoteChain("Texture");
		Register_Object(textures);
		Page::NoteIterator texture_itr(textures);
		texture_count = texture_itr.GetSize();
	}
	if (!texture_count)
	{
		process->ProcessCallback(
			ArrangeMegatexturesProcess::EmptyMegatexture,
			mega_name,
			NULL
		);
		if (!process->continueProcess)
		{
Unwind_1:
			if (textures)
			{
				Unregister_Object(textures);
				delete textures;
			}
			return;
#undef UNWIND
#define UNWIND() goto Unwind_1
		}
	}

	//
	//----------------------------------------------------------------------
	// Now start stepping through the entries for the page and skip the page
	// size entry
	//----------------------------------------------------------------------
	//
	DynamicArrayOf<Vector2DOf<int> >
		offsets(texture_count),
		sizes(texture_count);
	DynamicArrayOf<TextureProxy*>
		proxies(texture_count);
	int errors = 0;
	texture_count = -1;
	Page::NoteIterator texture_itr(textures);
	Note *entry;
	while ((entry = texture_itr.ReadAndNext()) != NULL)
	{
		Check_Object(entry);
		++texture_count;
		Verify(texture_count >= 0);

		//
		//--------------------------------------------
		// Extract the texture data from the tron file
		//--------------------------------------------
		//
		const char* data;
		entry->GetEntry(&data);
		Check_Pointer(data);
		char texture_name[80];
		int param_count = sscanf(data, "%s", texture_name);
		if (param_count != 1 || !strlen(texture_name))
		{
			process->ProcessCallback(
				ArrangeMegatexturesProcess::BadTextureLine,
				mega_name,
				texture_name
			);
			if (!process->continueProcess)
				UNWIND();
			else
			{
				++errors;
				continue;
			}
		}

		//
		//--------------------------
		// Do the status check again
		//--------------------------
		//
		process->ProcessCallback(
			ArrangeMegatexturesProcess::StatusCheck,
			mega_name,
			texture_name
		);
		if (!process->continueProcess)
			UNWIND();

		//
		//-----------------------------------------------------------------
		// We now have a texture name, so get the actual texture proxy from
		// the library
		//-----------------------------------------------------------------
		//
		proxies[texture_count] = library->UseTextureProxy(texture_name);
		Check_Object(proxies[texture_count]);

		//
		//---------------------------
		// Make sure the size is good
		//---------------------------
		//
		proxies[texture_count]->GetImageSize(&sizes[texture_count]);
		if (!sizes[texture_count].x && !sizes[texture_count].y)
		{
			process->ProcessCallback(
				ArrangeMegatexturesProcess::BadTexture,
				mega_name,
				texture_name
			);
			if (!process->continueProcess)
			{
Unwind_2:
				for (int i=0; i<=texture_count; ++i)
					proxies[i]->DetachReference();
				UNWIND();
#undef UNWIND
#define UNWIND() goto Unwind_2
			}
			else
			{
				++errors;
				continue;
			}
		}
		Verify(
			sizes[texture_count].x >= 16 && sizes[texture_count].x <= 512
			 && sizes[texture_count].y >= 16 && sizes[texture_count].y <= 512
			 && sizes[texture_count].x == (sizes[texture_count].x & -sizes[texture_count].x)
			 && sizes[texture_count].y == (sizes[texture_count].y & -sizes[texture_count].y)
		);

		//
		//------------------------------------------------------------------------
		// Find the parameters for the search, which is based upon an interleaving
		// scheme using a bit-wise mix of the x and y index values
		//------------------------------------------------------------------------
		//
		Vector2DOf<int>
			units(sizes[texture_count].x>>4, sizes[texture_count].y>>4);
		Verify(units.x>0 && units.y>0);
		int
			increment,
			run,
			scale;
		if (units.x < units.y)
		{
			run = units.y / units.x;
			scale = units.x*units.x;
			increment = scale * run*run;
		}
		else
		{
			run = units.x / units.y;
			scale = units.y*units.y;
			increment = scale * run*run;
			scale *= 2;
		}

		//
		//--------------------------------------------------------------------
		// We search the array associated with the step, looking for the first
		// empty slot that matches
		//--------------------------------------------------------------------
		//
		int index;
		int i;
		for (index=0; index<used.GetLength(); index += increment)
		{
			for (i=0; i<run; ++i)
			{
				int sub_index = scale*Offsets[i];
				if (!used[index + sub_index])
				{
					index += sub_index;
					break;
				}
			}
			if (i!=run)
				break;
		}

		//
		//-----------------------------------------------------
		// If we don't find any empty slots, do the error thing
		//-----------------------------------------------------
		//
		if (index == used.GetLength())
		{
			process->ProcessCallback(
				ArrangeMegatexturesProcess::NotEnoughRoom,
				mega_name,
				texture_name
			);
			if (!process->continueProcess)
				UNWIND();
			else
			{
				++errors;
				continue;
			}
		}

		//
		//--------------------
		// Mark the used cells
		//--------------------
		//
		int x,y;
		for (y=0; y<units.y; ++y)
			for (x=0; x<units.x; ++x)
			{
				Verify(!used[index + Offsets[y]*2 + Offsets[x]]);
				used[index + Offsets[y]*2 + Offsets[x]] = true;
			}

		//
		//----------------------------------------
		// Extract the x,y location from the index
		//----------------------------------------
		//
		int mask=1;
		x = y = 0;
		while (index)
		{
			if (index&1)
				x |= mask;
			index >>= 1;
			if (index&1)
				y |= mask;
			index >>= 1;
			mask <<= 1;
		}
		offsets[texture_count].x = x<<4;
		offsets[texture_count].y = y<<4;
	}
#undef UNWIND
#define UNWIND() goto Unwind_1

	//
	//------------------------------------------------------------------------
	// If we don't have any errors, go ahead and save our data back out to the
	// notation file.  We first will have to erase the old texture entries
	//------------------------------------------------------------------------
	//
	if (!errors)
	{
		Note* dummy;
		texture_itr.First();
		while ((dummy = texture_itr.ReadAndNext()) != NULL)
			texture_page->DeleteNote(dummy->GetName());
		int i;
		for (i=0; i<=texture_count; ++i)
		{
			char buffer[80];
			MString texture_name;
			Check_Object(proxies[i]);
			proxies[i]->GetName(&texture_name);
			sprintf(
				buffer,
				"%s %d %d %d %d",
				static_cast<const char*>(texture_name),
				offsets[i].x,
				offsets[i].y,
				sizes[i].x,
				sizes[i].y
			);
			texture_page->AppendEntry("Texture", buffer);
		}

		//
		//-----------------------------------------------
		// Now tell the debug stream about the space left
		//-----------------------------------------------
		//
		DynamicArrayOf<int> room(5);
		int total=0;
		for (i=0; i<5; ++i)
		{
			total <<= 2;
			int shift = 8 - 2*i;
			int range = 4 << (2*i);
			int count=0;
			for (int j=0; j<range; ++j)
				if (!used[j<<shift])
					++count;
			room[i] = count - total;
			total = count;
		}
		if (total)
		{
			SPEW(("ArrangeMegatextures", "\n[%s] has room for:", mega_name));
			for (i=0; i<5; ++i)
			{
				if (room[i])
					SPEW((
						"ArrangeMegatextures",
						"\t%d %dx%d textures",
						room[i],
						256 >> i,
						256 >> i
					));
			}
		}
	}

	//
	//---------
	// Clean up
	//---------
	//
	for (int i=0; i<=texture_count; ++i)
		proxies[i]->DetachReference();
	UNWIND();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	TextureLibrary::ArrangeMegatextures(ArrangeMegatexturesProcess *process)
{
	Check_Object(this);
	Check_Object(process);

	//
	//------------------------------------------------------------------------
	// Get the page list from the mega file and process each page after making
	// sure the app says it's OK
	//------------------------------------------------------------------------
	//
	NotationFile *mega_file = process->megaFile;
	Check_Object(mega_file);
	NotationFile::PageIterator *pages = mega_file->MakePageIterator();
	Register_Object(pages);
	Page *page;
	while ((page = pages->ReadAndNext()) != NULL)
	{
		const char* mega_name = page->GetName();
		Check_Pointer(mega_name);
		process->ProcessCallback(
			ArrangeMegatexturesProcess::StatusCheck,
			mega_name,
			NULL
		);
		if (!process->continueProcess)
			break;

		//
		//----------------------------------------------------------------------
		// For each page, get a list of all the entries.  If the page is missing
		// its PageSize page, it is a final uncoalesced texture, so skip it
		//----------------------------------------------------------------------
		//
		const char* size_string;
		if (!page->GetEntry("PageSize", &size_string))
			continue;

		//
		//------------------------------
		// Make sure the page size is OK
		//------------------------------
		//
		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);
		if (
			mega_size.x < 16 || mega_size.x > 512
			 || mega_size.y < 16 || mega_size.y > 512
			 || mega_size.x != (mega_size.x & -mega_size.x)
			 || mega_size.y != (mega_size.y & -mega_size.y)
		)
		{
			process->ProcessCallback(
				ArrangeMegatexturesProcess::BadPageSize,
				mega_name,
				NULL
			);
			if (!process->continueProcess)
				break;
			else
				continue;
		}

		//
		//---------------------------------------------------
		// Create the megatexture proxy run the process on it
		//---------------------------------------------------
		//
		TextureProxy *mega_texture =
			TextureProxy::MakeProxy(mega_name, mega_size, this);
		Register_Object(mega_texture);
		mega_texture->ArrangeMegatextures(process);
		mega_texture->DetachReference();

		//
		//---------
		// Clean up
		//---------
		//
		if (!process->continueProcess)
			break;
	}

	//
	//-----------------------
	// Clean up the page list
	//-----------------------
	//
	Unregister_Object(pages);
	delete pages;
}
