//////////////////////////////////////////////////////////////////////////////
//
// Tools_Xbox360_Texture_Writer.cpp
//
// Copyright (c) 2006 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

/******************************************************************************/
/* Included Files                                                             */
/******************************************************************************/

#include "Core/Asura.h"

#include "ImageProcessing/Tools_Xbox360_Texture_Writer.h"

// Asura Platform-independent
#include "ChunkSystem/Asura_Chunk_ResourceFile.h"
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "Core/Asura_MaterialFlags.h"
#include "Core/Asura_Maths.h"
#include "Core/Asura_String.h"
#include "Core/Asura_TextureFlags.h"
#include "Core/Asura_TextureManagement.h"
#include "Core/Asura_UserInput.h"

// Asura PC
#include "Core/Asura_Windows.h"
#include "Rendering/Asura_Direct3D.h"

// Asura Xbox 360
#include "../Xbox360/ChunkSystem/Asura_Chunk_ResourceFile_Xbox360.h"

// Tools
#include "Load&Save/LevelExport/Tools_Level_Export.h"
#include "Load&Save/Tools_TextureConversionData.h"
#include "Materials/Tools_MaterialResponse_System.h"
#include "PC/OS/Tools_OS.h"
#include "Texturing/Tools_Texture_Management.h"

/******************************************************************************/
/* Constants                                                                  */
/******************************************************************************/

const char szXPR_RESOURCE_FILENAME[] = "XPR";

const char szRDF_HEADER[] = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n\n<RDF Version=\"XPR2\">\n\n";
const char szRDF_FOOTER[] = "</RDF>";

const char szRDF_TEXTURE_HEADER[]       = "<Texture\n";
const char szRDF_TEXTURE_NAME[]         = "\tName = \"";        // This must be followed by "\"\n"
const char szRDF_TEXTURE_SOURCE[]       = "\tSource = \"";      // This too
const char szRDF_TEXTURE_ALPHA_SOURCE[] = "\tAlphaSource = \""; // And this
const char szRDF_TEXTURE_FORMAT[]       = "\tFormat = \"";      // And this
const char szRDF_TEXTURE_LEVELS[]       = "\tLevels = \"";      // And this
const char szRDF_TEXTURE_WIDTH[]        = "\tWidth = \"";       // And this
const char szRDF_TEXTURE_HEIGHT[]       = "\tHeight = \"";      // And this
const char szRDF_TEXTURE_APPLYFILTER[]  = "\tApplyfilter = \"";      // And this
const char szRDF_TEXTURE_FOOTER[]       = "/>\n\n";

const char szRDF_PIXELSHADER_HEADER[] = "<PixelShader\n";
const char szRDF_PIXELSHADER_NAME[]   = "\tName = \"";    // This must be followed by "\"\n"
const char szRDF_PIXELSHADER_FILE[]   = "\tFile = \"";    // And this
const char szRDF_PIXELSHADER_ENTRY[]  = "\tEntry = \"";   // And this
const char szRDF_PIXELSHADER_DEFINE[] = "\tDefine = \"";  // And this
const char szRDF_PIXELSHADER_FOOTER[] = "/>\n\n";

// Max allocation size when writing portions of the XPR chunk
const u_int uMAX_ALLOCATION_SIZE = 100 * 1024 * 1024;

/******************************************************************************/
/* Functions                                                                  */
/******************************************************************************/

////////////////////////////////////////////////////////////////////////////////
//
// WriteRDFString()
//
// Helper function to write a string to the RDF
//
////////////////////////////////////////////////////////////////////////////////

