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

#include "gameuidynamictextures.h"
#include "gamelayer.h"
#include "gamerect.h"
#include "tier1/utlstring.h"
#include "tier1/utlstringmap.h"
#include "tier1/utlbuffer.h"
#include "gameuisystemmgr.h"
#include "tier1/fmtstr.h"
#include "materialsystem/imaterial.h"
#include "materialsystem/imaterialvar.h"


#include "materialsystem/imaterialsystem.h"
#include "materialsystem/imesh.h"
#include "rendersystem/irenderdevice.h"
#include "rendersystem/irendercontext.h"
#include "tier1/keyvalues.h"
#include "materialsystem/IMaterialProxy.h"
#include "materialsystem/imaterialproxyfactory.h"
#include "gameuisystemmgr.h"


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

	 
#define GAMEUI_DYNAMIC_TEXTURE_SHEET_WIDTH 2048
#define GAMEUI_DYNAMIC_TEXTURE_SHEET_HEIGHT 2048

//-----------------------------------------------------------------------------
// A material proxy that resets the base texture to use the dynamic texture
//-----------------------------------------------------------------------------
class CGameControlsProxy : public IMaterialProxy
{
public:
	CGameControlsProxy();
	virtual ~CGameControlsProxy(){};
	virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
	virtual void OnBind( void *pProxyData );
	virtual void Release( void ) 
	{ 
		delete this; 
	}
	virtual IMaterial *GetMaterial();

private:
	IMaterialVar* m_BaseTextureVar;
};

CGameControlsProxy::CGameControlsProxy(): m_BaseTextureVar( NULL )
{
}

bool CGameControlsProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
{
	bool bFoundVar;
	m_BaseTextureVar = pMaterial->FindVar( "$basetexture", &bFoundVar, false );
	return bFoundVar;
}

void CGameControlsProxy::OnBind( void *pProxyData )
{
	const char *pBaseTextureName = ( const char * )pProxyData;
	ITexture *pTexture = g_pMaterialSystem->FindTexture( pBaseTextureName, TEXTURE_GROUP_OTHER, true );
	m_BaseTextureVar->SetTextureValue( pTexture );
}

IMaterial *CGameControlsProxy::GetMaterial()
{
	return m_BaseTextureVar->GetOwningMaterial();
}


//-----------------------------------------------------------------------------
// Factory to create dynamic material. ( Return in this case. )
//-----------------------------------------------------------------------------
class CMaterialProxyFactory : public IMaterialProxyFactory
{
public:
	IMaterialProxy *CreateProxy( const char *proxyName );
	void DeleteProxy( IMaterialProxy *pProxy );
	CreateInterfaceFn GetFactory();
};
static CMaterialProxyFactory s_DynamicMaterialProxyFactory;

IMaterialProxy *CMaterialProxyFactory::CreateProxy( const char *proxyName )
{
	if ( Q_strcmp( proxyName, "GameControlsProxy" ) == NULL )
	{	
		return static_cast< IMaterialProxy * >( new CGameControlsProxy );
	}
	return NULL;
}

void CMaterialProxyFactory::DeleteProxy( IMaterialProxy *pProxy )
{
}

CreateInterfaceFn CMaterialProxyFactory::GetFactory()
{
	return Sys_GetFactoryThis();
}


//-----------------------------------------------------------------------------
// Constructor / Destructor.
//-----------------------------------------------------------------------------
CGameUIDynamicTextures::CGameUIDynamicTextures() 
{
	m_pDynamicTexturePacker = NULL;
	m_RenderMaterial = NULL;
	m_bRegenerate = false;	
}

CGameUIDynamicTextures::~CGameUIDynamicTextures()
{	
	Shutdown();
}

