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

//#include "cbase.h"

#include "custom_material.h"
#include "composite_texture.h"
#include "materialsystem/base_visuals_data_processor.h"
#include "materialsystem_global.h"
#include "keyvalues.h"
#include "tier0/vprof.h"

#ifndef DEDICATED
#include "filesystem.h"
#endif

//#define DEBUG_CUSTOMMATERIALS

int CCustomMaterial::m_nMaterialCount = 0;

//
// Material applied to the item to give it a custom look
//

CCustomMaterial::CCustomMaterial( KeyValues *pKeyValues )
	: m_bValid( false )
	, m_nModelMaterialIndex( -1 )
	, m_szBaseMaterialName( NULL)
{
	// we need to copy this, because the passed in one was allocated outside materialsystem.dll
	m_pVMTKeyValues = ( pKeyValues != NULL ) ? pKeyValues->MakeCopy() : NULL;
}

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

void CCustomMaterial::AddTexture( ICompositeTexture * pTextureInterface )
{
	CCompositeTexture *pTexture = dynamic_cast< CCompositeTexture * >( pTextureInterface );
	if ( pTexture )
	{
		m_pTextures.AddToTail( pTexture );
		pTexture->AddRef();
	}
}

ICompositeTexture *CCustomMaterial::GetTexture( int nIndex )
{
	if ( nIndex >= 0 && nIndex < m_pTextures.Count() )
	{
		return m_pTextures[ nIndex ];
	}
	return NULL;
}

bool CCustomMaterial::CheckRegenerate( int nSize )
{
	if ( m_pTextures.Count() > 0 )
	{
		return nSize != ( 2048 >> m_pTextures[ 0 ]->Size() );
	}

	return false;
}

void CCustomMaterial::SetBaseMaterialName( const char* szName )
{
	Assert( !m_szBaseMaterialName );
	m_szBaseMaterialName = strdup( szName );
}

void CCustomMaterial::Shutdown()
{
	DestroyProceduralMaterial();

	if ( m_pVMTKeyValues != NULL )
	{
		m_pVMTKeyValues->deleteThis();
		m_pVMTKeyValues = NULL;
	}

	for ( int i = 0; i < m_pTextures.Count(); i++ )
	{
		if ( m_pTextures[ i ] )
		{
			m_pTextures[ i ]->Release();
			m_pTextures[ i ] = NULL;
		}
	}
	if ( m_szBaseMaterialName )
		free( ( void * ) m_szBaseMaterialName );

	m_pTextures.RemoveAll();
}

void CCustomMaterial::Usage( int& nTextures, int& nBackingTextures )
{
	for ( int i = 0; i < m_pTextures.Count(); i++ )
	{
		m_pTextures[ i ]->Usage( nTextures, nBackingTextures );
	}
}

bool CCustomMaterial::TexturesReady() const
{
	if ( m_pTextures.Count() == 0 )
	{
		return false;
	}

	for ( int i = 0; i < m_pTextures.Count(); i++ )
	{
		if ( !m_pTextures[ i ]->IsReady() )
		{
			return false;
		}
	}

	return true;
}

void CCustomMaterial::RegenerateTextures()
{
	for ( int i = 0; i < m_pTextures.Count(); i++ )
	{
		m_pTextures[ i ]->ForceRegenerate();
	}
}

bool CCustomMaterial::ShouldRelease()
{
	return ( ( GetRefCount() == 1 ) && IsValid() );
}

bool CCustomMaterial::Compare( const CUtlVector< SCompositeTextureInfo > &vecTextures )
{
	if ( m_pTextures.Count() == vecTextures.Count() )
	{
		FOR_EACH_VEC( m_pTextures, i )
		{
			if ( !m_pTextures[ i ]->Compare( vecTextures[ i ] ) )
			{
				return false;
			}
		}
		return true;
	}

	return false;
}

void CCustomMaterial::Finalize()
{
	char szUniqueMaterialName[ 256 ];
	V_snprintf( szUniqueMaterialName, sizeof( szUniqueMaterialName ), "cs_custom_material_%i", m_nMaterialCount++ );

	DestroyProceduralMaterial();

	if ( m_pVMTKeyValues != NULL )
	{
		CreateProceduralMaterial( szUniqueMaterialName, m_pVMTKeyValues->MakeCopy() );
	}
	else
	{
		// Create default material key values
		m_pVMTKeyValues = new KeyValues( "VertexLitGeneric" );

		m_pVMTKeyValues->SetInt( "$phongalbedoboost", 45 );
		m_pVMTKeyValues->SetInt( "$phong", 1 );
		m_pVMTKeyValues->SetFloat( "$phongboost", 0.4 );
		m_pVMTKeyValues->SetString( "$phongfresnelranges", "[.8 .8 1]" );
		m_pVMTKeyValues->SetInt( "$basemapalphaphongmask", 1 );
		m_pVMTKeyValues->SetString( "$envmap", "env_cubemap" );
		m_pVMTKeyValues->SetInt( "$envmapfresnel", 1 );
		m_pVMTKeyValues->SetString( "$envmapFresnelMinMaxExp", "[0 5 .4]" );
		m_pVMTKeyValues->SetString( "$envmaptint", "[.02 .02 .02]" );
		m_pVMTKeyValues->SetInt( "$phongalbedotint", 1 );

		CreateProceduralMaterial( szUniqueMaterialName, m_pVMTKeyValues->MakeCopy() );
	}
}