static bool WriteRDFString( const Tools_Xbox360_RDF& xRDF, const char* const szInputString )
{
	// JAT 27/09/07 can't put '&' in the RDF output file because it is XML based have to put &amp;
	// instead
	Asura_String xNewString( szInputString );
	xNewString.Replace( "&", "&amp;" );

	const Asura_Char* szString = xNewString.GetString();
	DWORD ulNumBytesWritten;
	const size_t uStringLength = strlen( szString );

	if( !WriteFile( xRDF.m_xHandle, szString, uStringLength, &ulNumBytesWritten, NULL )
	 || ulNumBytesWritten != uStringLength )
	{
		#if defined ASURA_MAIN
			Tools_Level_Export::AddMessage( Tools_Level_Export::MT_ERROR,
				"Error writing to texture definition file." );
		#else
			Asura_Assert( false, "Error writing to texture definition file." );
		#endif

		return false;
	}

	return true;
}

/******************************************************************************/
/* Tools_Xbox360_Texture_Writer Static Class Functions                        */
/******************************************************************************/

////////////////////////////////////////////////////////////////////////////////
//
// Tools_Xbox360_Texture_Writer::AddTextureToRDF()
//
// Writes an entry to the RDF for a given texture filename
//
////////////////////////////////////////////////////////////////////////////////

