//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Texture management functions. Exposes a list of available textures,
//			texture groups, and Most Recently Used textures.
//
//			There is one texture context per game configuration in GameCfg.ini.
//			
//=============================================================================//

#include "stdafx.h"
#include <process.h>
#include <io.h>
#include <sys\stat.h>
#include <fcntl.h>
#include "DummyTexture.h"		// Specific IEditorTexture implementation
#include "GlobalFunctions.h"
#include "MainFrm.h"
#include "MapDoc.h"
#include "Material.h"			// Specific IEditorTexture implementation
#include "Options.h"
#include "TextureSystem.h"
#include "WADTexture.h"			// Specific IEditorTexture implementation
#include "WADTypes.h"
#include "hammer.h"
#include "filesystem.h"
#include "materialsystem/itexture.h"
#include "tier1/utldict.h"
#include "FaceEditSheet.h"

// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>


#pragma warning(disable:4244)


#define _GraphicCacheAllocate(n)	malloc(n)
#define IsSortChr(ch) ((ch == '-') || (ch == '+'))


//-----------------------------------------------------------------------------
// Stuff for loading WAD3 files.
//-----------------------------------------------------------------------------
typedef struct
{
	int			filepos;
	int			disksize;
	int			size;					// uncompressed
	char		type;
	char		compression;
	char		pad1, pad2;
	char		name[16];				// must be null terminated
} WAD3lumpinfo_t;


	
//-----------------------------------------------------------------------------
// List of global graphics
//-----------------------------------------------------------------------------
CTextureSystem g_Textures;



//-----------------------------------------------------------------------------
// CMaterialFileChangeWatcher implementation.
//-----------------------------------------------------------------------------
void CMaterialFileChangeWatcher::Init( CTextureSystem *pSystem, intp context )
{
	m_pTextureSystem = pSystem;
	m_Context = context;

	m_Watcher.Init( this );
	
	char searchPaths[1024 * 16];
	if ( g_pFullFileSystem->GetSearchPath( "GAME", false, searchPaths, sizeof( searchPaths ) ) > 0 )
	{
		CUtlVector<char*> searchPathList;
		V_SplitString( searchPaths, ";", searchPathList );

		for ( int i=0; i < searchPathList.Count(); i++ )
		{
			m_Watcher.AddDirectory( searchPathList[i], "materials", true );
		}
		
		searchPathList.PurgeAndDeleteElements();
	}
	else
	{
		Warning( "Error in GetSearchPath. Dynamic material list updating will not be available." );
	}
}

void CMaterialFileChangeWatcher::OnFileChange( const char *pRelativeFilename, const char *pFullFilename )
{
	//Msg( "OnNewFile: %s\n", pRelativeFilename );

	CTextureSystem::EFileType eFileType;
	if ( CTextureSystem::GetFileTypeFromFilename( pRelativeFilename, &eFileType ) )
		m_pTextureSystem->OnFileChange( pRelativeFilename, m_Context, eFileType );
}

void CMaterialFileChangeWatcher::Update()
{
	m_Watcher.Update();
}


//-----------------------------------------------------------------------------
// Purpose: Constructor. Creates the "All" group and sets it as the active group.
//-----------------------------------------------------------------------------
CTextureSystem::CTextureSystem(void)
{
	m_pLastTex = NULL;
	m_nLastIndex = 0;
	m_pActiveContext = NULL;
	m_pActiveGroup = NULL;
	m_pCubemapTexture = NULL;
	m_pNoDrawTexture = NULL;
}