void CCustomMaterial::CreateProceduralMaterial( const char *pMaterialName, KeyValues *pVMTKeyValues )
{
	// Replace parts of existing material key values
	// loop over m_pTextures and set the material params
	for ( int i = 0; i < m_pTextures.Count(); i++ )
	{
		pVMTKeyValues->SetString( g_szMaterialParamNames[ m_pTextures[ i ]->GetMaterialParamNameId() ], m_pTextures[ i ]->GetName() );
	}

	m_Material.Init( pMaterialName, pVMTKeyValues );
	m_Material->Refresh();
}

void CCustomMaterial::DestroyProceduralMaterial()
{
	m_Material.Shutdown( true );
}


//
// global custom material manager
//  the game uses this to make/get a custom material
//

CCustomMaterialManager::CCustomMaterialManager()
{
#ifndef DEDICATED
	m_pCustomMaterials.EnsureCapacity( 128 );
	m_mapVMTKeyValues.SetLessFunc( StringLessThan );
#endif
}

CCustomMaterialManager::~CCustomMaterialManager()
{	
}

// this is called at the end of each frame
bool ProcessDynamicCustomMaterialGenerator()
{
	return MaterialSystem()->GetCustomMaterialManager()->Process();
}

bool CCustomMaterialManager::Init()
{
#ifndef DEDICATED
	MaterialSystem()->AddEndFramePriorToNextContextFunc( ::ProcessDynamicCustomMaterialGenerator );

	KeyValues *pVMTCache = new KeyValues( "VMTCache" );
	if ( pVMTCache->LoadFromFile( g_pFullFileSystem, "resource/vmtcache.txt", "MOD" ) )
	{
		KeyValues *pValue = pVMTCache->GetFirstValue();
		while ( pValue )
		{
			const char *pszVMTToCache = pValue->GetString();
			if ( pszVMTToCache && pszVMTToCache[0] != 0 )
			{
				KeyValues *pVMTKeyValues = new KeyValues( "VertexLitGeneric" );
				bool bVMTExists = pVMTKeyValues->LoadFromFile( g_pFullFileSystem , pszVMTToCache, "MOD" );
				if ( bVMTExists )
				{
					m_mapVMTKeyValues.Insert( pszVMTToCache, pVMTKeyValues );
					DevMsg( "CustomMaterialManager: Cached KeyValues %s.\n", pszVMTToCache );
				}
				else
				{
					DevMsg( "Failed to load VMT: %s\n", pszVMTToCache );
				}
			}
			pValue = pValue->GetNextValue();
		}
	}
#endif
	return true;
}

void CCustomMaterialManager::Shutdown()
{
#ifndef DEDICATED
	MaterialSystem()->RemoveEndFramePriorToNextContextFunc( ::ProcessDynamicCustomMaterialGenerator );
	FOR_EACH_MAP_FAST( m_mapVMTKeyValues, i )
	{
		m_mapVMTKeyValues[ i ]->deleteThis();
	}
	m_mapVMTKeyValues.Purge();
	DestroyMaterials();
#endif
}

bool CCustomMaterialManager::GetVMTKeyValues( const char *pszVMTName, KeyValues **ppVMTKeyValues )
{
	// lookup VMT KeyValues in container
	int nIndex = m_mapVMTKeyValues.Find( pszVMTName );
	if ( nIndex != m_mapVMTKeyValues.InvalidIndex() )
	{
		// need to return a copy here, since we don't want the caller to change our copy.
		*ppVMTKeyValues = m_mapVMTKeyValues[ nIndex ]->MakeCopy();
		return true;
	}
	return false;
}

// handles finalizing materials once all the textures are ready, swapping materials that are pending swap and ready, and cleans up materials that are no longer used
bool CCustomMaterialManager::Process()
{
	//TM_ZONE( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );

	for ( int i = 0; i < m_pCustomMaterials.Count(); ++i )
	{
		if ( m_pCustomMaterials[ i ] &&
			 m_pCustomMaterials[ i ]->TexturesReady() &&
			 !m_pCustomMaterials[ i ]->IsValid() )
		{
			m_pCustomMaterials[ i ]->Finalize();
			m_pCustomMaterials[ i ]->SetValid( true );
#ifdef DEBUG_CUSTOMMATERIALS
			DevMsg( "Finalized custom material: %s \n", m_pCustomMaterials[ i ]->GetMaterial() ? m_pCustomMaterials[ i ]->GetMaterial()->GetName() : "*unknown*" );
#endif
		}
		else if ( !m_pCustomMaterials[ i ]->TexturesReady() &&
				  m_pCustomMaterials[ i ]->IsValid() )
		{
			// this happens when textures regenerate because of mat_picmip changes
			m_pCustomMaterials[ i ]->SetValid( false );
		}
	}

	for ( int i = m_pCustomMaterials.Count() - 1; i >= 0; i-- )
	{
		if ( m_pCustomMaterials[ i ] )
		{
			// clean up materials that are no longer used (we are the only reference)
			if ( m_pCustomMaterials[ i ]->ShouldRelease() )
			{
#ifdef DEBUG_CUSTOMMATERIALS
				DevMsg( "Releasing custom material: %s \n", m_pCustomMaterials[ i ]->GetMaterial()->GetName() );
#endif
				m_pCustomMaterials[ i ]->Release();
				m_pCustomMaterials[ i ] = NULL;
				m_pCustomMaterials.Remove( i );
			}
		}
	}

	return false;
}