bool Tools_Xbox360_Texture_Writer::AddTextureToRDF( const Tools_Xbox360_RDF& xRDF,
													const char* const szTextureName,
												    const char* const szFilename,
													const int iTextureIndex,
													const TextureConversionData& xConversionData )
{
	// Check file extension
	{
		const bool bCorrectFileType = Asura_StringUtil::EndsWith( szTextureName, "bmp", true )
								   || Asura_StringUtil::EndsWith( szTextureName, "ppm", true )
								   || Asura_StringUtil::EndsWith( szTextureName, "tga", true )
								   || Asura_StringUtil::EndsWith( szTextureName, "dib", true )
								   || Asura_StringUtil::EndsWith( szTextureName, "rad", true );

		if( !bCorrectFileType )
		{
			Tools_Level_Export::AddMessage( Tools_Level_Export::MT_ERROR, "Unable to export texture %s:", szTextureName );
			Tools_Level_Export::AddMessage( Tools_Level_Export::MT_ERROR, "Format must be BMP, PPM, TGA, DIB or RAD." );

			return false;
		}
	}

	const char* szOutputFormat;

	#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

		const Asura_Hash_ID uTextureHash = Asura_GetHashID_IgnoreExtension( szTextureName );
		const u_int uUsage = Tools_MaterialResponse_System::GetTextureUsage( uTextureHash );

		const bool bNormalMap = (uUsage & Tools_MaterialResponse_System::TU_NORMAL_CHANNELS) != 0;
		const bool bAlpha = (uUsage & Tools_MaterialResponse_System::TU_SINGLE_CHANNEL) != 0;
		const bool bColour = (uUsage & Tools_MaterialResponse_System::TU_COLOUR_CHANNELS) != 0;

		if( bNormalMap )
		{
			szOutputFormat = (xConversionData.m_iTextureFlags & AsuraTextureFlag_HighQualityCompression)
						   ? "D3DFMT_DXN"
						   : "D3DFMT_CTX1";
		}
		else if( bColour )
		{
			szOutputFormat = bAlpha ? "D3DFMT_DXT5" : "D3DFMT_DXT1";
		}
		else if( bAlpha )
		{
			szOutputFormat = "D3DFMT_DXT5A";
		}
		else
		{
			// Not used by the material response system. Might be corona, PFX etc. Just pick DXT5
			szOutputFormat = "D3DFMT_DXT5";
		}

	#else // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

		// This is pretty much deprecated, 360 projects should be using the material response system

		const u_int u8BitAlphaFlags = AsuraMaterialFlag_AlphaDecal
									| AsuraMaterialFlag_ParallaxMapping
									| AsuraMaterialFlag_SelfIlluminate
									| AsuraMaterialFlag_Specular_Sharp
									| AsuraMaterialFlag_Specular_Soft;

		if( xConversionData.m_iTextureFlags & AsuraTextureFlag_NormalMap && !(xConversionData.m_uMaterialFlags & AsuraMaterialFlag_ParallaxMapping) )
		{
			// Parallax mapping requires the alpha channel.
			// TODO: should use DXN or CTX1 with a separate parallax map, but that will require shader changes
			// For now, abuse the "Xbox half size" flag to uncompress specific normal maps
			
			// (RAR 16/10/2006) AsuraTextureFlag_DontCompress added since the PC and PS3 need similar functioality for the moment.
			szOutputFormat = xConversionData.m_iTextureFlags & (AsuraTextureFlag_HalfForXBox|AsuraTextureFlag_HighQualityCompression) ? "D3DFMT_A8R8G8B8" : "D3DFMT_DXT5";
		}
		else if( xConversionData.m_iTextureFlags & AsuraTextureFlag_8BitAlpha || xConversionData.m_uMaterialFlags & u8BitAlphaFlags )
		{
			szOutputFormat = "D3DFMT_DXT5";
		}
		else
		{
			szOutputFormat = "D3DFMT_DXT1";
		}

	#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

	const u_int uMipLevels = (xConversionData.m_iTextureFlags & AsuraTextureFlag_NoMipMaps ? 1 : 0);

	int iWidth = 0, iHeight = 0;
	bool bUseAlphaSource = false;

	#if defined ASURA_MAIN

		// Don't need this in utils like Resource Packer

		if( xConversionData.m_iTextureFlags & AsuraTextureFlag_ReduceExtraForConsoles || xConversionData.m_uMaximumTextureSize)
		{
			const int iTextureIndex = Asura_Texture_Management::GetTextureIndex( szTextureName );
			Tools_Texture_Management::GetTextureSize( iTextureIndex, iWidth, iHeight );

			if(xConversionData.m_iTextureFlags & AsuraTextureFlag_ReduceExtraForConsoles)
			{
				iWidth /= 2;
				iHeight /= 2;
			}
			if(xConversionData.m_uMaximumTextureSize)
			{
				//Reduce texture if needed to get beneath the maximum texture size. RAR 26/11/2007
				while((u_int)iWidth>xConversionData.m_uMaximumTextureSize || (u_int)iHeight>xConversionData.m_uMaximumTextureSize)
				{
					iWidth /= 2;
					iHeight /= 2;
				}
			}
		}

		// When converting the .tga files to D3DFMT_DXT5A, Bundler will assume that the source is
		// luminance only and output no alpha. If we actually loaded the texture as A8, tell
		// Bundler to use the red channel as alpha

		Direct3D_BaseTexture* const pxD3DBaseTexture = Asura_Direct3D::GetDirect3DTextureByIndex( iTextureIndex );
		if( pxD3DBaseTexture )
		{
			Direct3D_Texture* pxD3DTexture = NULL;
			pxD3DBaseTexture->QueryInterface( IID_IDirect3DTexture9, reinterpret_cast< void** >( &pxD3DTexture ) );

			if( pxD3DTexture )
			{
				D3DSURFACE_DESC xDesc;
				pxD3DTexture->GetLevelDesc( 0, &xDesc );

				bUseAlphaSource = (xDesc.Format == D3DFMT_A8);

				pxD3DTexture->Release();
			}
		}

	#endif // defined ASURA_MAIN

	const bool bSharpenMipMaps =
		(xConversionData.m_iTextureFlags & AsuraTextureFlag_SharpenMipMaps) != 0;

	return AddTextureToRDF( xRDF, szTextureName, szFilename, szOutputFormat, uMipLevels, iWidth,
		iHeight, bUseAlphaSource, bSharpenMipMaps );
}

////////////////////////////////////////////////////////////////////////////////
//
// Tools_Xbox360_Texture_Writer::AddTextureToRDF()
//
// Writes an entry to the RDF for a given texture filename
//
////////////////////////////////////////////////////////////////////////////////

