//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================

#include <stdafx.h>
#include "MainFrm.h"
#include "MapWorld.h"
#include "MessageWnd.h"
#include "IEditorTexture.h"
#include "GlobalFunctions.h"
#include "TextureSystem.h"
#include "TextureConverter.h"


CProgressDlg *	CTextureConverter::m_pProgDlg;
int				CTextureConverter::m_nSolidCount;
int				CTextureConverter::m_nFaceCount;
int				CTextureConverter::m_nDecalCount;
int				CTextureConverter::m_nCurrentSolid;
int				CTextureConverter::m_nCurrentDecal;
int				CTextureConverter::m_nSuccesses;
int				CTextureConverter::m_nErrors;
int				CTextureConverter::m_nSkipped;
int				CTextureConverter::m_nWarnings;


//-----------------------------------------------------------------------------
// Purpose: Reset counters.
// Input  : 
// Output : Counters all reset to 0.
//-----------------------------------------------------------------------------
void CTextureConverter::Initialize( void )
{
	m_nSolidCount	= 0;
	m_nCurrentSolid	= 0;
	m_nFaceCount	= 0;
	m_nDecalCount	= 0;
	m_nCurrentDecal	= 0;

	m_nSuccesses	= 0;
	m_nErrors		= 0;
	m_nSkipped		= 0;
	m_nWarnings		= 0;
}


//-----------------------------------------------------------------------------
// Purpose: Recurse through the contents of the map, passing solid objects on
//          to be converted.
// Input  : pWorld - pointer to the map to have textures converted.
// Output : All solid faces and decals in the world have WAD3 textures
//          converted to VMT.
//-----------------------------------------------------------------------------
void CTextureConverter::ConvertWorldTextures( CMapWorld * pWorld )
{
	Initialize();

	// Bring the WC message window to the front, to display conversion info
	GetMainWnd()->pMsgWnd->ShowWindow( SW_SHOW );
	GetMainWnd()->pMsgWnd->SetWindowPos( &( CWnd::wndTopMost ), 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW );
	GetMainWnd()->pMsgWnd->BringWindowToTop();
	GetMainWnd()->pMsgWnd->SetFocus();

	Msg( mwStatus, "Converting textures from WAD to VMT format..." );

	// Set up a progress meter dialogue
	m_pProgDlg = new CProgressDlg;
	m_pProgDlg->Create();
	m_pProgDlg->SetStep( 1 );
	m_pProgDlg->SetWindowText( "Preparing to convert textures..." );

	// Run the converter
	ConvertSolids( pWorld );
	ConvertDecals( pWorld );
	DisplayStatistics();

	// Destroy the progress meter
	if ( m_pProgDlg )
	{
		m_pProgDlg->DestroyWindow();
		delete m_pProgDlg;
		m_pProgDlg = NULL;
	}

	AfxMessageBox( "Conversion complete.  Check the Worldcraft \"Messages\" window for complete details." );
}


//-----------------------------------------------------------------------------
// Purpose: Recurse through the contents of the map, passing solid objects on
//          to be converted.
// Input  : pWorld - pointer to the map to have textures converted.
// Output : All solid faces in the world have WAD3 textures converted to VMT.
//-----------------------------------------------------------------------------
void CTextureConverter::ConvertSolids( CMapWorld * pWorld )
{
	// Count total map solids so we know how many we have to do (for progress meter).
	pWorld->EnumChildren( ENUMMAPCHILDRENPROC( CountMapSolids ), 0, MAPCLASS_TYPE( CMapSolid ) );

	m_pProgDlg->SetRange( 0, m_nSolidCount );
	m_pProgDlg->SetStep( 2 );
	m_pProgDlg->SetWindowText( "Converting solids..." );

	// Cycle through the solids again and convert as necessary.
	pWorld->EnumChildren( ENUMMAPCHILDRENPROC( CheckSolidTextures ), 0, MAPCLASS_TYPE( CMapSolid ) );
}


//-----------------------------------------------------------------------------
// Purpose: Enumeration function, increment the solids counter.
// Input  : 
// Output : Always return true to continue enumerating.
//-----------------------------------------------------------------------------
bool CTextureConverter::CountMapSolids( CMapSolid *, DWORD )
{
	m_nSolidCount++;

	return true;	// return true to continue enumerating
}