ICustomMaterial * CCustomMaterialManager::GetOrCreateCustomMaterial( KeyValues *pKeyValues, const CUtlVector< SCompositeTextureInfo > &vecTextureInfos, bool bIgnorePicMip /*= false */ )
{
#if defined( DEDICATED ) || defined( DISABLE_CUSTOM_MATERIAL_GENERATION )
	return NULL;
#endif
	TM_MESSAGE( TELEMETRY_LEVEL0, TMMF_ICON_NOTE | TMMF_SEVERITY_WARNING, "%s %d", __FUNCTION__, vecTextureInfos[0].m_size );

	for ( int i = 0; i < m_pCustomMaterials.Count(); ++i )
	{
		CCustomMaterial *pMaterial = m_pCustomMaterials[ i ];
		if ( pMaterial && pMaterial->Compare( vecTextureInfos ) )
		{
			return pMaterial;
		}
	}

	CCustomMaterial *pMaterial = new CCustomMaterial( pKeyValues );
	pMaterial->SetValid( false );
	pMaterial->SetBaseMaterialName( vecTextureInfos[ 0 ].m_pVisualsDataProcessor->GetOriginalMaterialBaseName() );

	FOR_EACH_VEC( vecTextureInfos, i )
	{
		ICompositeTexture *pTexture = g_pMaterialSystem->GetCompositeTextureGenerator()->GetCompositeTexture( vecTextureInfos[ i ] );
		if ( pTexture )
		{
			pMaterial->AddTexture( pTexture );
		}
		else
		{
			AssertMsg( pTexture != NULL, "Unable to get/create composite texture for custom material!" );
			pMaterial->Release();
			return NULL;
		}
	}

	m_pCustomMaterials.AddToTail( pMaterial );

#ifdef DEBUG_CUSTOMMATERIALS
	DevMsg( "Created custom material for: %s \n", pVisualsDataProcessor->GetOriginalMaterialName() );
#endif

	// The material may not be complete yet, but it will be completed over the next few frames via Process()
	return pMaterial;
}

void CCustomMaterialManager::ReloadAllMaterials( const CCommand &args )
{
	for ( int i = 0; i < m_pCustomMaterials.Count(); ++i )
	{
		CCustomMaterial *pMaterial = m_pCustomMaterials[ i ];
		if ( pMaterial )
		{
			pMaterial->RegenerateTextures();
		}
	}
}

void CCustomMaterialManager::ReloadVmtCache( const CCommand &args )
{
}

int CCustomMaterialManager::DebugGetNumActiveCustomMaterials( )
{
	int nActive = 0;

	for ( int i = 0; i < m_pCustomMaterials.Count(); ++i )
	{
		CCustomMaterial *pMaterial = m_pCustomMaterials[ i ];

		if ( pMaterial && pMaterial->IsValid() )
		{
			nActive++;
		}
	}
	return nActive;
}

void CCustomMaterialManager::Usage( const CCommand &args )
{
	int nTextures = 0;
	int nBackingTextures = 0;
	int nActive = 0;

	for ( int i = 0; i < m_pCustomMaterials.Count(); ++i )
	{
		CCustomMaterial *pMaterial = m_pCustomMaterials[ i ];

		if ( pMaterial && pMaterial->IsValid() )
		{
			int nBackingTexture = 0;
			pMaterial->Usage( nTextures, nBackingTexture );
			Msg( "%2d. %s, %d \n", i, pMaterial->GetMaterial() ? pMaterial->GetMaterial()->GetName() : "*pending*", nBackingTexture );
			nBackingTextures += nBackingTexture;
			nActive++;
		}
	}

	Msg( "Custom Weapon Material Usage: Total: %d Active: %d  Textures: %d  BackingTextures: %d \n", m_pCustomMaterials.Count(), nActive, nTextures, nBackingTextures );
}

void CCustomMaterialManager::DestroyMaterials( void )
{
	for ( int i = 0; i < m_pCustomMaterials.Count(); ++i )
	{
		if ( m_pCustomMaterials[ i ] )
		{
			m_pCustomMaterials[ i ]->Release();
			m_pCustomMaterials[ i ] = NULL;
		}
	}
	m_pCustomMaterials.RemoveAll();
}