//-----------------------------------------------------------------------------
// Purpose: Destructor. Frees the list of groups and dummy textures.
//-----------------------------------------------------------------------------
CTextureSystem::~CTextureSystem(void)
{
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTextureSystem::FreeAllTextures()
{
	if ( m_pCubemapTexture )
	{
	 	m_pCubemapTexture->DecrementReferenceCount();
		m_pCubemapTexture = NULL;
	}

	int nContextCount = m_TextureContexts.Count();
	for (int nContext = 0; nContext < nContextCount; nContext++)
	{
		TextureContext_t *pContext = &m_TextureContexts.Element(nContext);

		//
		// Delete all the texture groups for this context.
		//
		int nGroupCount = pContext->Groups.Count();
		for (int nGroup = 0; nGroup < nGroupCount; nGroup++)
		{
			delete pContext->Groups.Element(nGroup);
		}

		//
		// Delete dummy textures.
		//
		int nDummyCount = pContext->Dummies.Count();
		for (int nDummy = 0; nDummy < nDummyCount; nDummy++)
		{
			IEditorTexture *pTex = pContext->Dummies.Element(nDummy);
			delete pTex;
		}
	}

	//
	// Delete all the textures from the master list.
	//
	for (int i = 0; i < m_Textures.Count(); i++)
	{
		IEditorTexture *pTex = m_Textures[i];
		delete pTex;
	}
	m_Textures.RemoveAll();

	m_pLastTex = NULL;
	m_nLastIndex = -1;


	// Delete the keywords.
	m_Keywords.PurgeAndDeleteElements();
	m_ChangeWatchers.PurgeAndDeleteElements();
}


//-----------------------------------------------------------------------------
// Purpose: Adds a texture to the master list of textures.
// Input  : pTexture - Pointer to texture to add.
// Output : Returns the index of the texture in the master texture list.
//-----------------------------------------------------------------------------
int CTextureSystem::AddTexture(IEditorTexture *pTexture)
{
	return m_Textures.AddToTail(pTexture);
}


//-----------------------------------------------------------------------------
// Purpose: Begins iterating the list of texture/material keywords.
//-----------------------------------------------------------------------------
int CTextureSystem::GetNumKeywords(void)
{
	return(m_Keywords.Count());
}


//-----------------------------------------------------------------------------
// Purpose: Continues iterating the list of texture/material keywords.
//-----------------------------------------------------------------------------
const char *CTextureSystem::GetKeyword(int pos)
{
	return(m_Keywords.Element(pos));
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *piIndex - 
//			bUseMRU - 
// Output : 
//-----------------------------------------------------------------------------
IEditorTexture *CTextureSystem::EnumActiveTextures(int *piIndex, TEXTUREFORMAT eDesiredFormat) const
{
	Assert(piIndex != NULL);
	
	if (piIndex != NULL)
	{
		if (m_pActiveGroup != NULL)
		{
			IEditorTexture *pTex = NULL;

			do
			{
				pTex = m_pActiveGroup->GetTexture(*piIndex);
				if (pTex != NULL)
				{
					(*piIndex)++;

					if ((eDesiredFormat == tfNone) || (pTex->GetTextureFormat() == eDesiredFormat))
					{
						return(pTex);
					}
				}
			} while (pTex != NULL);
		}
	}

	return(NULL);
}


//-----------------------------------------------------------------------------
// Purpose: Initializes the texture system.
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CTextureSystem::Initialize(HWND hwnd)
{
	bool bWAD = CWADTexture::Initialize();
	bool bMaterial = CMaterial::Initialize(hwnd);

	return(bWAD && bMaterial);
}


//-----------------------------------------------------------------------------
// Purpose: Shuts down the texture system.
//-----------------------------------------------------------------------------
void CTextureSystem::ShutDown(void)
{
	CWADTexture::ShutDown();
	CMaterial::ShutDown();
	FreeAllTextures();
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pszName - 
//			piIndex - 
//			bDummy - 
// Output : 
//-----------------------------------------------------------------------------
IEditorTexture *CTextureSystem::FindActiveTexture(LPCSTR pszInputName, int *piIndex, BOOL bDummy)
{

	// The .vmf file format gets confused if there are backslashes in material names,
	// so make sure they're all using forward slashes here.
	char szName[MAX_PATH];
	Q_StrSubst( pszInputName, "\\", "/", szName, sizeof( szName ) );
	const char *pszName = szName;
	IEditorTexture *pTex = NULL;
	//
	// Check the cache first.
	//
	if (m_pLastTex && !stricmp(pszName, m_pLastTex->GetName()))
	{
		if (piIndex)
		{
			*piIndex = m_nLastIndex;
		}

		return m_pLastTex;
	}

	int iIndex = 0;

	// We're finding by name, so we don't care what the format is as long as the name matches.
	if ( m_pActiveGroup )
	{
		pTex = m_pActiveGroup->FindTextureByName( pszName, &iIndex, tfNone );
		if ( pTex )
		{
			if ( piIndex )
				*piIndex = iIndex;
			
			m_pLastTex = pTex;
			m_nLastIndex = iIndex;
			
			return pTex;
		}
	}

	//
	// Let's try again, this time with \textures\ decoration
	// TODO: remove this?
	//
	{
		iIndex = 0;
		char szBuf[512];

		sprintf(szBuf, "textures\\%s", pszName);

		for (int i = strlen(szBuf) -1; i >= 0; i--)
		{
			if (szBuf[i] == '/')
				szBuf[i] = '\\';
		}

		strlwr(szBuf);

		if ( m_pActiveGroup )
		{
			pTex = m_pActiveGroup->FindTextureByName( szBuf, &iIndex, tfNone );
			if ( pTex )
			{
				if ( piIndex )
					*piIndex = iIndex;
				
				m_pLastTex = pTex;
				m_nLastIndex = iIndex;
				
				return pTex;
			}
		}
	}
	//
	// Caller doesn't want dummies.
	//
	if (!bDummy)
	{
		return(NULL);
	}

	Assert(!piIndex);

	//
	// Check the list of dummies for a texture with the same name and texture format.
	//
	if (m_pActiveContext)
	{
		int nDummyCount = m_pActiveContext->Dummies.Count();
		for (int nDummy = 0; nDummy < nDummyCount; nDummy++)
		{
			IEditorTexture *pTexDummy = m_pActiveContext->Dummies.Element(nDummy);
			if (!strcmpi(pszName, pTexDummy->GetName()))
			{
				m_pLastTex = pTexDummy;
				m_nLastIndex = -1;
				return(pTexDummy);
			}
		}

		//
		// Not found; add a dummy as a placeholder for the missing texture.
		//
		pTex = AddDummy(pszName, g_pGameConfig->GetTextureFormat());
	}

	if (pTex != NULL)
	{
		m_pLastTex = pTex;
		m_nLastIndex = -1;
	}

	return(pTex);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pTex - 
//-----------------------------------------------------------------------------
void CTextureSystem::AddMRU(IEditorTexture *pTex)
{
	if (!m_pActiveContext)
		return;

	int nIndex = m_pActiveContext->MRU.Find(pTex);
	if (nIndex != -1)
	{
		m_pActiveContext->MRU.Remove(nIndex);
	}
	else if (m_pActiveContext->MRU.Count() == 8)
	{
		m_pActiveContext->MRU.Remove(7);
	}

	m_pActiveContext->MRU.AddToHead(pTex);
}


//-----------------------------------------------------------------------------
// Purpose: Change palette on all textures.
// Input  : 
// dvs: need to handle a palette change for Quake support
//-----------------------------------------------------------------------------
void CTextureSystem::InformPaletteChanged()
{
//	int nGraphics = GetCount();
//
//	for (int i = 0; i < nGraphics; i++)
//	{
//		IEditorTexture *pTex = &GetAt(i);
//	}
}


//-----------------------------------------------------------------------------
// Purpose: Returns the texture context that corresponds to the given game config.
//-----------------------------------------------------------------------------
TextureContext_t *CTextureSystem::FindTextureContextForConfig(CGameConfig *pConfig)
{
	for (int i = 0; i < m_TextureContexts.Count(); i++)
	{
		if (m_TextureContexts.Element(i).pConfig == pConfig)
		{
			return &m_TextureContexts.Element(i);
		}
	}

	return NULL;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTextureSystem::SetActiveConfig(CGameConfig *pConfig)
{
	TextureContext_t *pContext = FindTextureContextForConfig(pConfig);
	if (pContext)
	{
		m_pActiveContext = pContext;
		m_pActiveGroup = m_pActiveContext->pAllGroup;
	}
	else
	{
		m_pActiveContext = NULL;
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : char *pcszName - 
//-----------------------------------------------------------------------------
void CTextureSystem::SetActiveGroup(const char *pcszName)
{
	if (!m_pActiveContext)
		return;

	char szBuf[MAX_PATH];
	sprintf(szBuf, "textures\\%s", pcszName);

	int iCount = m_pActiveContext->Groups.Count();
	for (int i = 0; i < iCount; i++)
	{
		CTextureGroup *pGroup = m_pActiveContext->Groups.Element(i);
		if (!strcmpi(pGroup->GetName(), pcszName))
		{
			m_pActiveGroup = pGroup;
			return;
		}

		if (strstr(pGroup->GetName(), pcszName))
		{
			m_pActiveGroup = pGroup;
			return;
		}

	}

	TRACE0("No Group Found!");
}


void HammerFileSystem_ReportSearchPath( const char *szPathID )
{
	char szSearchPath[ 4096 ];
	g_pFullFileSystem->GetSearchPath( szPathID, true, szSearchPath, sizeof( szSearchPath ) );

	Msg( mwStatus, "------------------------------------------------------------------" );

	char *pszOnePath = strtok( szSearchPath, ";" );
	while ( pszOnePath )
	{
		Msg( mwStatus, "Search Path (%s): %s", szPathID, pszOnePath );
		pszOnePath = strtok( NULL, ";" );
	}
}


//-----------------------------------------------------------------------------
// FIXME: Make this work correctly, using the version in filesystem_tools.cpp
// (it doesn't work currently owing to filesystem setup issues)
//-----------------------------------------------------------------------------
void HammerFileSystem_SetGame( const char *pExeDir, const char *pModDir )
{
	static bool s_bOnce = false;
	Assert( !s_bOnce );
	s_bOnce = true;

	char buf[MAX_PATH];

	Q_snprintf( buf, MAX_PATH, "%s\\hl2", pExeDir );
	g_pFullFileSystem->AddSearchPath( buf, "GAME", PATH_ADD_TO_HEAD );

	if ( pModDir && *pModDir != '\0' )
	{
		g_pFullFileSystem->AddSearchPath( pModDir, "GAME", PATH_ADD_TO_HEAD );
	}

	HammerFileSystem_ReportSearchPath( "GAME" );
}


//-----------------------------------------------------------------------------
// Purpose: Loads textures from all texture files.
//-----------------------------------------------------------------------------
void CTextureSystem::LoadAllGraphicsFiles(void)
{
	FreeAllTextures();

	// For each game config...
	// dvs: Disabled for single-config running.
	//for (int nConfig = 0; nConfig < Options.configs.GetGameConfigCount(); nConfig++)
	{
		//CGameConfig *pConfig = Options.configs.GetGameConfig(nConfig);
		CGameConfig *pConfig = g_pGameConfig;

		// Create a new texture context with the WADs and materials for that config.
		TextureContext_t *pContext = AddTextureContext();

		// Bind it to this config.
		pContext->pConfig = pConfig;

		// Create a group to hold all the textures for this context.
		pContext->pAllGroup = new CTextureGroup("All Textures");
		pContext->Groups.AddToTail(pContext->pAllGroup);

		HammerFileSystem_SetGame(pConfig->m_szGameExeDir, pConfig->m_szModDir);

		// Set the new context as the active context.
		m_pActiveContext = pContext;

		// Load the textures for all WAD files set in this config.
		// Only do this for configs that use WAD textures.
		if (pConfig->GetTextureFormat() == tfWAD3)
		{
			LoadWADFiles(pConfig);
		}

		// Load the materials for this config.
		// Do this unconditionally so that we get necessary editor materials.
		LoadMaterials(pConfig);

		m_pActiveContext->pAllGroup->Sort();
	}
}


//-----------------------------------------------------------------------------
// Purpose: Loads all WAD files for the given game config.
//-----------------------------------------------------------------------------
void CTextureSystem::LoadWADFiles(CGameConfig *pConfig)
{
	// dvs: FIXME: WADs are not currently per-config
	for (int i = 0; i < Options.textures.nTextureFiles; i++)
	{
		LoadGraphicsFile(Options.textures.TextureFiles[i]);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Loads all the materials for the given game config.
//-----------------------------------------------------------------------------
void CTextureSystem::LoadMaterials(CGameConfig *pConfig)
{
	CTextureGroup *pGroup = new CTextureGroup("Materials");
	pGroup->SetTextureFormat(tfVMT);
	m_pActiveContext->Groups.AddToTail(pGroup);

	// Add all the materials to the group.
	CMaterial::EnumerateMaterials( this, "materials", (int)pGroup, INCLUDE_WORLD_MATERIALS );
	
	// Watch the materials directory recursively...
	CMaterialFileChangeWatcher *pWatcher = new CMaterialFileChangeWatcher;
	pWatcher->Init( this, (int)pGroup );
	m_ChangeWatchers.AddToTail( pWatcher );

	Assert( m_pCubemapTexture == NULL );

	m_pCubemapTexture = MaterialSystemInterface()->FindTexture( "editor/cubemap", NULL, true );

	if ( m_pCubemapTexture )
	{
		m_pCubemapTexture->IncrementReferenceCount();
		CMatRenderContextPtr pRenderContext( MaterialSystemInterface() );
		pRenderContext->BindLocalCubemap( m_pCubemapTexture );
	}
	
	// Get the nodraw texture.
	m_pNoDrawTexture = NULL;
	for ( int i=0; i < m_Textures.Count(); i++ )
	{
		if ( V_stricmp( m_Textures[i]->GetName(), "tools/toolsnodraw" ) == 0 || V_stricmp( m_Textures[i]->GetName(), "tools/toolsnodraw" ) == 0 )
		{
			m_pNoDrawTexture = m_Textures[i];
			break;
		}
	}
	if ( !m_pNoDrawTexture )				
		m_pNoDrawTexture = CMaterial::CreateMaterial( "tools/toolsnodraw", true );
}

void CTextureSystem::RebindDefaultCubeMap()
{
	// rebind with the default cubemap
	
	if (  m_pCubemapTexture )
	{
		CMatRenderContextPtr pRenderContext( MaterialSystemInterface() );
		pRenderContext->BindLocalCubemap( m_pCubemapTexture );
	}
}


void CTextureSystem::UpdateFileChangeWatchers()
{
	for ( int i=0; i < m_ChangeWatchers.Count(); i++ )
		m_ChangeWatchers[i]->Update();
}


void CTextureSystem::OnFileChange( const char *pFilename, intp context, CTextureSystem::EFileType eFileType )
{
	// It requires the forward slashes later...
	char fixedSlashes[MAX_PATH];
	V_StrSubst( pFilename, "\\", "/", fixedSlashes, sizeof( fixedSlashes ) );	

	// Get rid of the extension.
	if ( V_strlen( fixedSlashes ) < 5 )
	{
		Assert( false );
		return;
	}
	fixedSlashes[ V_strlen( fixedSlashes ) - 4 ] = 0;


	// Handle it based on what type of file we've got.
	if ( eFileType == k_eFileTypeVMT )
	{
		IEditorTexture *pTex = FindActiveTexture( fixedSlashes, NULL, FALSE );
		if ( pTex )
		{
			pTex->Reload( true );
		}
		else
		{
			EnumMaterial( fixedSlashes, context );
			IEditorTexture *pTexFixed = FindActiveTexture( fixedSlashes, NULL, FALSE );
			if ( pTexFixed )
			{
				GetMainWnd()->m_TextureBar.NotifyNewMaterial( pTexFixed );
				GetMainWnd()->GetFaceEditSheet()->NotifyNewMaterial( pTexFixed );
			}
		}
	}
	else if ( eFileType == k_eFileTypeVTF )
	{
		// Whether a VTF was added, removed, or modified, we do the same thing.. refresh it and any materials that reference it.
		ITexture *pTexture = materials->FindTexture( fixedSlashes, TEXTURE_GROUP_UNACCOUNTED, false );
		if ( pTexture )
		{
			pTexture->Download( NULL );
			ReloadMaterialsUsingTexture( pTexture );
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: Load any materials that reference this texture. Used so we can refresh a 
// material's preview image if a relevant .vtf changes.
//-----------------------------------------------------------------------------
void CTextureSystem::ReloadMaterialsUsingTexture( ITexture *pTestTexture )
{
	for ( int i=0; i < m_Textures.Count(); i++ )
	{
		IEditorTexture *pEditorTex = m_Textures[i];
		IMaterial *pMat = pEditorTex->GetMaterial( false );
		if ( !pMat )
			continue;
		
		IMaterialVar **pParams = pMat->GetShaderParams();
		int nParams = pMat->ShaderParamCount();
		for ( int iParam=0; iParam < nParams; iParam++ )
		{
			if ( pParams[iParam]->GetType() != MATERIAL_VAR_TYPE_TEXTURE )
				continue;
			
			ITexture *pTex = pParams[iParam]->GetTextureValue();
			if ( !pTex )
				continue;
			
			if ( pTex == pTestTexture )
			{
				pEditorTex->Reload( true );
			}
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: Figure out the file type from its extension. Returns false if we don't have an enum for that extension.
//-----------------------------------------------------------------------------
bool CTextureSystem::GetFileTypeFromFilename( const char *pFilename, CTextureSystem::EFileType *pFileType )
{
	char strRight[16];
	V_StrRight( pFilename, 4, strRight, sizeof( strRight ) );
	if ( V_stricmp( strRight, ".vmt" ) == 0 )
	{
		*pFileType = CTextureSystem::k_eFileTypeVMT;
		return true;
	}
	else if ( V_stricmp( strRight, ".vtf" ) == 0 )
	{
		*pFileType = CTextureSystem::k_eFileTypeVTF;
		return true;
	}
	return false;
}


//-----------------------------------------------------------------------------
// Purpose: Loads textures from all texture files.
//-----------------------------------------------------------------------------
void CTextureSystem::ReloadTextures( const char *pFilterName )
{
	MaterialSystemInterface()->ReloadMaterials( pFilterName );

	for ( int i = 0; i < m_Textures.Count(); i++ )
	{
		if ( !Q_stristr( pFilterName, m_Textures[i]->GetName() ) )
			continue;

		m_Textures[i]->Reload( false );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Adds a placeholder texture for a texture that exists in the map, but
//			was not found on disk.
// Input  : pszName - Name of missing texture.
// Output : Returns a pointer to the new dummy texture.
//-----------------------------------------------------------------------------
IEditorTexture *CTextureSystem::AddDummy(LPCTSTR pszName, TEXTUREFORMAT eFormat)
{
	if (!m_pActiveContext)
		return NULL;

	IEditorTexture *pTex = new CDummyTexture(pszName, eFormat);
	m_pActiveContext->Dummies.AddToTail(pTex);

	return(pTex);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : elem1 - 
//			elem2 - 
// Output : static int __cdecl
//-----------------------------------------------------------------------------
static int __cdecl SortTexturesProc(IEditorTexture * const *elem1, IEditorTexture * const *elem2)
{
	IEditorTexture *pElem1 = *((IEditorTexture **)elem1);
	IEditorTexture *pElem2 = *((IEditorTexture **)elem2);

	Assert((pElem1 != NULL) && (pElem2 != NULL));
	if ((pElem1 == NULL) || (pElem2 == NULL))
	{
		return(0);
	}

	const char *pszName1 = pElem1->GetName();
	const char *pszName2 = pElem2->GetName();

	char ch1 = pszName1[0];
	char ch2 = pszName2[0];

	if (IsSortChr(ch1) && !IsSortChr(ch2))
	{
		int iFamilyLen = strlen(pszName1+2);
		int iFamily = strnicmp(pszName1+2, pszName2, iFamilyLen);
		if (!iFamily)
		{
			return(-1);	// same family - put elem1 before elem2
		}
		return(iFamily);	// sort normally
	}
	else if (!IsSortChr(ch1) && IsSortChr(ch2))
	{
		int iFamilyLen = strlen(pszName2+2);
		int iFamily = strnicmp(pszName1, pszName2+2, iFamilyLen);
		if (!iFamily)
		{
			return(1);	// same family - put elem2 before elem1
		}
		return(iFamily);	// sort normally
	}
	else if (IsSortChr(ch1) && IsSortChr(ch2))
	{
		// do family name sorting
		int iFamily = strcmpi(pszName1+2, pszName2+2);

		if (!iFamily)
		{
			// same family - sort by number
			return pszName1[1] - pszName2[1];
		}

		// different family
		return(iFamily);
	}

	return(strcmpi(pszName1, pszName2));
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : sizeSrc - 
//			sizeDest - 
//			*src - 
//			*dest - 
//-----------------------------------------------------------------------------
void ScaleBitmap(CSize sizeSrc, CSize sizeDest, char *src, char *dest)
{
    int i;
    int e_y = (sizeSrc.cy << 1) - sizeDest.cy;
    int sizeDest2_y = (sizeDest.cy << 1);
	int sizeSrc2_y = sizeSrc.cy << 1;
	int srcline = 0, destline = 0;
	char *srclinep, *destlinep;
	int e_x = (sizeSrc.cx << 1) - sizeDest.cx;
	int sizeDest2_x = (sizeDest.cx << 1);
	int sizeSrc2_x = sizeSrc.cx << 1;

    for( i = 0; i < sizeDest.cy; i++ )
    {
		// scale by X
		{
			srclinep = src + (srcline * sizeSrc.cx);
			destlinep = dest + (destline * sizeDest.cx);

			for( int j = 0; j < sizeDest.cx; j++ )
			{
				*destlinep = *srclinep;

				while( e_x >= 0 )
				{
					++srclinep;
					e_x -= sizeDest2_x;
				}

				++destlinep;
				e_x += sizeSrc2_x;
			}
		}

        while( e_y >= 0 )
        {
            ++srcline;
            e_y -= sizeDest2_y;
        }

        ++destline;
        e_y += sizeSrc2_y;
    }
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : id - 
//			*piIndex - 
// Output : GRAPHICSFILESTRUCT *
//-----------------------------------------------------------------------------
bool CTextureSystem::FindGraphicsFile(GRAPHICSFILESTRUCT *pFileInfo, DWORD id, int *piIndex)
{
	for (int i = 0; i < m_GraphicsFiles.Count(); i++)
	{
		if (m_GraphicsFiles[i].id == id)
		{
			if (piIndex)
			{
				piIndex[0] = i;
			}

			if (pFileInfo != NULL)
			{
				*pFileInfo = m_GraphicsFiles[i];
			}

			return(true);
		}
	}

	return(false);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pFile - 
//			fd - 
//			pGroup - 
//-----------------------------------------------------------------------------
void CTextureSystem::LoadGraphicsFileWAD3(GRAPHICSFILESTRUCT *pFile, int fd, CTextureGroup *pGroup)
{
	// read wad header
	wadinfo_t hdr;
	_lseek(fd, 0, SEEK_SET);
	_read(fd, (char*)&hdr, sizeof hdr);

	_lseek(fd, hdr.infotableofs, SEEK_SET);

	// allocate directory memory.
	WAD3lumpinfo_t *dir = new WAD3lumpinfo_t[hdr.numlumps];
		
	// read entries.
	_read(fd, dir, sizeof(WAD3lumpinfo_t) * hdr.numlumps);

	// load graphics!
	for (int i = 0; i < hdr.numlumps; i++)
	{
		if (dir[i].type == TYP_MIPTEX)
		{
			_lseek(fd, dir[i].filepos, SEEK_SET);

			CWADTexture *pNew = new CWADTexture;
			if (pNew != NULL)
			{
				if (pNew->Init(fd, pFile->id, FALSE, dir[i].name))
				{
					pNew->SetTextureFormat(pFile->format);

					//
					// Add the texture to master list of textures.
					//
					AddTexture(pNew);

					//
					// Add the texture's index to the given group and to the "All" group.
					//
					pGroup->AddTexture(pNew);
					if (pGroup != m_pActiveContext->pAllGroup)
					{
						m_pActiveContext->pAllGroup->AddTexture(pNew);
					}
				}
				else
				{
					delete pNew;
				}
			}
		}
	}

	// free memory
	delete[] dir;
}


//-----------------------------------------------------------------------------
// Purpose: Loads all textures in a given graphics file and returns an ID for
//			the file.
// Input  : filename - Full path of graphics file to load.
// Output : Returns the file ID.
//-----------------------------------------------------------------------------
DWORD CTextureSystem::LoadGraphicsFile(const char *pFilename)
{
	static DWORD __GraphFileID = 1;	// must start at 1.

	//
	// Make sure it's not already there.
	//
	int i = m_GraphicsFiles.Count() - 1;
	while (i > -1)
	{
		if (!strcmp(m_GraphicsFiles[i].filename, pFilename))
		{
			return(m_GraphicsFiles[i].id);
		}

		i--;
	}

	//
	// Is this a WAD file?
	//
	DWORD dwAttrib = GetFileAttributes(pFilename);
	if (dwAttrib == 0xFFFFFFFF)
	{
		return(0);
	}

	GRAPHICSFILESTRUCT gf;

	if (!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY))
	{
		// open the file, and add it to the GraphicFileList array
		gf.fd = _open(pFilename, _O_BINARY | _O_RDONLY);
		if (gf.fd == -1)
		{
			// todo: if errno is "out of handles", close some other
			// graphics files.

			// StatusMsg(IDS_ERROPENGRAPHFILE, errno);
			return 0;	// could not open
		}

		char buf[4];
		_read(gf.fd, buf, 4);

		//
		// Make sure the file is in a format that we can read.
		//
		if (!memcmp(buf, "WAD3", 4))
		{
			gf.format = tfWAD3;
		}
		else
		{
			char str[MAX_PATH*2];
			Q_snprintf( str, sizeof(str), "The file \"%s\" is not a valid WAD3 file and will not be used.", pFilename);
			AfxMessageBox(str, MB_ICONEXCLAMATION | MB_OK);
			_close(gf.fd);
			return(0);
		}
	}

	// got it -- setup the rest of the gf structure
	gf.id = __GraphFileID++;
	Q_strncpy( gf.filename, pFilename, sizeof(gf.filename) );
	gf.bLoaded = FALSE;

	//
	// Add file to list of texture files.
	//
	m_GraphicsFiles.AddToTail(gf);

	//
	// Create a new texture group for the file.
	//
	CTextureGroup *pGroup = new CTextureGroup(pFilename);
	pGroup->SetTextureFormat(gf.format);
	m_pActiveContext->Groups.AddToTail(pGroup);
	
	//
	// Load the textures from the file and place them in the texture group.
	//
	LoadGraphicsFileWAD3(&gf, gf.fd, pGroup);
	gf.bLoaded = TRUE;

	//
	// Sort this group's list
	//
	pGroup->Sort();

	return(gf.id);
}


//-----------------------------------------------------------------------------
// Purpose: Determines whether or not there is at least one available texture
//			group for a given texture format.
// Input  : format - Texture format to look for.
// Output : Returns TRUE if textures of a given format are available, FALSE if not.
//-----------------------------------------------------------------------------
bool CTextureSystem::HasTexturesForConfig(CGameConfig *pConfig)
{
	if (!pConfig)
		return false;

	TextureContext_t *pContext = FindTextureContextForConfig(pConfig);
	if (!pContext)
		return false;

	int nCount = pContext->Groups.Count();
	for (int i = 0; i < nCount; i++)
	{
		CTextureGroup *pGroup = pContext->Groups.Element(i);
		if (pGroup->GetTextureFormat() == pConfig->GetTextureFormat())
		{
			return true;
		}
	}

	return false;
}


//-----------------------------------------------------------------------------
// Used to add all the world materials into the material list
//-----------------------------------------------------------------------------
bool CTextureSystem::EnumMaterial( const char *pMaterialName, int nContext )
{
	CTextureGroup *pGroup = (CTextureGroup *)nContext;
	CMaterial *pMaterial = CMaterial::CreateMaterial(pMaterialName, false);
	if (pMaterial != NULL)
	{
		// Add it to the master list of textures.
		AddTexture(pMaterial);

		// Add the texture's index to the given group and to the "All" group.
		pGroup->AddTexture(pMaterial);
		if (pGroup != m_pActiveContext->pAllGroup)
		{
			m_pActiveContext->pAllGroup->AddTexture(pMaterial);
		}
	}
	return true;
}


//-----------------------------------------------------------------------------
// Registers the keywords as existing in a particular material
//-----------------------------------------------------------------------------
void CTextureSystem::RegisterTextureKeywords( IEditorTexture *pTexture )
{
	//
	// Add any new keywords from this material to the list of keywords.
	//
	char szKeywords[MAX_PATH];
	pTexture->GetKeywords(szKeywords);
	if (szKeywords[0] != '\0')
	{
		char *pch = strtok(szKeywords, " ,;");
		while (pch != NULL)
		{
			// dvs: hide in a Find function
			bool bFound = false;
			
			for( int pos=0; pos < m_Keywords.Count(); pos++ )
			{
				const char *pszTest = m_Keywords.Element(pos);
				if (!stricmp(pszTest, pch))
				{
					bFound = true;
					break;
				}
			}

			if (!bFound)
			{
				char *pszKeyword = new char[strlen(pch) + 1];
				strcpy(pszKeyword, pch);
				m_Keywords.AddToTail(pszKeyword);
			}

			pch = strtok(NULL, " ,;");
		}
	}
}


//-----------------------------------------------------------------------------
// Used to lazily load in all the textures
//-----------------------------------------------------------------------------
void CTextureSystem::LazyLoadTextures()
{
	if ( m_pActiveContext && m_pActiveContext->pAllGroup && !IsRunningInEngine() )
	{
		m_pActiveContext->pAllGroup->LazyLoadTextures();
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Output : TextureContext_t
//-----------------------------------------------------------------------------
TextureContext_t *CTextureSystem::AddTextureContext()
{
	// Allocate a new texture context.
	int nIndex = m_TextureContexts.AddToTail();

	// Add the group to this config's list of texture groups.
	TextureContext_t *pContext = &m_TextureContexts.Element(nIndex);
	return pContext;
}


//-----------------------------------------------------------------------------
// Opens the source file associated with a material
//-----------------------------------------------------------------------------
void CTextureSystem::OpenSource( const char *pMaterialName )
{
	if ( !pMaterialName )
		return;

	char pRelativePath[MAX_PATH];
	Q_snprintf( pRelativePath, MAX_PATH, "materials/%s.vmt", pMaterialName );

	char pFullPath[MAX_PATH];
	if ( g_pFullFileSystem->GetLocalPath( pRelativePath, pFullPath, MAX_PATH ) )
	{
		ShellExecute( NULL, "open", pFullPath, NULL, NULL, SW_SHOWNORMAL );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Constructor.
// Input  : pszName - Name of group, ie "Materials" or "u:\hl\tfc\tfc.wad".
//-----------------------------------------------------------------------------
CTextureGroup::CTextureGroup(const char *pszName)
{
	strcpy(m_szName, pszName);
	m_eTextureFormat = tfNone;
	m_nTextureToLoad = 0;
}


//-----------------------------------------------------------------------------
// Purpose: Adds a texture to this group.
// Input  : pTexture - Texture to add.
//-----------------------------------------------------------------------------
void CTextureGroup::AddTexture(IEditorTexture *pTexture)
{
	int index = m_Textures.AddToTail(pTexture);
	m_TextureNameMap.Insert( pTexture->GetName(), index );
}


//-----------------------------------------------------------------------------
// Purpose: Sorts the group.
//-----------------------------------------------------------------------------
void CTextureGroup::Sort(void)
{
	m_Textures.Sort(SortTexturesProc);

	// Redo the name map.
	m_TextureNameMap.RemoveAll();
	for ( int i=0; i < m_Textures.Count(); i++ )
	{
		IEditorTexture *pTex = m_Textures[i];
		m_TextureNameMap.Insert( pTex->GetName(), i );
	}

	// Changing the order means we don't know where we should be loading from
	m_nTextureToLoad = 0;
}


//-----------------------------------------------------------------------------
// Purpose: Retrieves a texture by index.
// Input  : nIndex - Index of the texture in this group.
//-----------------------------------------------------------------------------
IEditorTexture *CTextureGroup::GetTexture(int nIndex)
{
	if ((nIndex >= m_Textures.Count()) || (nIndex < 0))
	{
		return(NULL);
	}

	return(m_Textures[nIndex]);
}


//-----------------------------------------------------------------------------
// finds a texture by name
//-----------------------------------------------------------------------------
IEditorTexture *CTextureGroup::GetTexture( char const* pName )
{
	for (int i = 0; i < m_Textures.Count(); i++)
	{
		if (!strcmp(pName, m_Textures[i]->GetName()))
			return m_Textures[i];
	}

	return NULL;
}


//-----------------------------------------------------------------------------
// Quickly find a texture by name.
//-----------------------------------------------------------------------------
IEditorTexture* CTextureGroup::FindTextureByName( const char *pName, int *piIndex, TEXTUREFORMAT eDesiredFormat )
{
	int iMapEntry = m_TextureNameMap.Find( pName );
	if ( iMapEntry == m_TextureNameMap.InvalidIndex() )
	{
		return NULL;
	}
	else
	{
		IEditorTexture *pTex = m_Textures[ m_TextureNameMap[iMapEntry] ];
		if ((eDesiredFormat == tfNone) || (pTex->GetTextureFormat() == eDesiredFormat))
			return pTex;
		else
			return NULL;
	}		
}


//-----------------------------------------------------------------------------
// Used to lazily load in all the textures
//-----------------------------------------------------------------------------
void CTextureGroup::LazyLoadTextures()
{
	// Load at most once per call
	while (m_nTextureToLoad < m_Textures.Count())
	{
		if (!m_Textures[m_nTextureToLoad]->IsLoaded())
		{
			m_Textures[m_nTextureToLoad]->Load();
			++m_nTextureToLoad;
			return;
		}

		// This one was already loaded; skip it
		++m_nTextureToLoad;
	}
}