//-----------------------------------------------------------------------------
// Purpose: Enumeration function, check all the faces of a solid for texture conversion
// Input  : pSolid - map solid to be checked.
// Output : Always return true to continue enumerating.
//-----------------------------------------------------------------------------
bool CTextureConverter::CheckSolidTextures( CMapSolid * pSolid, DWORD )
{
	int nFaceCount;

	m_nCurrentSolid++;

	m_pProgDlg->SetPos( m_nCurrentSolid );

	// check each face of the solid
	nFaceCount = pSolid->GetFaceCount();
	while( nFaceCount-- )
	{
		CheckFaceTexture( pSolid->GetFace( nFaceCount ) );
	}

	return true;	// return true to continue enumerating
}


//-----------------------------------------------------------------------------
// Purpose: Check the texture of a face to determine if conversion is necessary.
// Input  : pFace - a map face.
// Output : 
//-----------------------------------------------------------------------------
void CTextureConverter::CheckFaceTexture( CMapFace * pFace )
{
	m_nFaceCount++;

	// Criteria for needing conversion is a) being a dummy texture AND b) having no slashes
	// in the texture name.

	if ( !pFace->GetTexture()->IsDummy() )
	{
		m_nSkipped++;
		return;
	}

	if ( strchr( pFace->GetTexture()->GetName(), '/') != NULL )
	{
		m_nSkipped++;
		return;
	}

	ConvertFaceTexture( pFace );
}


//-----------------------------------------------------------------------------
// Purpose: Determine if any materials match the old texture of a face and replace
//          appropriately.
// Input  : pFace - a map face known to need conversion.
// Output : 
//-----------------------------------------------------------------------------
void CTextureConverter::ConvertFaceTexture( CMapFace * pFace )
{
	ITextureList	tlMatches;
	IEditorTexture *		pNewTexture;

	GetNewTextureMatches( pFace->GetTexture()->GetName(), &tlMatches );

	switch( tlMatches.GetCount() )
	{
	case 0:
		MsgConvertFace( pFace, "ERROR: No matching material.  Cannot convert." );
		m_nErrors++;

		break;
	case 1:
		pNewTexture = tlMatches.GetHead();
		ReplaceFaceTexture( pFace, pNewTexture );

		break;
	default:
		// Multiple matches.  For now, just use the first.
		pNewTexture = tlMatches.GetHead();

		MsgConvertFace( pFace, "WARNING: Multiple matches found.  Using first match (%s).", pNewTexture->GetName() );
		m_nWarnings++;

		ReplaceFaceTexture( pFace, pNewTexture );

		break;
	}
}


//-----------------------------------------------------------------------------
// Purpose: Recurse through the contents of the map, passing decal objects on
//          to be converted.
// Input  : pWorld - pointer to the map to have textures converted.
// Output : All decals in the world have WAD3 textures converted to VMT.
//-----------------------------------------------------------------------------
void CTextureConverter::ConvertDecals( CMapWorld * pWorld )
{
	// Count total map decals so we know how many we have to do (for progress meter).
	pWorld->EnumChildren( ENUMMAPCHILDRENPROC( CountMapDecals ), 0, MAPCLASS_TYPE( CMapEntity ) );

	m_pProgDlg->SetRange( 0, m_nDecalCount );
	m_pProgDlg->SetStep( 3 );
	m_pProgDlg->SetWindowText( "Converting decals..." );

	// Cycle through the solids again and convert as necessary.
	pWorld->EnumChildren( ENUMMAPCHILDRENPROC( CheckDecalTextures ), 0, MAPCLASS_TYPE( CMapEntity ) );
}


//-----------------------------------------------------------------------------
// Purpose: Enumeration function, increment the decals counter if entity is a decal.
// Input  : 
// Output : Always return true to continue enumerating.
//-----------------------------------------------------------------------------
bool CTextureConverter::CountMapDecals( CMapEntity * pEnt, DWORD )
{
	if ( !strcmp( pEnt->GetClassName(), "infodecal" ) )
		m_nDecalCount++;

	return true;	// return true to continue enumerating
}