//-----------------------------------------------------------------------------
// Init any render targets needed by the UI.
//-----------------------------------------------------------------------------
void CGameUIDynamicTextures::InitRenderTargets()
{
	if ( !m_TexturePage.IsValid() )
	{
		m_TexturePage.InitRenderTarget( GAMEUI_DYNAMIC_TEXTURE_SHEET_WIDTH, GAMEUI_DYNAMIC_TEXTURE_SHEET_HEIGHT, 
			RT_SIZE_NO_CHANGE, IMAGE_FORMAT_ARGB8888, 
			MATERIAL_RT_DEPTH_NONE, false, "_rt_DynamicUI" );
	}

	if ( m_TexturePage.IsValid() )
	{
		int nSheetWidth = m_TexturePage->GetActualWidth();
		int nSheetHeight = m_TexturePage->GetActualHeight();

		m_pDynamicTexturePacker = new CTexturePacker( nSheetWidth, nSheetHeight, IsGameConsole() ? 0 : 1 );

		KeyValues *pVMTKeyValues = new KeyValues( "GameControls" );
		pVMTKeyValues->SetString( "$basetexture", "_rt_DynamicUI" );
		CMaterialReference material;
		// Material names must be different
		CFmtStr materialName;
		materialName.sprintf( "dynamictx_%s", "_rt_DynamicUI" );
		material.Init( materialName, TEXTURE_GROUP_OTHER, pVMTKeyValues );
		material->Refresh();

		{
			ImageAliasData_t imageData;
			imageData.m_Width = nSheetWidth;
			imageData.m_Height = nSheetHeight;
			imageData.m_Material = material;
			m_ImageAliasMap[ "_rt_DynamicUI" ] = imageData;
		}

		{
			CTextureReference pErrorTexture;
			pErrorTexture.Init( "error", TEXTURE_GROUP_OTHER, true );

			ImageAliasData_t imageData;
			imageData.m_Width = pErrorTexture->GetActualWidth();
			imageData.m_Height = pErrorTexture->GetActualHeight();
			imageData.m_szBaseTextureName = "error";
			imageData.m_Material = NULL;
			m_ImageAliasMap[ "errorImageAlias" ] = imageData;
		}

		pVMTKeyValues = new KeyValues( "GameControls" );
		pVMTKeyValues->SetInt( "$ignorez", 1 );
		KeyValues *pProxies = new KeyValues( "Proxies" );
		pProxies->AddSubKey( new KeyValues( "GameControlsProxy" ) );
		pVMTKeyValues->AddSubKey( pProxies );
		m_RenderMaterial.Init( "dynamic_render_texture", TEXTURE_GROUP_OTHER, pVMTKeyValues );
		m_RenderMaterial->Refresh();

		g_pGameUISystemMgrImpl->InitImageAlias( "defaultImageAlias" );
		g_pGameUISystemMgrImpl->LoadImageAliasTexture( "defaultImageAlias", "vguiedit/pixel"  );
	}
}

IMaterialProxy *CGameUIDynamicTextures::CreateProxy( const char *proxyName )
{
	return s_DynamicMaterialProxyFactory.CreateProxy( proxyName );
}

//-----------------------------------------------------------------------------
// 
//-----------------------------------------------------------------------------
void CGameUIDynamicTextures::Shutdown()
{
	m_RenderMaterial.Shutdown();
	m_TexturePage.Shutdown();

	if ( m_pDynamicTexturePacker )
	{
		delete m_pDynamicTexturePacker;
	}

	m_pDynamicTexturePacker = NULL;
	m_RenderMaterial = NULL;
	m_bRegenerate = false;
}

void CGameUIDynamicTextures::SetImageEntry( const char *pEntryName, ImageAliasData_t &imageData )
{
	m_ImageAliasMap[ pEntryName ] = imageData;
}


