#include "ProxyHeaders.hpp"

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BuildMegatexturesProcess::BuildMegatexturesProcess( 
	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
	BuildMegatexturesProcess::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 SizesDontMatch:
		Check_Pointer(texture);
		PAUSE(("[%s]%s does not match its stated size!", megatexture, texture));
		break;

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

	case TextureDoesntFit:
		Check_Pointer(texture);
		PAUSE(("[%s]%s doesn't fit!", megatexture, texture));
		break;

	case TextureOverlap:
		Check_Pointer(texture);
		PAUSE(("[%s]%s overlaps a previous texture!", megatexture, texture));
		break;

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	TextureProxy::BuildMegatextures(BuildMegatexturesProcess *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)
	);
	TextureLibrary *library = GetTextureLibrary();
	Check_Object(library);

	//
	//-----------------------------------------------------------------
	// Create the megatexture proxy and clear it out to invisible black
	//-----------------------------------------------------------------
	//
	unsigned channel_size = mega_size.x*mega_size.y;
	DynamicArrayOf<BYTE>
		red(channel_size),
		green(channel_size),
		blue(channel_size),
		alpha(channel_size);
	memset(red.GetData(), 0, channel_size);
	memset(green.GetData(), 0, channel_size);
	memset(blue.GetData(), 0, channel_size);
	memset(alpha.GetData(), 0, channel_size);
	DynamicArrayOf<bool>
		used(false, channel_size);

	//
	//----------------------------------------------------------------------
	// Now start stepping through the entries for the page and skip the page
	// size entry
	//----------------------------------------------------------------------
	//
	bool alpha_used = false;
	int texture_count = 0;
	ChainOf<Note*> *textures = NULL;
	Note *entry = NULL;
	unsigned errors = 0;

	Page *mega_page = mega_file->FindPage(mega_name);
	if (mega_page)
	{
		textures = mega_page->MakeNoteChain("Texture");
		Check_Object(textures);
		Page::NoteIterator texture_itr(textures);
		while ((entry = texture_itr.ReadAndNext()) != NULL)
		{
			Check_Object(entry);

			//
			//--------------------------------------------
			// Extract the texture data from the tron file
			//--------------------------------------------
			//
			const char* data;
			entry->GetEntry(&data);
			Check_Pointer(data);
			Vector2DOf<int> offset, size(0,0);
			char texture_name[80];
			int param_count =
				sscanf(
					data,
					"%s %d %d %d %d",
					texture_name,
					&offset.x,
					&offset.y,
					&size.x,
					&size.y
				);
			if (param_count != 5 || !strlen(texture_name))
			{
				process->ProcessCallback(
					BuildMegatexturesProcess::BadTextureLine,
					mega_name,
					texture_name
				);
				if (!process->continueProcess)
				{
	Unwind_1:
					Check_Object(textures);
					delete textures;
					return;
	#undef UNWIND
	#define UNWIND() goto Unwind_1
				}
				else
				{
					++errors;
					continue;
				}
			}

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

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

			//
			//---------------------------
			// Make sure the size is good
			//---------------------------
			//
			Vector2DOf<int> texture_size;
			texture->GetImageSize(&texture_size);
			if (!texture_size.x && !texture_size.y)
			{
				process->ProcessCallback(
					BuildMegatexturesProcess::BadTexture,
					mega_name,
					texture_name
				);
				if (!process->continueProcess)
				{
	Unwind_2:
					texture->DetachReference();
					UNWIND();
	#undef UNWIND
	#define UNWIND() goto Unwind_2
				}
				else
				{
					texture->DetachReference();
					++errors;
					continue;
				}
			}

			//
			//-----------------------------------------------
			// Make sure the size matches what is in the file
			//-----------------------------------------------
			//
			if (size != texture_size)
			{
				process->ProcessCallback(
					BuildMegatexturesProcess::SizesDontMatch,
					mega_name,
					texture_name
				);
				if (!process->continueProcess)
					UNWIND();
				else
				{
					texture->DetachReference();
					++errors;
					continue;
				}
			}
			++texture_count;

			//
			//--------------------------------------------------------------
			// Figure out where this texture wishes to be in the megatexture
			//--------------------------------------------------------------
			//
			if (
				offset.x < 0 || offset.x+size.x > mega_size.x
				 || offset.y < 0 || offset.y+size.y > mega_size.y
			)
			{
				process->ProcessCallback(
					BuildMegatexturesProcess::TextureDoesntFit,
					mega_name,
					texture_name
				);
				if (!process->continueProcess)
					UNWIND();
				else
				{
					++errors;
					texture->DetachReference();
					continue;
				}
			}

			//
			//-----------------------
			// Get the texture depths
			//-----------------------
			//
			unsigned
				red_depth,
				green_depth,
				blue_depth,
				alpha_depth;
			texture->GetChannelDepth(
				&red_depth,
				&green_depth,
				&blue_depth,
				&alpha_depth
			);
			if (alpha_depth)
				alpha_used = true;

			//
			//---------------------
			// Get the texture data
			//---------------------
			//
			DynamicArrayOf<BYTE>
				r,
				g,
				b,
				a;
			
			if (alpha_depth)
				texture->GetChannels(&r, &g, &b, &a);
			else
			{
				texture->GetChannels(&r, &g, &b, NULL);
				alpha_depth = 0;
			}

			//
			//-------------------------
			// Copy the data row by row
			//-------------------------
			//
			Vector2DOf<int> source(0,0);
			for (source.y=0; source.y<size.y; ++source.y)
			{
				Vector2DOf<int> dest;
				dest.Add(offset, source);
				unsigned source_pixel = texture->GetPixelIndex(source);
				unsigned dest_pixel = GetPixelIndex(dest);

				//
				//--------------------
				// Look for an overlap
				//--------------------
				//
				void *overlap = memchr(&used[dest_pixel], true, size.x);
				if (overlap)
				{
					process->ProcessCallback(
						BuildMegatexturesProcess::TextureOverlap,
						mega_name,
						texture_name
					);
					if (!process->continueProcess)
						UNWIND();
					else
					{
						++errors;
						source.y = size.y;
						continue;
					}
				}
				memset(&used[dest_pixel], true, size.x);

				//
				//---------------------
				// Copy the row of data
				//---------------------
				//
				Mem_Copy(
					&red[dest_pixel],
					&r[source_pixel],
					size.x,
					red.GetLength() - dest_pixel
				);
				Mem_Copy(
					&green[dest_pixel],
					&g[source_pixel],
					size.x,
					green.GetLength() - dest_pixel
				);
				Mem_Copy(
					&blue[dest_pixel],
					&b[source_pixel],
					size.x,
					blue.GetLength() - dest_pixel
				);

				//
				//----------------------------------------------------------------
				// Copy the alpha data if enabled, otherwise just set it to opaque
				//----------------------------------------------------------------
				//
				if (alpha_depth)
				{
					Mem_Copy(
						&alpha[dest_pixel],
						&a[source_pixel],
						size.x,
						alpha.GetLength() - dest_pixel
					);
				}
				else
					memset(&alpha[dest_pixel], static_cast<BYTE>(255), size.x);
			}

			//
			//---------------------
			// Clean up the texture
			//---------------------
			//
			texture->DetachReference();
		}
	}

	//
	//-------------------------------------------------
	// Tell the application if the megatexture is empty
	//-------------------------------------------------
	//
	if (!texture_count)
	{
		process->ProcessCallback(
			BuildMegatexturesProcess::EmptyMegatexture,
			mega_name,
			NULL
		);
		if (!process->continueProcess)
		{
			Check_Object(textures);
			delete textures;
		}
	}

	//
	//---------------------------------------------------------------------
	// If we didn't have any errors and aren't empty, copy the texture into
	// the texture library
	//---------------------------------------------------------------------
	//
	else if (!errors)
	{
		SetChannels(
			&red,
			&green,
			&blue,
			(alpha_used) ? &alpha : NULL
		);
		TextureProxy *proxy = library->UseMatchingTextureProxy(this);
		Check_Object(proxy);
		Verify(IsEqualTo(proxy));
		proxy->DetachReference();
	}

	//
	//---------
	// Clean up
	//---------
	//
	Check_Object(textures);
	delete textures;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	TextureLibrary::BuildMegatextures(BuildMegatexturesProcess *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();
	Check_Object(pages);
	Page *page;
	while ((page = pages->ReadAndNext()) != NULL)
	{
		Check_Object(page);
		const char* mega_name = page->GetName();
		Check_Pointer(mega_name);
		process->ProcessCallback(
			BuildMegatexturesProcess::StatusCheck,
			mega_name,
			NULL
		);
		if (!process->continueProcess)
		{
Unwind_1:
			Check_Object(pages);
			delete pages;
			return;
#undef UNWIND
#define UNWIND() goto Unwind_1
		}

		//
		//----------------------------------------------------------------------
		// 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(
				BuildMegatexturesProcess::BadPageSize,
				mega_name,
				NULL
			);
			if (!process->continueProcess)
				UNWIND();
			else
				continue;
		}

		//
		//---------------------------------------------------
		// Create the megatexture proxy run the process on it
		//---------------------------------------------------
		//
		TextureProxy *mega_texture =
			TextureProxy::MakeProxy(mega_name, mega_size, this);
		Check_Object(mega_texture);
		mega_texture->BuildMegatextures(process);
		mega_texture->DetachReference();
		if (!process->continueProcess)
			UNWIND();
	}

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