//========= Copyright Valve Corporation, All rights reserved. ============//

#include <windows.h>
#include "materialsystem/imaterialproxyfactory.h"
#include "materialsystem/imaterialvar.h"
#include "materialsystem/imaterialproxy.h"

class C_BaseEntity;

// Copied and bastardized a few material proxy classes from the TF client dll. The purpose here is
// to make TF materials for paintable items show the default paint color (using the SelectFirstIfNotZero proxy)
// and to completely hide the burn detail texture (fake BurnLevel proxy).
// Implemented a lame material proxy factory that only knows about these two proxies.

//-----------------------------------------------------------------------------
// Helper class to deal with floating point inputs
//-----------------------------------------------------------------------------
class CFloatInput
{
public:
	bool  Init( IMaterial *pMaterial, KeyValues *pKeyValues, const char *pKeyName, float flDefault = 0.0f );
	float GetFloat() const;

private:
	float m_flValue;
	IMaterialVar *m_pFloatVar;
	int	m_FloatVecComp;
};

bool CFloatInput::Init( IMaterial *pMaterial, KeyValues *pKeyValues, const char *pKeyName, float flDefault )
{
	m_pFloatVar = NULL;
	KeyValues *pSection = pKeyValues->FindKey( pKeyName );
	if (pSection)
	{
		if (pSection->GetDataType() == KeyValues::TYPE_STRING)
		{
			const char *pVarName = pSection->GetString();

			// Look for numbers...
			float flValue;
			int nCount = sscanf( pVarName, "%f", &flValue );
			if (nCount == 1)
			{
				m_flValue = flValue;
				return true;
			}

			// Look for array specification...
			char pTemp[256];
			if (strchr(pVarName, '['))
			{		 
				// strip off the array...
				Q_strncpy( pTemp, pVarName, 256 );
				char *pArray = strchr( pTemp, '[' );
				*pArray++ = 0;

				char* pIEnd;
				m_FloatVecComp = strtol( pArray, &pIEnd, 10 );

				// Use the version without the array...
				pVarName = pTemp;
			}
			else
			{
				m_FloatVecComp = -1;
			}

			bool bFoundVar;
			m_pFloatVar = pMaterial->FindVar( pVarName, &bFoundVar, true );
			if (!bFoundVar)
				return false;
		}
		else
		{
			m_flValue = pSection->GetFloat();
		}
	}
	else
	{
		m_flValue = flDefault;
	}
	return true;
}

float CFloatInput::GetFloat() const
{
	if (!m_pFloatVar)
		return m_flValue;

	if( m_FloatVecComp < 0 )
		return m_pFloatVar->GetFloatValue();

	int iVecSize = m_pFloatVar->VectorSize();
	if ( m_FloatVecComp >= iVecSize )
		return 0;

	float v[4];
	m_pFloatVar->GetVecValue( v, iVecSize );
	return v[m_FloatVecComp];
}



//-----------------------------------------------------------------------------
//
// Result proxy; a result (with vector friendliness)
//
//-----------------------------------------------------------------------------
class CResultProxy : public IMaterialProxy
{
public:
	CResultProxy();
	virtual ~CResultProxy();
	virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
	virtual void Release( void ) { delete this; }
	virtual IMaterial *GetMaterial();

protected:
	C_BaseEntity *BindArgToEntity( void *pArg );
	void SetFloatResult( float result );

	IMaterialVar* m_pResult;
	int m_ResultVecComp;
};

CResultProxy::CResultProxy() : m_pResult(0)
{
}

CResultProxy::~CResultProxy()
{
}


bool CResultProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
{
	char const* pResult = pKeyValues->GetString( "resultVar" );
	if( !pResult )
		return false;

	// Look for array specification...
	char pTemp[256];
	if (strchr(pResult, '['))
	{		 
		// strip off the array...
		Q_strncpy( pTemp, pResult, 256 );
		char *pArray = strchr( pTemp, '[' );
		*pArray++ = 0;

		char* pIEnd;
		m_ResultVecComp = strtol( pArray, &pIEnd, 10 );

		// Use the version without the array...
		pResult = pTemp;
	}
	else
	{
		m_ResultVecComp = -1;
	}

	bool foundVar;
	m_pResult = pMaterial->FindVar( pResult, &foundVar, true );
	if( !foundVar )
		return false;

	return true;
}


//-----------------------------------------------------------------------------
// A little code to allow us to set single components of vectors
//-----------------------------------------------------------------------------
void CResultProxy::SetFloatResult( float result )
{
	if (m_pResult->GetType() == MATERIAL_VAR_TYPE_VECTOR)
	{		
		if ( m_ResultVecComp >= 0 )
		{
			m_pResult->SetVecComponentValue( result, m_ResultVecComp );
		}
		else
		{
			float v[4];
			int vecSize = m_pResult->VectorSize();

			for (int i = 0; i < vecSize; ++i)
				v[i] = result;

			m_pResult->SetVecValue( v, vecSize );
		}		
	}
	else
	{
		m_pResult->SetFloatValue( result );
	}
}

C_BaseEntity *CResultProxy::BindArgToEntity( void *pArg )
{
	return NULL;
	/*
	IClientRenderable *pRend = (IClientRenderable *)pArg;
	return pRend->GetIClientUnknown()->GetBaseEntity();
	*/
}

IMaterial *CResultProxy::GetMaterial()
{
	return m_pResult->GetOwningMaterial();
}