bool Tools_Xbox360_Texture_Writer::AddTextureToRDF( const Tools_Xbox360_RDF& xRDF,
													const char* const szTextureName,
												    const char* const szFilename,
													const char* const szOutputFormat,
													const u_int uMipLevels,
													const u_int uWidth /*=0*/,
													const u_int uHeight /*=0*/,
													const bool bUseAlphaSource /*=false*/, 
													const bool bSharpenMipMaps /*=false*/)
{
	if( !szTextureName || !szFilename || !szOutputFormat )
	{
		Asura_Assert( false, "NULL parameter passed to Tools_Xbox360_Texture_Writer::AddTextureToRDF()." );
		return false;
	}

	if( xRDF.m_xHandle == INVALID_HANDLE_VALUE )
	{
		Asura_Assert( false, "Trying to add texture to RDF, but file isn't open." );
		return false;
	}

	char szAbsolutePath[ MAX_PATH + 1 ];

	#if defined ASURA_ASSERT
		const DWORD uPathLength =
	#endif
		GetFullPathName( szFilename, MAX_PATH, szAbsolutePath, NULL );

	Asura_Assert( uPathLength <= MAX_PATH, "Path buffer isn't long enough." );

	char szMipLevels[20];
	Asura_PrintfToBfr( szMipLevels, "%s%u\"\n", 20, true, szRDF_TEXTURE_LEVELS, uMipLevels );

	char szWidth[20];
	Asura_PrintfToBfr( szWidth, "%s%u\"\n", 20, true, szRDF_TEXTURE_WIDTH, uWidth );

	char szHeight[20];
	Asura_PrintfToBfr( szHeight, "%s%u\"\n", 20, true, szRDF_TEXTURE_HEIGHT, uHeight );
	char szApplyFilter[40];
	Asura_PrintfToBfr( szApplyFilter, "%s%u\"\n", 40, true, szRDF_TEXTURE_APPLYFILTER, bSharpenMipMaps );

	if( !WriteRDFString( xRDF, szRDF_TEXTURE_HEADER )
	 || !WriteRDFString( xRDF, szRDF_TEXTURE_NAME )
	 || !WriteRDFString( xRDF, szTextureName )
	 || !WriteRDFString( xRDF, "\"\n" )
	 || !WriteRDFString( xRDF, szRDF_TEXTURE_SOURCE )
	 || !WriteRDFString( xRDF, szAbsolutePath )
	 || !WriteRDFString( xRDF, "\"\n" )
	 || (bUseAlphaSource && !WriteRDFString( xRDF, szRDF_TEXTURE_ALPHA_SOURCE ))
	 || (bUseAlphaSource && !WriteRDFString( xRDF, szAbsolutePath ))
	 || (bUseAlphaSource && !WriteRDFString( xRDF, "\"\n" ))
	 || !WriteRDFString( xRDF, szRDF_TEXTURE_FORMAT )
	 || !WriteRDFString( xRDF, szOutputFormat )
	 || !WriteRDFString( xRDF, "\"\n" )
	 || !WriteRDFString( xRDF, szMipLevels )
	 || (uWidth != 0 && !WriteRDFString( xRDF, szWidth ))
	 || (uHeight != 0 && !WriteRDFString( xRDF, szHeight ))
	 || (bSharpenMipMaps != 0 && !WriteRDFString( xRDF, szApplyFilter ))
	 || !WriteRDFString( xRDF, szRDF_TEXTURE_FOOTER ) )
	{
		return false;
	}

	return true;
}