//-----------------------------------------------------------------------------
// Associate this image alias name with a .vtf texture.
//-----------------------------------------------------------------------------
void CGameUIDynamicTextures::LoadImageAlias( const char *pAlias, const char *pBaseTextureName )
{
	if ( !pAlias || !*pAlias )
		return;

	if ( !pBaseTextureName || !*pBaseTextureName )
		return;


	CFmtStr texturePath;
	texturePath.sprintf( "materials\\%s.vtf", pBaseTextureName );
	if ( !g_pFullFileSystem->FileExists( texturePath.Access(), IsGameConsole() ? "MOD" : "GAME" ) )
	{
		Warning( "Unable to find game ui dynamic texture \"%s\"\n", texturePath.Access() );
	}

	if ( Q_strcmp( pBaseTextureName, "" ) == 0 )
	{
		ImageAliasData_t *pImageData = GetImageAliasData( "errorImageAlias" );
		ImageAliasData_t imageData;
		imageData.Init();
		imageData.m_XPos = pImageData->m_XPos;
		imageData.m_YPos = pImageData->m_YPos;
		imageData.m_Width = pImageData->m_Width;
		imageData.m_Height = pImageData->m_Height;
		imageData.m_szBaseTextureName = pImageData->m_szBaseTextureName;
		imageData.m_Material = pImageData->m_Material;
		imageData.m_bIsInSheet = pImageData->m_bIsInSheet;
		imageData.m_nNodeIndex = pImageData->m_nNodeIndex;
		SetImageEntry( pAlias, imageData );
		return;
	}


	Msg( "Loading Alias %s Texture %s\n", pAlias, pBaseTextureName );


	CTextureReference pTexture;
	pTexture.Init( pBaseTextureName, TEXTURE_GROUP_OTHER, true );

	ImageAliasData_t *pImageData = GetImageAliasData( pAlias );
	int nodeIndex = -1;

	if ( m_pDynamicTexturePacker )
	{
		if ( !IsErrorImageAliasData( pImageData ) )
		{
			if ( pImageData->m_nNodeIndex != -1 )
			{
				// We already had something packed in there for this alias.
				// Remove the old alias texture
				m_pDynamicTexturePacker->RemoveRect( pImageData->m_nNodeIndex );
			}

			// Set up new imagedata values
			pImageData->m_Width = pTexture->GetActualWidth();
			pImageData->m_Height = pTexture->GetActualHeight();
			pImageData->m_szBaseTextureName = pBaseTextureName;
		}
		else
		{
			ImageAliasData_t imageData;
			imageData.m_Width = pTexture->GetActualWidth();
			imageData.m_Height = pTexture->GetActualHeight();
			imageData.m_szBaseTextureName = pBaseTextureName;
			imageData.m_nRefCount = pImageData->m_nRefCount;
			SetImageEntry( pAlias, imageData );
			pImageData = GetImageAliasData( pAlias );
			Assert( !IsErrorImageAliasData( pImageData ) );
		}

		int nSheetWidth = 0;
		int nSheetHeight = 0;
		GetDynamicSheetSize( nSheetWidth, nSheetHeight );
		Assert( nSheetWidth != 0 );
		Assert( nSheetHeight != 0 );
		// If our texture is huge in some way, don't bother putting it in the packed texture.
		if ( ( pTexture->GetActualWidth() << 1 ) < nSheetWidth || ( pTexture->GetActualHeight() << 1 ) < nSheetHeight ) 
		{
			// Put this texture into the packed render target texture.
			Rect_t texRect;
			texRect.x = 0;
			texRect.y = 0;
			texRect.width = pTexture->GetActualWidth();
			texRect.height = pTexture->GetActualHeight();
			nodeIndex = m_pDynamicTexturePacker->InsertRect( texRect );	
		}
	}

	// For debugging this will make no packing happen. Each texture is one draw call
	//nodeIndex = -1;
	pImageData->m_nNodeIndex = nodeIndex;

	LoadImageAliasTexture( pAlias, pBaseTextureName );
}