//-----------------------------------------------------------------------------
// Purpose: Enumeration function, check a decal's texture to determine if
//          conversion is necessary.
// Input  : pEnt - map decal to be checked.
// Output : Always return true to continue enumerating.
//-----------------------------------------------------------------------------
bool CTextureConverter::CheckDecalTextures( CMapEntity * pEnt, DWORD )
{
	if ( strcmp( pEnt->GetClassName(), "infodecal" ) )
		return true;	// not a decal, return true to continue enumerating

	m_nCurrentDecal++;

	m_pProgDlg->SetPos( m_nCurrentDecal );

	if ( strchr( pEnt->GetKeyValue( "texture" ), '/') != NULL )
	{
		m_nSkipped++;
	}
	else
	{
		ConvertDecalTexture( pEnt );
	}

	return true;	// return true to continue enumerating
}


//-----------------------------------------------------------------------------
// Purpose: Determine if any materials match the old texture of a decal and replace
//          appropriately.
// Input  : pEnt - a map decal known to need conversion.
// Output : 
//-----------------------------------------------------------------------------
void CTextureConverter::ConvertDecalTexture( CMapEntity * pEnt )
{
	ITextureList	tlMatches;
	IEditorTexture *		pNewTexture;

	GetNewTextureMatches( pEnt->GetKeyValue( "texture" ), &tlMatches );

	switch( tlMatches.GetCount() )
	{
	case 0:
		MsgConvertDecal( pEnt, "ERROR: No matching material.  Cannot convert." );
		m_nErrors++;

		break;
	case 1:
		pNewTexture = tlMatches.GetHead();
		ReplaceDecalTexture( pEnt, pNewTexture );

		break;
	default:
		// Multiple matches.  For now, just use the first.
		pNewTexture = tlMatches.GetHead();

		MsgConvertDecal( pEnt, "WARNING: Multiple matches found.  Using first match (%s).", pNewTexture->GetName() );
		m_nWarnings++;

		ReplaceDecalTexture( pEnt, pNewTexture );

		break;
	}
}