bool Tools_Xbox360_Texture_Writer::AddPixelShaderToRDF( const Tools_Xbox360_RDF& xRDF,
														const char* const szShaderName,
														const char* const szFilename,
														const char* const szEntryPoint,
														const u_int uNumDefines,
														const char* const aszDefines[] )
{
	if( !szFilename )
	{
		Asura_Assert( false,
			"NULL parameter passed to Tools_Xbox360_Texture_Writer::AddTextureToRDF()." );

		return false;
	}

	if( xRDF.m_xHandle == INVALID_HANDLE_VALUE )
	{
		Asura_Assert( false, "Trying to add texture to RDF, but file isn't open." );
		return false;
	}

	if( !WriteRDFString( xRDF, szRDF_PIXELSHADER_HEADER ) ) return false;

	if( szShaderName )
	{
		if( !WriteRDFString( xRDF, szRDF_PIXELSHADER_NAME )
		 || !WriteRDFString( xRDF, szShaderName )
		 || !WriteRDFString( xRDF, "\"\n" ) )
		{
			return false;
		}
	}

	if( !WriteRDFString( xRDF, szRDF_PIXELSHADER_FILE )
	 || !WriteRDFString( xRDF, szFilename )
	 || !WriteRDFString( xRDF, "\"\n" ) )
	{
		return false;
	}

	if( szEntryPoint )
	{
		if( !WriteRDFString( xRDF, szRDF_PIXELSHADER_ENTRY )
		 || !WriteRDFString( xRDF, szEntryPoint )
		 || !WriteRDFString( xRDF, "\"\n" ) )
		{
			return false;
		}
	}

	for( u_int u = 0; u < uNumDefines; ++u )
	{
		if( !WriteRDFString( xRDF, szRDF_PIXELSHADER_DEFINE )
		 || !WriteRDFString( xRDF, aszDefines[u] )
		 || !WriteRDFString( xRDF, "\"\n" ) )
		{
			return false;
		}
	}

	if( !WriteRDFString( xRDF, szRDF_PIXELSHADER_FOOTER ) ) return false;

	return true;
}


////////////////////////////////////////////////////////////////////////////////
//
// Tools_Xbox360_Texture_Writer::BeginTextureExport()
//
// Creates an empty .rdf file ready for texture entries to be added
//
////////////////////////////////////////////////////////////////////////////////