//-----------------------------------------------------------------------------
// Release the entry for this alias in the packer.
//-----------------------------------------------------------------------------
void CGameUIDynamicTextures::ReleaseImageAlias( const char *pAlias )
{
	if ( Q_strcmp( pAlias, "_rt_DynamicUI" ) == 0 )
		return;
	if ( Q_strcmp( pAlias, "errorImageAlias" ) == 0 )
		return;
	if ( Q_strcmp( pAlias, "defaultImageAlias" ) == 0 )
		return;

	ImageAliasData_t *pImageData = GetImageAliasData( pAlias );	
	if ( pImageData )
	{
		Assert( pImageData->m_nRefCount > 0 );
		pImageData->m_nRefCount--;
		if ( pImageData->m_nRefCount == 0 )
		{
			if ( pImageData->m_bIsInSheet )
			{
				Msg( "Releasing Alias %s\n", pAlias );
				m_pDynamicTexturePacker->RemoveRect( pImageData->m_nNodeIndex );
			}
		
			pImageData->Init();
		}
	}
}

//-----------------------------------------------------------------------------
// Associate this image alias name with a .vtf texture.
// This fxn loads the texture into the GameControls shader and makes a material 
// for you
//-----------------------------------------------------------------------------
void CGameUIDynamicTextures::LoadImageAliasTexture( const char *pAlias, const char *pBaseTextureName )
{
	if ( !pBaseTextureName || !*pBaseTextureName )
		return;

	CTextureReference pTexture;
	pTexture.Init( pBaseTextureName, TEXTURE_GROUP_OTHER, true );

	ImageAliasData_t *pImageData = GetImageAliasData( pAlias );
	
	// Now set up the material for the new imagedata.
	if ( pImageData->m_nNodeIndex == -1 )// it won't fit in the map, give it its own material, it is its own draw call.
	{
		// Material names must be different
		CUtlVector<char *> words;
		V_SplitString( pImageData->m_szBaseTextureName, "/", words );

		CFmtStr materialName;
		materialName.sprintf( "dynamictx_%s", words[words.Count()-1] );

		KeyValues *pVMTKeyValues = new KeyValues( "GameControls" );
		pVMTKeyValues->SetString( "$basetexture", pImageData->m_szBaseTextureName );
		CMaterialReference material;
		material.Init( materialName, TEXTURE_GROUP_OTHER, pVMTKeyValues );
		material->Refresh();

		pImageData->m_Material = material;
		pImageData->m_bIsInSheet = false;
	}
	else // Do this if it fit in the packed texture.
	{
		if ( !m_RenderMaterial )
			return;

		pImageData->m_Material.Init( GetImageAliasMaterial( "_rt_DynamicUI" ) );
		Assert( pImageData->m_Material );
		pImageData->m_bIsInSheet = true;
		
		const CTexturePacker::TreeEntry_t &newEntry = m_pDynamicTexturePacker->GetEntry( pImageData->m_nNodeIndex );
		Assert( newEntry.rc.width == pImageData->m_Width );
		Assert( newEntry.rc.height == pImageData->m_Height );

		pImageData->m_XPos = newEntry.rc.x;
		pImageData->m_YPos = newEntry.rc.y;

		CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
		pRenderContext->PushRenderTargetAndViewport( m_TexturePage, NULL, pImageData->m_XPos, pImageData->m_YPos, pImageData->m_Width, pImageData->m_Height );	
		pRenderContext->MatrixMode( MATERIAL_PROJECTION );
		pRenderContext->PushMatrix();
		pRenderContext->LoadIdentity();
		pRenderContext->Scale( 1, -1, 1 );
		float flPixelOffsetX = 0.5f;
		float flPixelOffsetY = 0.5f;
		pRenderContext->Ortho( flPixelOffsetX, flPixelOffsetY, pImageData->m_Width + flPixelOffsetX, pImageData->m_Height + flPixelOffsetY, -1.0f, 1.0f );
		//pRenderContext->Ortho( 0, 0, pImageData->m_Width, pImageData->m_Height, -1.0f, 1.0f );

		// Clear to random color. Useful for testing.
		//pRenderContext->ClearColor3ub( rand()%256, rand()%256, rand()%256 );
		pRenderContext->ClearColor4ub( 0, 0, 0, 255 );
		pRenderContext->ClearBuffers( true, false );
	

		// make sure there is no translation and rotation laying around
		pRenderContext->MatrixMode( MATERIAL_MODEL );
		pRenderContext->PushMatrix();
		pRenderContext->LoadIdentity();

		pRenderContext->MatrixMode( MATERIAL_VIEW );
		pRenderContext->PushMatrix();
		pRenderContext->LoadIdentity();
		
		pRenderContext->Bind( m_RenderMaterial, (void *)pImageData->m_szBaseTextureName.Get() );

		int x = 0;
		int y = 0;
		int wide = pImageData->m_Width;
		int tall = pImageData->m_Height;


		unsigned char drawcolor[4];
		drawcolor[0] = 255;
		drawcolor[1] = 255;
		drawcolor[2] = 255;
		drawcolor[3] = 255;

		IMesh *pMesh = pRenderContext->GetDynamicMesh( true );
		if ( pMesh )
		{
			CMeshBuilder meshBuilder;
			meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 );

			meshBuilder.Position3f( x, y, 0 );
			meshBuilder.Color4ubv( drawcolor );
			meshBuilder.TexCoord2f( 0, 0, 0 );
			meshBuilder.AdvanceVertexF<VTX_HAVEPOS | VTX_HAVECOLOR, 1>();

			meshBuilder.Position3f( wide, y, 0 );
			meshBuilder.Color4ubv( drawcolor );
			meshBuilder.TexCoord2f( 0, 1, 0 );
			meshBuilder.AdvanceVertexF<VTX_HAVEPOS | VTX_HAVECOLOR, 1>();

			meshBuilder.Position3f( wide, tall, 0 );
			meshBuilder.Color4ubv( drawcolor );
			meshBuilder.TexCoord2f( 0, 1, 1 );
			meshBuilder.AdvanceVertexF<VTX_HAVEPOS | VTX_HAVECOLOR, 1>();

			meshBuilder.Position3f( x, tall, 0 );
			meshBuilder.Color4ubv( drawcolor );
			meshBuilder.TexCoord2f( 0, 0, 1 );
			meshBuilder.AdvanceVertexF<VTX_HAVEPOS | VTX_HAVECOLOR, 1>();

			meshBuilder.End();
			pMesh->Draw();
		}


		// Restore the matrices
		pRenderContext->MatrixMode( MATERIAL_PROJECTION );
		pRenderContext->PopMatrix();

		pRenderContext->MatrixMode( MATERIAL_MODEL );
		pRenderContext->PopMatrix();

		pRenderContext->MatrixMode( MATERIAL_VIEW );
		pRenderContext->PopMatrix();
		

		pRenderContext->PopRenderTargetAndViewport();
	}		
}