//-----------------------------------------------------------------------------
//
// Base functional proxy; two sources (one is optional) and a result
//
//-----------------------------------------------------------------------------
class CFunctionProxy : public CResultProxy
{
public:
	CFunctionProxy();
	virtual ~CFunctionProxy();
	virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );

protected:
	void ComputeResultType( MaterialVarType_t& resultType, int& vecSize );

	IMaterialVar* m_pSrc1;
	IMaterialVar* m_pSrc2;
};

CFunctionProxy::CFunctionProxy() : m_pSrc1(0), m_pSrc2(0)
{
}

CFunctionProxy::~CFunctionProxy()
{
}


bool CFunctionProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
{
	if (!CResultProxy::Init( pMaterial, pKeyValues ))
		return false;

	char const* pSrcVar1 = pKeyValues->GetString( "srcVar1" );
	if( !pSrcVar1 )
		return false;

	bool foundVar;
	m_pSrc1 = pMaterial->FindVar( pSrcVar1, &foundVar, true );
	if( !foundVar )
		return false;

	// Source 2 is optional, some math ops may be single-input
	char const* pSrcVar2 = pKeyValues->GetString( "srcVar2" );
	if( pSrcVar2 && (*pSrcVar2) )
	{
		m_pSrc2 = pMaterial->FindVar( pSrcVar2, &foundVar, true );
		if( !foundVar )
			return false;
	}
	else
	{
		m_pSrc2 = 0;
	}

	return true;
}


void CFunctionProxy::ComputeResultType( MaterialVarType_t& resultType, int& vecSize )
{
	// Feh, this is ugly. Basically, don't change the result type
	// unless it's undefined.
	resultType = m_pResult->GetType();
	if (resultType == MATERIAL_VAR_TYPE_VECTOR)
	{
		if (m_ResultVecComp >= 0)
			resultType = MATERIAL_VAR_TYPE_FLOAT;
		vecSize = m_pResult->VectorSize();
	}
	else if (resultType == MATERIAL_VAR_TYPE_UNDEFINED)
	{
		resultType = m_pSrc1->GetType();
		if (resultType == MATERIAL_VAR_TYPE_VECTOR)
		{
			vecSize = m_pSrc1->VectorSize();
		}
		else if ((resultType == MATERIAL_VAR_TYPE_UNDEFINED) && m_pSrc2)
		{
			resultType = m_pSrc2->GetType();
			if (resultType == MATERIAL_VAR_TYPE_VECTOR)
			{
				vecSize = m_pSrc2->VectorSize();
			}
		}
	}
}


class CFakeBurnLevelProxy : public CResultProxy
{
public:
	virtual void OnBind( void *pC_BaseEntity )
	{
		// Slam burn level to 0 to avoid the burn detail texture showing through in modelbrowser.
		Assert( m_pResult );
		if ( m_pResult )
		{
			m_pResult->SetFloatValue( 0.0f );
		}
	}
};


//-----------------------------------------------------------------------------
// Selects the first var value if it's non-zero, otherwise goes with the second
//-----------------------------------------------------------------------------

class CSelectFirstIfNonZeroProxy : public CFunctionProxy
{
public:
	virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
	virtual void OnBind( void *pC_BaseEntity );
};

bool CSelectFirstIfNonZeroProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
{
	// Requires 2 args..
	bool ok = CFunctionProxy::Init( pMaterial, pKeyValues );
	ok = ok && m_pSrc2;
	return ok;
}

void CSelectFirstIfNonZeroProxy::OnBind( void *pC_BaseEntity )
{
	Assert( m_pSrc1 && m_pSrc2 && m_pResult );

	MaterialVarType_t resultType;
	int vecSize;
	ComputeResultType( resultType, vecSize );

	switch( resultType )
	{
	case MATERIAL_VAR_TYPE_VECTOR:
		{
			Vector a, b;
			m_pSrc1->GetVecValue( a.Base(), vecSize ); 
			m_pSrc2->GetVecValue( b.Base(), vecSize ); 

			if ( !a.IsZero() )
			{
				m_pResult->SetVecValue( a.Base(), vecSize );
			}
			else
			{
				m_pResult->SetVecValue( b.Base(), vecSize );
			}
		}
		break;

	case MATERIAL_VAR_TYPE_FLOAT:
		if ( m_pSrc1->GetFloatValue() )
		{
			SetFloatResult( m_pSrc1->GetFloatValue() );
		}
		else
		{
			SetFloatResult( m_pSrc2->GetFloatValue() );
		}
		break;

	case MATERIAL_VAR_TYPE_INT:
		if ( m_pSrc1->GetIntValue() )
		{
			m_pResult->SetFloatValue( m_pSrc1->GetIntValue() );
		}
		else
		{
			m_pResult->SetFloatValue( m_pSrc2->GetIntValue() );
		}
		break;
	}
}


class CModelBrowserMaterialProxyFactory : public IMaterialProxyFactory
{
public:
	virtual IMaterialProxy *CreateProxy( const char *proxyName )
	{
		if ( V_stricmp( proxyName, "SelectFirstIfNonZero" ) == 0 )
		{
			return new CSelectFirstIfNonZeroProxy;
		}
		else if ( V_stricmp( proxyName, "BurnLevel" ) == 0 )
		{
			return new CFakeBurnLevelProxy;
		}
		return NULL;
	}

	virtual void DeleteProxy( IMaterialProxy *pProxy )
	{ 
		if ( pProxy )
		{
			pProxy->Release();
		}
	}
};