//-----------------------------------------------------------------------------
// Purpose: Look for material matches for an old texture and add them to a list.
// Input  : pszOldName - old texture name.
//          pMatchList - empty texture list.
// Output : pMatchList - texture list is filled in with matching material textures.
//-----------------------------------------------------------------------------
void CTextureConverter::GetNewTextureMatches( const char * pszOldName, ITextureList * pMatchList )
{
	IEditorTexture *	pTexture;
	int			nIndex;

	nIndex		= 0;
	pTexture	= g_Textures.EnumActiveTextures( &nIndex, tfVMT );

	// loop through all VMT textures
	while ( pTexture != NULL )
	{
		if ( TextureNameMatchesMaterialName( pszOldName, pTexture->GetName() ) )  // check for a match
		{
			pMatchList->AddTail( pTexture );
		}

		pTexture = g_Textures.EnumActiveTextures( &nIndex, tfVMT );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Compare an old texture name to a new material name.
// Input  : pszTextureName - Old texture name.
//          pszMaterialName - New material name.
// Output : Return true if the old texture name is the same (case insensitive)
//          as the last token (delimiter '/') of the new material name, otherwise
//          return false.
//-----------------------------------------------------------------------------
bool CTextureConverter::TextureNameMatchesMaterialName( const char * pszTextureName, const char * pszMaterialName )
{
	const char * pszPartialMaterialName;		// sublocation of the material name

	pszPartialMaterialName = strrchr( pszMaterialName, '/' );  // Find the last '/'

	if ( pszPartialMaterialName != NULL)
	{
		pszPartialMaterialName++;	// Point to the character after the '/'
	}
	else
	{
		pszPartialMaterialName = pszMaterialName;  // No slashes found, just point to the name
	}

	// No '/' found in the VMT name, or the name ended in a '/'.  This shouldn't happen.
	if ( ( pszPartialMaterialName == NULL ) || strlen( pszPartialMaterialName ) == 0 )
		return false;

	if ( stricmp( pszTextureName, pszPartialMaterialName ) == 0 )
		return true;

	return false;
}


//-----------------------------------------------------------------------------
// Purpose: Change the texture on a face, re-scaling if possible.
// Input  : pFace - a map face
//          pNewTexture - texture to place on the map face.
// Output : pFace has a new texture pointer set.
//-----------------------------------------------------------------------------
void CTextureConverter::ReplaceFaceTexture( CMapFace * pFace, IEditorTexture * pNewTexture )
{
	IEditorTexture *	pOldTexture;
	bool		bCanRescale;

	pOldTexture = FindWAD3Texture( pFace->GetTexture()->GetName() );

	if ( pOldTexture == NULL )
	{
		MsgConvertFace( pFace, "WARNING: Couldn't find old texture.  Texture converted but not re-scaled." );
		m_nWarnings++;

		bCanRescale = FALSE;
	}
	else if ( !pOldTexture->Load() )	// make sure old texture is loaded
	{
		MsgConvertFace( pFace, "WARNING: Couldn't load old texture.  Texture converted but not re-scaled." );
		m_nWarnings++;

		bCanRescale = FALSE;
	}
	else
	{
		bCanRescale = TRUE;
	}

	if ( !pNewTexture->Load() )	// make sure new texture is loaded
	{
		MsgConvertFace( pFace, "WARNING: Couldn't load new material.  Texture converted but not re-scaled." );
		m_nWarnings++;

		bCanRescale = FALSE;
	}

	pFace->SetTexture( pNewTexture );

	if ( bCanRescale )
	{
		RescaleFaceTexture( pFace, pOldTexture, pNewTexture );
	}

	m_nSuccesses++;
}


//-----------------------------------------------------------------------------
// Purpose: Change the texture on a decal
// Input  : pEntity - a map decal
//          pNewTexture - texture to place on the map face.
// Output : pEnt has a new texture set
//-----------------------------------------------------------------------------
void CTextureConverter::ReplaceDecalTexture( CMapEntity * pEnt, IEditorTexture * pNewTexture )
{
	pEnt->SetKeyValue( "texture", pNewTexture->GetName() );
	m_nSuccesses++;
}


//-----------------------------------------------------------------------------
// Purpose: Find a WAD3 texture based on a name search.
// Input  : pszName - name of the texture to search for.
// Output : return a texture if found, otherwise NULL.
//-----------------------------------------------------------------------------
IEditorTexture * CTextureConverter::FindWAD3Texture( const char * pszName )
{
	IEditorTexture *	pTexture;
	int			nIndex;

	nIndex		= 0;
	pTexture	= g_Textures.EnumActiveTextures( &nIndex, tfWAD3 );

	// loop through all the WAD3 textures
	while ( pTexture != NULL )
	{
		if ( !strcmp( pTexture->GetName(), pszName ) )		//check for exact match
			return pTexture;

		pTexture = g_Textures.EnumActiveTextures( &nIndex, tfWAD3 );
	}

	return NULL;
}


//-----------------------------------------------------------------------------
// Purpose: Change the scale and shift values of a texture, based on old texture
//          image dimensions compared to new texture image dimensions.
// Input  : pFace - map face that the texture to be scaled is on.
//          pOldTexture - the old texture.
//          pNewTexture - the new texture.
// Output : pFace->scale and pface->texture are modified if either the height or
//          width (or both) of the texture has changed.
//-----------------------------------------------------------------------------
void CTextureConverter::RescaleFaceTexture( CMapFace * pFace, IEditorTexture * pOldTexture, IEditorTexture * pNewTexture )
{
	int nOldWidth;
	int nOldHeight;
	int nNewWidth;
	int nNewHeight;

	nOldWidth	= pOldTexture->GetWidth();
	nOldHeight	= pOldTexture->GetHeight();
	nNewWidth	= pNewTexture->GetWidth();
	nNewHeight	= pNewTexture->GetHeight();

	// Divide by 0 checks
	if ( ( nOldWidth == 0 ) || ( nOldHeight == 0 ) )
	{
		MsgConvertFace( pFace,
						"WARNING: Invalid old texture dimensions (%dx%d).  Texture converted but not re-scaled.",
						nOldWidth,
						nOldHeight
						);
		m_nWarnings++;
		return;
	}

	// Divide by 0 checks
	if ( ( nNewWidth == 0 ) || ( nNewHeight == 0 ) )
	{
		MsgConvertFace( pFace,
						"WARNING: Invalid new material dimensions (%dx%d).  Texture converted but not re-scaled.",
						nNewWidth,
						nNewHeight
						);
		m_nWarnings++;
		return;
	}



	if ( nOldWidth != nNewWidth )
	{
		// Adjust the width scale by an old to new ratio
		pFace->texture.scale[ 0 ] = pFace->texture.scale[ 0 ] * nOldWidth / nNewWidth;

		// Adjust the height shift by a new to old ratio
		pFace->texture.UAxis[ 3 ] = pFace->texture.UAxis[ 3 ] * nNewWidth / nOldWidth;
	}

	if ( nOldHeight != nNewHeight )
	{
		// Adjust the height scale by an old to new ratio
		pFace->texture.scale[ 1 ] = pFace->texture.scale[ 1 ] * nOldHeight / nNewHeight;

		// Adjust the height shift by a new to old ratio
		pFace->texture.VAxis[ 3 ] = pFace->texture.VAxis[ 3 ] * nNewHeight / nOldHeight;
	}

	pFace->CalcTextureCoords();		// recompute internals base on the new scaling and shifting.
}


//-----------------------------------------------------------------------------
// Purpose: Send a message to WC's message window about the specified face.
// Input  : pFace - The map face the message relates to
//			format - The message format string, *printf style
//			... - The remaining arguments of the *printf style message
// Output : A status message is sent to the WC message window.
//-----------------------------------------------------------------------------
void CTextureConverter::MsgConvertFace( CMapFace * pFace, const char * format, ... )
{
	va_list	ptr;
	char	message[ 1024 ];
	Vector	vecFaceCenter;

	pFace->GetCenter( vecFaceCenter );

	va_start( ptr, format );
	_vsnprintf( message, 1024, format, ptr );
	va_end( ptr );

	Msg(	mwStatus,
			"[face] %s at (%d,%d,%d):  %s",
			pFace->GetTexture()->GetName(),
			(int)vecFaceCenter[ 0 ],
			(int)vecFaceCenter[ 1 ],
			(int)vecFaceCenter[ 2 ],
			message
	);
}


//-----------------------------------------------------------------------------
// Purpose: Send a message to WC's message window about the specified decal.
// Input  : pEnt - The map decal the message relates to
//			format - The message format string, *printf style
//			... - The remaining arguments of the *printf style message
// Output : A status message is sent to the WC message window.
//-----------------------------------------------------------------------------
void CTextureConverter::MsgConvertDecal( CMapEntity * pEnt, const char * format, ... )
{
	va_list	ptr;
	char	message[ 1024 ];
	Vector	vecOrigin;

	pEnt->GetOrigin( vecOrigin );

	va_start( ptr, format );
	_vsnprintf( message, 1024, format, ptr );
	va_end( ptr );

	Msg(	mwStatus,
			"[decal] %s at (%d,%d,%d):  %s",
			pEnt->GetKeyValue("texture"),
			(int) vecOrigin.x,
			(int) vecOrigin.y,
			(int) vecOrigin.z,
			message
	);
}


//-----------------------------------------------------------------------------
// Purpose: Display information about the full conversion process.
// Input  : 
// Output : Values of the counters are logged.
//-----------------------------------------------------------------------------
void CTextureConverter::DisplayStatistics( void )
{
	Msg( mwStatus, "==================" );
	Msg( mwStatus, "Conversion summary:" );
	Msg( mwStatus, "==================" );
	Msg( mwStatus, "Total solids:                 %10d", m_nSolidCount );
	Msg( mwStatus, "Total faces:                  %10d", m_nFaceCount );
	Msg( mwStatus, "Total decals:                 %10d", m_nDecalCount );
	Msg( mwStatus, "Total conversions:            %10d", m_nFaceCount + m_nDecalCount );
	Msg( mwStatus, "Successful conversions:       %10d", m_nSuccesses );
	Msg( mwStatus, "Skipped conversions           %10d", m_nSkipped );
	Msg( mwStatus, "Conversion errors:            %10d", m_nErrors );
	Msg( mwStatus, "Conversion warnings:          %10d", m_nWarnings );
}