//-----------------------------------------------------------------------------
// Return the material bound to this image alias.
// If the texture was not found you will see the purple and black checkerboard
// error texture.
//-----------------------------------------------------------------------------
IMaterial *CGameUIDynamicTextures::GetImageAliasMaterial( const char *pAlias ) 
{ 
	if ( m_ImageAliasMap.Defined( pAlias ) )
	{
		return m_ImageAliasMap[pAlias].m_Material;
	}
	return m_ImageAliasMap[ "errorImageAlias" ].m_Material;			
}


//-----------------------------------------------------------------------------
// Return the data bound to this image alias.
// If the alias was not found you will see the purple and black checkerboard
// error texture.
//-----------------------------------------------------------------------------
ImageAliasData_t *CGameUIDynamicTextures::GetImageAliasData( const char *pAlias )
{ 
	if ( m_ImageAliasMap.Defined( pAlias ) )
	{
		return &m_ImageAliasMap[pAlias];
	}
	
	return &m_ImageAliasMap[ "errorImageAlias" ];
}


//-----------------------------------------------------------------------------
// Return true if this data is the error alias
//-----------------------------------------------------------------------------
bool CGameUIDynamicTextures::IsErrorImageAliasData( ImageAliasData_t *pData ) 
{
	return ( &m_ImageAliasMap[ "errorImageAlias" ] ) == pData;
}


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CGameUIDynamicTextures::GetDynamicSheetSize( int &nWidth, int &nHeight )
{
	nWidth = m_TexturePage->GetActualWidth();
	nHeight = m_TexturePage->GetActualHeight();
}