bool Tools_Xbox360_Texture_Writer::BeginTextureExport( Tools_Xbox360_RDF& xRDF,
													   const char* const szExportFilename )
{
	// Split up export filename

	const Asura_String xExportFilename( szExportFilename );

	xRDF.m_xPath.MakeNull();
	Asura_String xRDFName;
	xExportFilename.SplitFilename( &xRDF.m_xPath, &xRDFName, NULL );

	// Create .rdf text file
	if ( !xRDF.m_xPath.IsBlank() )
	{
		xRDF.m_xPath += "\\";
	}

	xRDF.m_xPath += xRDFName;
	xRDF.m_xPath += ".rdf";

	xRDF.m_xHandle =
		CreateFile( xRDF.m_xPath.GetString(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL );

	if( xRDF.m_xHandle == INVALID_HANDLE_VALUE ) return false;

	// Write header

	if( !WriteRDFString( xRDF, szRDF_HEADER ) )
	{
		CloseHandle( xRDF.m_xHandle );
		xRDF.m_xHandle = INVALID_HANDLE_VALUE;

		return false;
	}

	return true;
}

////////////////////////////////////////////////////////////////////////////////
//
// Tools_Xbox360_Texture_Writer::CreateBundleChunk()
//
// Adds the .xpr created by Bundler to an Asura resource file
//
////////////////////////////////////////////////////////////////////////////////

bool Tools_Xbox360_Texture_Writer::CreateBundleChunk( const Tools_Xbox360_RDF& xRDF,
													  const HANDLE hExportHandle )
{
	if( hExportHandle == INVALID_HANDLE_VALUE )
	{
		Asura_Assert( false,
			"Invalid parameter passed to Tools_Xbox360_Texture_Writer::CreateBundleChunk()." );

		return false;
	}

	Asura_String xXPRPath, xXPRName;
	xRDF.m_xPath.SplitFilename( &xXPRPath, &xXPRName, NULL );

	xXPRPath += "\\";
	xXPRPath += xXPRName;
	xXPRPath += ".xpr";

	const HANDLE hXPRHandle = CreateFile( xXPRPath.GetString(), GENERIC_READ, FILE_SHARE_READ, NULL,
		OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL );

	if( hXPRHandle == INVALID_HANDLE_VALUE )
	{
		Asura_Assertf( false, ("Unable to open XPR file %s.", xXPRPath.GetString()) );
		return false;
	}

	const u_int uXPRFileSize = GetFileSize( hXPRHandle, NULL );
	const u_int uNameSize = strlen( szXPR_RESOURCE_FILENAME ) + 1;
	const u_int uChunkSize = sizeof( Asura_Chunk_ResourceFile ) + uNameSize + uXPRFileSize;
	const u_int uAllocationSize = Asura_Maths::Min( uChunkSize, uMAX_ALLOCATION_SIZE );

	// XPRs for levels can be huge, over 256 MB. It seems we can't allocate that much in one block.

	char* const pcChunkBuffer = new char[ uAllocationSize ];
	if( !pcChunkBuffer )
	{
		#if defined ASURA_MAIN
			Tools_Level_Export::AddMessage( Tools_Level_Export::MT_ERROR,
				"Failed to allocate memory for bundle chunk." );
		#else
			Asura_Assert( false, "Failed to allocate memory for bundle chunk." );
		#endif

		CloseHandle( hXPRHandle );

		return false;
	}

	// Tell the chunk stream the chunk size is bigger than it really is. It won't write past the end
	// anyway

	Asura_Chunk_Memory_Stream xChunkStream( uChunkSize, pcChunkBuffer );
	xChunkStream.SetSwapEndian( Tools_Level_Export::ShouldExportBigEndian() );

	xChunkStream.WriteChunk_Start( ASURA_CHUNK_RESOURCEFILE, 0, 0 );

	xChunkStream << static_cast< u_int >( ASURA_RESOURCEFILE_TYPE_PLATFORMSPECIFIC );
	xChunkStream << static_cast< u_int >( ASURA_RESOURCEFILE_TYPE_XBOX360_XPR );

	xChunkStream << uXPRFileSize;

	const Asura_WriteToStream_Array_Wrapper< char >
		xResourceName( uNameSize, szXPR_RESOURCE_FILENAME );

	xChunkStream << xResourceName;

	// This shouldn't actually write anything, just set the file size in the chunk header
	xChunkStream.Skip( uXPRFileSize );
	xChunkStream.WriteChunk_End();

	DWORD uNumBytesProcessed;

	// Write the chunk header

	const u_int uHeaderSize = sizeof( int ) * 4 // ID, size, version, flags
							+ sizeof( int ) * 3 // Resource type, resource subtype, file size
							+ uNameSize;		// File name

	if( !WriteFile( hExportHandle, pcChunkBuffer, uHeaderSize, &uNumBytesProcessed, NULL )
	 || uNumBytesProcessed != uHeaderSize )
	{
		Asura_Assert( false, "Error writing XPR chunk header." );
		CloseHandle( hXPRHandle );
		delete[] pcChunkBuffer;

		return false;
	}

	// Now read portions of the bundle file and copy them through

	do
	{
		if( !ReadFile( hXPRHandle, pcChunkBuffer, uAllocationSize, &uNumBytesProcessed, NULL ) )
		{
			Asura_Assertf( false, ("Unable to read from %s.", xXPRPath.GetString()) );
			CloseHandle( hXPRHandle );
			delete[] pcChunkBuffer;

			return false;
		}

		DWORD uNumBytesWritten;

		if( !WriteFile( hExportHandle, pcChunkBuffer, uNumBytesProcessed, &uNumBytesWritten, NULL ) )
		{
			Asura_Assert( false, "Unable to write to XPR chunk." );
			CloseHandle( hXPRHandle );
			delete[] pcChunkBuffer;

			return false;
		}
	} while( uNumBytesProcessed == uAllocationSize );

	CloseHandle( hXPRHandle );
	delete[] pcChunkBuffer;

	return true;
}

////////////////////////////////////////////////////////////////////////////////
//
// Tools_Xbox360_Texture_Writer::EndTextureExport()
//
// Finishes off the .rdf file
//
////////////////////////////////////////////////////////////////////////////////

void Tools_Xbox360_Texture_Writer::EndTextureExport( Tools_Xbox360_RDF& xRDF )
{
	// Finish off the .rdf

	if( xRDF.m_xHandle == INVALID_HANDLE_VALUE )
	{
		Asura_Assert( false, "Trying to end texture export, but RDF isn't open." );
		return;
	}

	WriteRDFString( xRDF, szRDF_FOOTER );

	CloseHandle( xRDF.m_xHandle );
	xRDF.m_xHandle = INVALID_HANDLE_VALUE;
}

////////////////////////////////////////////////////////////////////////////////
//
// Tools_Xbox360_Texture_Writer::RunBundler()
//
// Runs the Bundler tool on a generated .rdf file
//
////////////////////////////////////////////////////////////////////////////////

bool Tools_Xbox360_Texture_Writer::RunBundler( const Tools_Xbox360_RDF& xRDF )
{
	// Redirect Bundler stdout etc. to files in case of errors

	SECURITY_ATTRIBUTES xSecurityAttributes;
	xSecurityAttributes.nLength = sizeof( xSecurityAttributes );
	xSecurityAttributes.lpSecurityDescriptor = NULL;
	xSecurityAttributes.bInheritHandle = TRUE;

	const HANDLE xStdoutHandle = CreateFile( "Logfiles\\Bundler_Output.txt", GENERIC_WRITE,
		FILE_SHARE_READ, &xSecurityAttributes, CREATE_ALWAYS, 0, NULL );

	const HANDLE xStderrHandle = CreateFile( "Logfiles\\Bundler_Errors.txt", GENERIC_WRITE,
		FILE_SHARE_READ, &xSecurityAttributes, CREATE_ALWAYS, 0, NULL );

	Asura_Assert( xStdoutHandle != INVALID_HANDLE_VALUE && xStderrHandle != INVALID_HANDLE_VALUE,
		"Failed to open Bundler logfile for writing." );

	// Execute Bundler process

	Asura_String xBundlerCommand( "Bundler.exe \"" );
	xBundlerCommand += xRDF.m_xPath;
	xBundlerCommand += "\"";

	PROCESS_INFORMATION xProcessInfo;

	STARTUPINFO xStartupInfo = {0};
	xStartupInfo.cb = sizeof( STARTUPINFO );
	xStartupInfo.dwFlags = STARTF_USESTDHANDLES;
	xStartupInfo.hStdInput = INVALID_HANDLE_VALUE;
	xStartupInfo.hStdOutput = xStdoutHandle;
	xStartupInfo.hStdError = xStderrHandle;

	if( !CreateProcess( NULL, xBundlerCommand.GetString(), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL,
		NULL, &xStartupInfo, &xProcessInfo ) )
	{
		#if defined ASURA_MAIN
			Tools_Level_Export::AddMessage( Tools_Level_Export::MT_ERROR,
				"Failed to bundle textures. Check that Bundler.exe is present." );
		#else
			Asura_Assert( false, "Failed to bundle textures. Check that Bundler.exe is present." );
		#endif

		CloseHandle( xStderrHandle );
		CloseHandle( xStdoutHandle );

		return false;
	}

	// Wait for it to finish, allowing user to cancel by pressing escape

	while( true )
	{
		if( WaitForSingleObject( xProcessInfo.hProcess, 250 ) == WAIT_OBJECT_0 ) break;

		#if defined ASURA_MAIN

			Asura_Windows::CheckForWindowsMessages();
			Asura_UserInput::Update();

			if( Asura_UserInput::KeyPressed( ASURA_KEYID_ESCAPE ) )
			{
				TerminateProcess( xProcessInfo.hProcess, 0 );

				CloseHandle( xProcessInfo.hThread );
				CloseHandle( xProcessInfo.hProcess );
				CloseHandle( xStderrHandle );
				CloseHandle( xStdoutHandle );

				Tools_Level_Export::SetAbortFlag();
				return false;
			}

		#endif // defined ASURA_MAIN
	}

	// Check the exit code for failure

	DWORD ulBundlerExitCode = 0;
	if( !GetExitCodeProcess( xProcessInfo.hProcess, &ulBundlerExitCode ) )
	{
		Asura_Assert( false, "Unable to query exit code for Bundler process." );
	}

	CloseHandle( xProcessInfo.hThread );
	CloseHandle( xProcessInfo.hProcess );
	CloseHandle( xStderrHandle );
	CloseHandle( xStdoutHandle );

	if( ulBundlerExitCode != 0 )
	{
		#if defined ASURA_MAIN
			Tools_Level_Export::AddMessage( Tools_Level_Export::MT_ERROR,
				"Bundler tool reported an error. Exit code %u. See Bundler logfiles.",
				ulBundlerExitCode );
		#else
			Asura_Assertf( false,
				("Bundler tool reported an error. Exit code %u. See Bundler logfiles.",
				ulBundlerExitCode) );
		#endif

		return false;
	}

	return true;
}

////////////////////////////////////////////////////////////////////////////////
//
// Tools_Xbox360_Texture_Writer::DeleteRDF()
//
// Deletes the .rdf file generated by the last bundling process
//
////////////////////////////////////////////////////////////////////////////////

bool Tools_Xbox360_Texture_Writer::DeleteRDF( Tools_Xbox360_RDF& xRDF )
{
	if( xRDF.m_xHandle != INVALID_HANDLE_VALUE )
	{
		Asura_Assertf( false, ("Trying to delete '%s', but it's still open.",
			xRDF.m_xPath.GetString()) );

		return false;
	}

	const bool bSuccess = (DeleteFile( xRDF.m_xPath.GetString() ) == TRUE);

	if( bSuccess )
	{
		xRDF.m_xPath.MakeBlank();
	}
	else
	{
		#if defined ASURA_MAIN
			Tools_Level_Export::AddMessage( Tools_Level_Export::MT_ERROR,
				"Failed to delete file '%s'.\n%s", xRDF.m_xPath.GetString(),
				Tools_OS::GetLastSystemError() );
		#else
			Asura_Assertf( false, ("Failed to delete file '%s'.\n%s", xRDF.m_xPath.GetString(),
				Tools_OS::GetLastSystemError()) );
		#endif
	}

	return bSuccess;
}

////////////////////////////////////////////////////////////////////////////////
//
// Tools_Xbox360_Texture_Writer::ShouldExportAsXPR()
//
// Temporary function to determine whether to export a texture as a .xpr or .dds.
// We need to use .xprs to get the special 360-only formats like CTX1
//
// Parameters:		iTextureIndex - Texture index to query
// Return value:	true iff the texture should go through the bundler
//
////////////////////////////////////////////////////////////////////////////////

bool Tools_Xbox360_Texture_Writer::ShouldExportAsXPR( const int iTextureIndex )
{
#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

	const Asura_Hash_ID uTextureHash = Asura_Texture_Management::GetHashIDFromIndex( iTextureIndex );
	const u_int uUsage = Tools_MaterialResponse_System::GetTextureUsage( uTextureHash );

	const bool bNormal = (uUsage & Tools_MaterialResponse_System::TU_NORMAL_CHANNELS) != 0;
	const bool bAlpha = (uUsage & Tools_MaterialResponse_System::TU_SINGLE_CHANNEL) != 0;
	const bool bColour = (uUsage & Tools_MaterialResponse_System::TU_COLOUR_CHANNELS) != 0;

	// For now, we'll use the bundler only on normals maps and alpha-only maps
	return bNormal || (bAlpha && !bColour);

#else
	return false;
#endif
}