//-----------------------------------------------------------------------------
// Draw the dynamic texture associated with this alias at x, y
// It is drawn full size. This fxn is modeled off of CGameUISystemSurface::DrawFontTexture()
//-----------------------------------------------------------------------------
void CGameUIDynamicTextures::DrawDynamicTexture( const char *pAlias, int x, int y )
{
	if ( !pAlias || !*pAlias )
		return;


	ImageAliasData_t *pImageData = GetImageAliasData( pAlias );

	int wide = 0;
	int tall = 0;

	if ( !pImageData->m_bIsInSheet )
	{
		wide = pImageData->m_Width;
		tall = pImageData->m_Height;
	}
	else
	{	
		GetDynamicSheetSize( wide, tall );
	}
	
	color32 drawcolor;
	drawcolor.r = 255;
	drawcolor.g = 255;
	drawcolor.b = 255;
	drawcolor.a = 255;

	CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
	IMesh *pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, pImageData->m_Material );
	if ( !pMesh )
		return;

	CMeshBuilder meshBuilder;
	meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 );

	meshBuilder.Position3f( x, y, 0 );
	meshBuilder.Color4ub( drawcolor.r, drawcolor.g, drawcolor.b, drawcolor.a );
	meshBuilder.TexCoord3f( 0, 0, 0, 0 );
	meshBuilder.AdvanceVertex();

	meshBuilder.Position3f( x + wide, y, 0 );
	meshBuilder.Color4ub( drawcolor.r, drawcolor.g, drawcolor.b, drawcolor.a );
	meshBuilder.TexCoord3f( 0, 1, 0, 0 );
	meshBuilder.AdvanceVertex();

	meshBuilder.Position3f( x + wide, y + tall, 0 );
	meshBuilder.Color4ub( drawcolor.r, drawcolor.g, drawcolor.b, drawcolor.a );
	meshBuilder.TexCoord3f( 0, 1, 1, 0 );
	meshBuilder.AdvanceVertex();

	meshBuilder.Position3f( x, y + tall, 0 );
	meshBuilder.Color4ub( drawcolor.r, drawcolor.g, drawcolor.b, drawcolor.a );
	meshBuilder.TexCoord3f( 0, 0, 1, 0 );
	meshBuilder.AdvanceVertex();

	meshBuilder.End();
	pMesh->Draw();			
}

//-----------------------------------------------------------------------------
// Reload all textures into the rendertarget.
//-----------------------------------------------------------------------------
void CGameUIDynamicTextures::OnRestore( int nChangeFlags )
{
	m_bRegenerate = true;
}

//-----------------------------------------------------------------------------
// Reload all textures into the rendertarget.
//-----------------------------------------------------------------------------
void CGameUIDynamicTextures::RegenerateTexture( int nChangeFlags )
{
	if ( !m_bRegenerate )
		return;

	int numEntries = m_ImageAliasMap.GetNumStrings();
	for ( int i = 0; i < numEntries; ++i )
	{
		const char *pAlias = m_ImageAliasMap.String( i );
		ImageAliasData_t *pImageData = GetImageAliasData( pAlias );
		if ( IsErrorImageAliasData( pImageData )  )
			continue;
		if ( Q_strcmp( pAlias, "_rt_DynamicUI") == 0 )
			continue;

		if ( !pImageData->m_szBaseTextureName || !*pImageData->m_szBaseTextureName )
			continue;

		ITexture *pTexture = g_pMaterialSystem->FindTexture( pImageData->m_szBaseTextureName.Get(), TEXTURE_GROUP_OTHER, true );
		Assert( pTexture );
		pTexture->Download();

		LoadImageAliasTexture( pAlias, pImageData->m_szBaseTextureName );
	}
}