//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//

#include "vmtdoc.h"
#include "tier1/KeyValues.h"
#include "tier1/utlbuffer.h"
#include "datamodel/dmelement.h"
#include "vmttool.h"
#include "materialsystem/imaterialsystem.h"
#include "materialsystem/ishader.h"
#include "toolutils/enginetools_int.h"
#include "filesystem.h"


//-----------------------------------------------------------------------------
// Standard properties
//-----------------------------------------------------------------------------
struct StandardParam_t
{
	const char *m_pParamName;
	ShaderParamType_t m_ParamType;
	const char *m_pDefaultValue;
	const char *m_pWidgetType;
	const char *m_pTextType;
};

// NOTE: All entries in here must have all-lowercase param names!
static StandardParam_t g_pStandardParams[] = 
{
	{ "$surfaceprop", SHADER_PARAM_TYPE_STRING, "default", "surfacepropertypicker", "surfacePropertyName" },
	{ "%detailtype", SHADER_PARAM_TYPE_STRING, "", "detailtypepicker", "detailTypeName" },
	{ "%compilesky", SHADER_PARAM_TYPE_BOOL, "0", NULL, NULL },
	{ "%compilehint", SHADER_PARAM_TYPE_BOOL, "0", NULL, NULL },
	{ "%compileskip", SHADER_PARAM_TYPE_BOOL, "0", NULL, NULL },
	{ "%compileorigin", SHADER_PARAM_TYPE_BOOL, "0", NULL, NULL },
	{ "%compileclip", SHADER_PARAM_TYPE_BOOL, "0", NULL, NULL },
	{ "%playerclip", SHADER_PARAM_TYPE_BOOL, "0", NULL, NULL },
	{ "%compilenpcclip", SHADER_PARAM_TYPE_BOOL, "0", NULL, NULL },
	{ "%compilenochop", SHADER_PARAM_TYPE_BOOL, "0", NULL, NULL },
	{ "%compiletrigger", SHADER_PARAM_TYPE_BOOL, "0", NULL, NULL },
	{ "%compilenolight", SHADER_PARAM_TYPE_BOOL, "0", NULL, NULL },
	{ "%compileplayercontrolclip", SHADER_PARAM_TYPE_BOOL, "0", NULL, NULL },
	{ "%compileladder", SHADER_PARAM_TYPE_BOOL, "0", NULL, NULL },
	{ "%compilewet", SHADER_PARAM_TYPE_BOOL, "0", NULL, NULL },
	{ "%compilenodraw", SHADER_PARAM_TYPE_BOOL, "0", NULL, NULL },
	{ "%compileinvisible", SHADER_PARAM_TYPE_BOOL, "0", NULL, NULL },
	{ "%compilenonsolid", SHADER_PARAM_TYPE_BOOL, "0", NULL, NULL },
	{ "%compiledetail", SHADER_PARAM_TYPE_BOOL, "0", NULL, NULL },
	{ "%compilewater", SHADER_PARAM_TYPE_BOOL, "0", NULL, NULL },
	{ "%compileslime", SHADER_PARAM_TYPE_BOOL, "0", NULL, NULL },
	{ NULL, SHADER_PARAM_TYPE_BOOL, NULL, NULL, NULL }
};


//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
CVMTDoc::CVMTDoc( IVMTDocCallback *pCallback ) : m_pCallback( pCallback )
{
	m_hRoot = NULL;
	m_pFileName[0] = 0;
	m_bDirty = false;
	m_pCurrentIShader = NULL;

	KeyValues *pKeyValues = new KeyValues( "Wireframe" );
	m_pScratchMaterial.Init( "VMT Preview", pKeyValues );
	g_pDataModel->InstallNotificationCallback( this );
}

CVMTDoc::~CVMTDoc()
{
	if ( m_hRoot.Get() )
	{
		RemoveAllShaderParams( m_hRoot );
	}
	g_pDataModel->RemoveNotificationCallback( this );
}


//-----------------------------------------------------------------------------
// Inherited from INotifyUI
//-----------------------------------------------------------------------------
void CVMTDoc::NotifyDataChanged( const char *pReason, int nNotifySource, int nNotifyFlags )
{
	OnDataChanged( pReason, nNotifySource, nNotifyFlags );
}

	
//-----------------------------------------------------------------------------
// Gets the file name
//-----------------------------------------------------------------------------
const char *CVMTDoc::GetFileName()
{
	return m_pFileName;
}

void CVMTDoc::SetFileName( const char *pFileName )
{
	Q_strncpy( m_pFileName, pFileName, sizeof( m_pFileName ) );
	Q_FixSlashes( m_pFileName );
	SetDirty( true );
}


//-----------------------------------------------------------------------------
// Dirty bits
//-----------------------------------------------------------------------------
void CVMTDoc::SetDirty( bool bDirty )
{
	m_bDirty = bDirty;
}

bool CVMTDoc::IsDirty() const
{
	return m_bDirty;
}


//-----------------------------------------------------------------------------
// Creates the root element
//-----------------------------------------------------------------------------
bool CVMTDoc::CreateRootElement()
{
	Assert( !m_hRoot.Get() );

	DmFileId_t fileid = g_pDataModel->FindOrCreateFileId( GetFileName() );

	// Create the main element
	m_hRoot = g_pDataModel->CreateElement( "DmElement", GetFileName(), fileid );
	if ( m_hRoot == DMELEMENT_HANDLE_INVALID )
		return false;

	g_pDataModel->SetFileRoot( fileid, m_hRoot );

	// Each VMT list needs to have an editortype associated with it so it displays nicely in editors
	m_hRoot->SetValue( "editorType", "vmt" );
	m_hRoot->AddAttribute( "proxies", AT_ELEMENT_ARRAY );
	m_hRoot->AddAttribute( "fallbacks", AT_ELEMENT_ARRAY );

	m_pCallback->RemoveAllToolParameters();

	// Add standard parameters
	for ( int i = 0; g_pStandardParams[i].m_pParamName; ++i )
	{
		AddNewShaderParam( m_hRoot, g_pStandardParams[i].m_pParamName, g_pStandardParams[i].m_ParamType, 
			g_pStandardParams[i].m_pDefaultValue );

		if ( g_pStandardParams[i].m_pParamName[0] == '%' )
		{
			m_pCallback->AddToolParameter( g_pStandardParams[i].m_pParamName, g_pStandardParams[i].m_pWidgetType, g_pStandardParams[i].m_pTextType );
		}
		else if ( g_pStandardParams[i].m_pWidgetType || g_pStandardParams[i].m_pTextType )
		{
			m_pCallback->RemoveShaderParameter( g_pStandardParams[i].m_pParamName ); 
			m_pCallback->AddShaderParameter( g_pStandardParams[i].m_pParamName, g_pStandardParams[i].m_pWidgetType, g_pStandardParams[i].m_pTextType );
		}
	}

	return true;
}


//-----------------------------------------------------------------------------
// Creates a new VMT
//-----------------------------------------------------------------------------
void CVMTDoc::CreateNew()
{
	Assert( !m_hRoot.Get() );

	// This is not undoable
	CAppDisableUndoScopeGuard guard( "CVMTDoc::CreateNew", NOTIFY_CHANGE_OTHER );

	Q_strncpy( m_pFileName, "untitled", sizeof( m_pFileName ) );

	// Create the main element
	if ( !CreateRootElement() )
		return;

	m_pPreviewMaterial.Init( m_pScratchMaterial );

	SetShader( "wireframe" );
	SetDirty( false );
}


//-----------------------------------------------------------------------------
// Copies VMT parameters into the root
//-----------------------------------------------------------------------------
void CVMTDoc::CopyParamsFromVMT( CDmElement *pVMT )
{
	// First, set the shader parameters
	SetShader( pVMT->GetValueString( "shader" ) );

	// Now, copy the shader parameters over
	CDmAttribute* pSrc;
	CDmAttribute* pDst;
	for ( pSrc = pVMT->FirstAttribute(); pSrc; pSrc = pSrc->NextAttribute() )
	{
		// Only copy shader parameters
		if ( !IsShaderParam( pSrc ) )
			continue;

		// Adds the attribute if it doesn't exist
		const char *pSrcName = pSrc->GetName();
		if ( !m_hRoot->HasAttribute( pSrcName ) )
		{
			m_hRoot->AddAttribute( pSrcName, pSrc->GetType() );
		}
		pDst = m_hRoot->GetAttribute( pSrcName );
		pDst->AddFlag( FATTRIB_USERDEFINED );

		DmAttributeType_t srcType = pSrc->GetType();
		DmAttributeType_t dstType = pDst->GetType();
		if ( dstType == srcType )
		{
			pDst->SetValue( pSrc );
			continue;
		}

		// Certain type conversions are allowed
		switch( dstType )
		{
		case AT_BOOL:
			if ( srcType == AT_INT )
			{
				pDst->SetValue( pSrc );
			}
			break;

		case AT_INT:
			if ( srcType == AT_BOOL )
			{
				pDst->SetValue( pSrc );
			}
			break;

		case AT_COLOR:
			if ( srcType == AT_VECTOR3 )
			{
				Color c;
				int r, g, b;
				Vector v = pSrc->GetValue<Vector>( );
				v *= 255.0f;
				r = clamp( v[0], 0, 255 );
				g = clamp( v[1], 0, 255 );
				b = clamp( v[2], 0, 255 );
				c.SetColor( r, g, b, 255 );
				pDst->SetValue( c );
			}
			break;
		}
	}

	// Any shader parameter that isn't in the VMT make undefined
	for ( pDst = m_hRoot->FirstAttribute(); pDst; pDst = pDst->NextAttribute() )
	{
		if ( !IsShaderParam( pDst ) )
			continue;

		if ( !pVMT->HasAttribute( pDst->GetName() ) )
		{
			// Special hack for alpha + colors
			if ( !Q_stricmp( pDst->GetName(), "$alpha" ) )
			{
				pDst->SetValue( 1.0f );
			}
			else if ( pDst->GetType() == AT_COLOR )
			{
				Color c( 255, 255, 255, 255 );
				pDst->SetValue( c );
			}
			else
			{
				pDst->SetToDefaultValue();
			}
		}
	}
}


//-----------------------------------------------------------------------------
// Hooks the preview to an existing material, if there is one
//-----------------------------------------------------------------------------
void CVMTDoc::SetupPreviewMaterial( )
{
	// Extract a material name from the material
	char pLocalName[MAX_PATH];

	// relative paths can be passed in for in-game material picking
	if ( !g_pFileSystem->FullPathToRelativePath( m_pFileName, pLocalName, sizeof(pLocalName) ) )
	{
		Q_strcpy( pLocalName, m_pFileName );
	}

	if ( Q_strnicmp( pLocalName, "materials", 9 ) )
		goto noMaterialConnection;

	// Skip the '/' also
	char pMaterialName[MAX_PATH];
	Q_StripExtension( pLocalName + 10, pMaterialName, sizeof(pMaterialName) );
	IMaterial *pMaterial = g_pMaterialSystem->FindMaterial( pMaterialName, "Editable material", false );
	if ( !pMaterial || pMaterial->IsErrorMaterial() )
		goto noMaterialConnection;

	m_pPreviewMaterial.Init( pMaterial );
	return;

noMaterialConnection:
	m_pPreviewMaterial.Init( m_pScratchMaterial );
}


//-----------------------------------------------------------------------------
// Saves/loads from file
//-----------------------------------------------------------------------------
bool CVMTDoc::LoadFromFile( const char *pFileName )
{
	Assert( !m_hRoot.Get() );

	SetDirty( false );

	Q_strncpy( m_pFileName, pFileName, sizeof( m_pFileName ) );
	if ( !m_pFileName[0] )
		return false;

	// This is not undoable
	CAppDisableUndoScopeGuard guard( "CVMTDoc::LoadFromFile", NOTIFY_CHANGE_OTHER );

	// Create the main element
	if ( !CreateRootElement() )
		return false;

	// change the filename of all the elements under the root, so we can unload the imported elements later
	DmFileId_t rootFileId = g_pDataModel->GetFileId( m_pFileName );
	g_pDataModel->SetFileName( rootFileId, "<temp>" );

	// This will allow us to edit in context!
	SetupPreviewMaterial( );

	CDmElement *pIVMT = NULL;
	g_pDataModel->RestoreFromFile( m_pFileName, NULL, "vmt", &pIVMT );
	CDmElement *pVMT = CastElement< CDmElement >( pIVMT );
	if ( !pVMT )
		return false;

	// FIXME: This is necessary so that all shader parameters appear in
	// the same order, with the same type, as what you'd get using File->New.
	// If we added a dependency to the material system into dmserializers,
	// we could avoid this work here (I think!).
	CopyParamsFromVMT( pVMT );

	// unload the imported elements and change the root's filename back
	DmFileId_t vmtFileId = g_pDataModel->GetFileId( m_pFileName );
	g_pDataModel->RemoveFileId( vmtFileId );
	g_pDataModel->SetFileName( rootFileId, m_pFileName );

	SetDirty( false );
	return true;
}

//-----------------------------------------------------------------------------
// Prior to saving to disk, extract all shader parameters which == the default
//-----------------------------------------------------------------------------
CDmElement* CVMTDoc::ExtractDefaultParameters( )
{					   
	CDmElement *pMaterial = m_hRoot->Copy( );

	CDmAttribute* pAttribute = pMaterial->FirstAttribute();
	CDmAttribute* pNextAttribute = NULL;
	for ( ; pAttribute; pAttribute = pNextAttribute )
	{
		pNextAttribute = pAttribute->NextAttribute();

		const char *pShaderParam = pAttribute->GetName();

		// Check for standard params
		int i;
		for ( i = 0; g_pStandardParams[i].m_pParamName != NULL; ++i )
		{
			if ( !Q_stricmp( g_pStandardParams[i].m_pParamName, pShaderParam ) )
			{
				char temp[512];
				CUtlBuffer buf( temp, sizeof(temp), CUtlBuffer::TEXT_BUFFER | CUtlBuffer::EXTERNAL_GROWABLE );
				pAttribute->Serialize( buf );

				if ( !Q_stricmp( (char*)buf.Base(), g_pStandardParams[i].m_pDefaultValue ) )
				{
					// Buffers match! Therefore it's still using the default parameter
					pMaterial->RemoveAttributeByPtr( pAttribute );
				}
				break;
			}
		}

		// Standard attribute found, continue
		if ( g_pStandardParams[i].m_pParamName )
			continue;

		// Only remove shader parameters
		if ( !IsShaderParam( pAttribute ) )
			continue;

		// Remove flags whose value is 0
		int nCount = g_pMaterialSystem->ShaderFlagCount();
		for ( i = 0; i < nCount; ++i )
		{
			const char *pFlagName = g_pMaterialSystem->ShaderFlagName( i );
			if ( !Q_stricmp( pShaderParam, pFlagName ) )
				break;
		}

		// It's a flag! Remove the attribute if its value is 0
		if ( i != nCount )
		{
			if ( pAttribute->GetValue<bool>( ) == 0 )
			{
				pMaterial->RemoveAttributeByPtr( pAttribute );
			}
			continue;
		}

		// FIXME: We can't do this.. the defaults in the strings need to be changed to 
		// make it so they actually match the true defaults
		continue;

		// Remove parameters which match the default value
		nCount = m_pCurrentIShader->GetNumParams();
		for ( i = 0; i < nCount; ++i )
		{
			// FIXME: Check type matches
			if ( Q_stricmp( pShaderParam, m_pCurrentIShader->GetParamName( i ) ) )
				continue;

			// NOTE: This isn't particularly efficient. Too bad!
			// It's hard to do efficiently owing to all the import conversion
			char temp[512];
			char temp2[512];
			CUtlBuffer buf( temp, sizeof(temp), CUtlBuffer::TEXT_BUFFER | CUtlBuffer::EXTERNAL_GROWABLE );
			CUtlBuffer buf2( temp2, sizeof(temp2), CUtlBuffer::TEXT_BUFFER | CUtlBuffer::EXTERNAL_GROWABLE );
			pAttribute->Serialize( buf );
			SetAttributeValueFromDefault( pMaterial, pAttribute, m_pCurrentIShader->GetParamDefault( i ) );
			pAttribute->Serialize( buf2 );

			if ( ( buf.TellMaxPut() == buf2.TellMaxPut() ) && !memcmp( buf.Base(), buf2.Base(), buf.TellMaxPut() ) )
			{
				// Buffers match! Therefore it's still using the default parameter
				pMaterial->RemoveAttributeByPtr( pAttribute );
			}
			else
			{
				// Restore the actual value
				pAttribute->Unserialize( buf );
			}
			break;
		}
	}

	return pMaterial;
}


//-----------------------------------------------------------------------------
// Saves to disk
//-----------------------------------------------------------------------------
bool CVMTDoc::SaveToFile( )
{
	if ( m_hRoot.Get() && m_pFileName && m_pFileName[0] )
	{
		CDisableUndoScopeGuard guard;
		CDmElement *pSaveRoot = ExtractDefaultParameters();
		bool bOk = g_pDataModel->SaveToFile( m_pFileName, NULL, "keyvalues", "vmt", pSaveRoot );
		DestroyElement( pSaveRoot, TD_DEEP );
		if ( !bOk )
			return false;
	}

	SetDirty( false );
	return true;
}


//-----------------------------------------------------------------------------
// Finds a shader
//-----------------------------------------------------------------------------
IShader *CVMTDoc::FindShader( const char *pShaderName )
{
	int nCount = g_pMaterialSystem->ShaderCount();
	IShader **ppShaderList = (IShader**)_alloca( nCount * sizeof(IShader*) );
	g_pMaterialSystem->GetShaders( 0, nCount, ppShaderList );
	for ( int i = 0; i < nCount; ++i )
	{
		if ( !Q_stricmp( pShaderName, ppShaderList[i]->GetName() ) )
			return ppShaderList[i];
	}
	return NULL;
}


//-----------------------------------------------------------------------------
// Is this attribute a shader parameter?
//-----------------------------------------------------------------------------
bool CVMTDoc::IsShaderParam( CDmAttribute* pAttribute )
{
	const char *pName = pAttribute->GetName();

	// Shader params start with a $ or %
	if ( pName[0] != '$' && pName[0] != '%' )
		return false;

	// Don't remove name, type, or id
	if ( pAttribute->IsFlagSet( FATTRIB_STANDARD ) )
		return false;

	// All shader params have USERDEFINED set
	if ( !pAttribute->IsFlagSet( FATTRIB_USERDEFINED ) )
		return false;

	// Don't remove arrays... those aren't shader parameters
	if ( pAttribute->GetType() == AT_ELEMENT_ARRAY )
		return false;

	// Standard params aren't counted here
	for ( int i = 0; g_pStandardParams[i].m_pParamName; ++i )
	{
		if ( !Q_stricmp( g_pStandardParams[i].m_pParamName, pName ) )
			return false;
	}

	return true;
}


//-----------------------------------------------------------------------------
// Remove all shader parameters 
//-----------------------------------------------------------------------------
void CVMTDoc::RemoveAllShaderParams( CDmElement *pMaterial )
{
	CDmAttribute* pAttribute;
	CDmAttribute* pNextAttribute = NULL;
	for ( pAttribute = pMaterial->FirstAttribute(); pAttribute; pAttribute = pNextAttribute )
	{
		pNextAttribute = pAttribute->NextAttribute();

		// Only remove shader parameters
		if ( !IsShaderParam( pAttribute ) )
			continue;

		m_pCallback->RemoveShaderParameter( pAttribute->GetName() );
		pMaterial->RemoveAttributeByPtr( pAttribute );
	}
}
	

//-----------------------------------------------------------------------------
// Remove all shader parameters that don't exist in the new shader
//-----------------------------------------------------------------------------
void CVMTDoc::RemoveUnusedShaderParams( CDmElement *pMaterial, IShader *pShader, IShader *pOldShader )
{
	CDmAttribute* pAttribute = pMaterial->FirstAttribute();
	CDmAttribute* pNextAttribute = NULL;
	for ( ; pAttribute; pAttribute = pNextAttribute )
	{
		pNextAttribute = pAttribute->NextAttribute();

		// Only remove shader parameters
		if ( !IsShaderParam( pAttribute ) )
			continue;

		// Don't remove flags
		int nCount = g_pMaterialSystem->ShaderFlagCount();
		int i;
		for ( i = 0; i < nCount; ++i )
		{
			const char *pFlagName = g_pMaterialSystem->ShaderFlagName( i );
			if ( !Q_stricmp( pAttribute->GetName(), pFlagName ) )
				break;
		}

		if ( i != nCount )
			continue;

		const char *pShaderParam = pAttribute->GetName();

		// Remove parameters we've currently got but which don't exist in the new shader
		nCount = pShader->GetNumParams();
		for ( i = 0; i < nCount; ++i )
		{
			// FIXME: Check type matches
			if ( !Q_stricmp( pShaderParam, pShader->GetParamName( i ) ) )
				break;
		}

		// No match? Remove it!
		if ( i == nCount )
		{
			m_pCallback->RemoveShaderParameter( pAttribute->GetName() );
			pMaterial->RemoveAttributeByPtr( pAttribute );
			continue;
		}

		// Remove parameters from the old shader which match the default value
		// This will make the default values update to the new shader's defaults
		if ( pOldShader )
		{
			nCount = pOldShader->GetNumParams();
			for ( i = 0; i < nCount; ++i )
			{
				// FIXME: Check type matches
				if ( Q_stricmp( pShaderParam, pOldShader->GetParamName( i ) ) )
					continue;

				// NOTE: This isn't particularly efficient. Too bad!
				// It's hard to do efficiently owing to all the import conversion
				char temp1[512];
				char temp2[512];
				CUtlBuffer buf1( temp1, sizeof(temp1), CUtlBuffer::TEXT_BUFFER | CUtlBuffer::EXTERNAL_GROWABLE );
				CUtlBuffer buf2( temp2, sizeof(temp2), CUtlBuffer::TEXT_BUFFER | CUtlBuffer::EXTERNAL_GROWABLE );
				pAttribute->Serialize( buf1 );
				SetAttributeValueFromDefault( pMaterial, pAttribute, pOldShader->GetParamDefault( i ) );
				pAttribute->Serialize( buf2 );

				if ( ( buf1.TellMaxPut() == buf2.TellMaxPut() ) && !memcmp( buf1.Base(), buf2.Base(), buf1.TellMaxPut() ) )
				{
					// Buffers match! Therefore it's still using the default parameter
					m_pCallback->RemoveShaderParameter( pAttribute->GetName() );
					pMaterial->RemoveAttributeByPtr( pAttribute );
				}
				else
				{
					pAttribute->Unserialize( buf1 );
				}
				break;
			}
		}
	}
}


//-----------------------------------------------------------------------------
// Add attribute for shader parameter
//-----------------------------------------------------------------------------
CDmAttribute* CVMTDoc::AddAttributeForShaderParameter( CDmElement *pMaterial, const char *pParamName, ShaderParamType_t paramType )
{
	CDmAttribute *pAttribute = NULL;
	switch ( paramType )
	{
	case SHADER_PARAM_TYPE_INTEGER:
		pAttribute = pMaterial->AddAttribute( pParamName, AT_INT );
		break;

	case SHADER_PARAM_TYPE_BOOL:
		pAttribute = pMaterial->AddAttribute( pParamName, AT_BOOL );
		break;

 	case SHADER_PARAM_TYPE_FLOAT:
		pAttribute = pMaterial->AddAttribute( pParamName, AT_FLOAT );
		break;

	case SHADER_PARAM_TYPE_STRING:
		pAttribute = pMaterial->AddAttribute( pParamName, AT_STRING );
		break;

	case SHADER_PARAM_TYPE_COLOR:
		pAttribute = pMaterial->AddAttribute( pParamName, AT_COLOR );
		break;

	case SHADER_PARAM_TYPE_VEC2:
		pAttribute = pMaterial->AddAttribute( pParamName, AT_VECTOR2 );
		break;

	case SHADER_PARAM_TYPE_VEC3:
		pAttribute = pMaterial->AddAttribute( pParamName, AT_VECTOR3 );
		break;

	case SHADER_PARAM_TYPE_VEC4:
		pAttribute = pMaterial->AddAttribute( pParamName, AT_VECTOR4 );
		break;

	case SHADER_PARAM_TYPE_FOURCC:
		Assert( 0 );
		break;

	case SHADER_PARAM_TYPE_MATRIX:
		pAttribute = pMaterial->AddAttribute( pParamName, AT_VMATRIX );
		break;

	case SHADER_PARAM_TYPE_TEXTURE:
		pAttribute = pMaterial->AddAttribute( pParamName, AT_STRING );
		m_pCallback->AddShaderParameter( pParamName, "vtfpicker", "vtfName" );
		break;

	case SHADER_PARAM_TYPE_MATERIAL:
		pAttribute = pMaterial->AddAttribute( pParamName, AT_STRING );
		m_pCallback->AddShaderParameter( pParamName, "vmtpicker", "vmtName" );
		break;

	default:
		break;
	}

	if ( pAttribute )
	{
		pAttribute->AddFlag( FATTRIB_USERDEFINED );
	}

	return pAttribute;
}


//-----------------------------------------------------------------------------
// A couple methods to set vmatrix param values from strings (OLD METHOD!)
//-----------------------------------------------------------------------------
bool CVMTDoc::SetVMatrixParamValue( CDmAttribute *pAttribute, const char *pValue )
{
	// FIXME: Change default strings to match DME?
	// Then we could remove this crap
	VMatrix mat;
	int count = sscanf( pValue, " [ %f %f %f %f  %f %f %f %f  %f %f %f %f  %f %f %f %f ]",
		&mat.m[0][0], &mat.m[0][1], &mat.m[0][2], &mat.m[0][3],
		&mat.m[1][0], &mat.m[1][1], &mat.m[1][2], &mat.m[1][3],
		&mat.m[2][0], &mat.m[2][1], &mat.m[2][2], &mat.m[2][3],
		&mat.m[3][0], &mat.m[3][1], &mat.m[3][2], &mat.m[3][3] );
	if (count == 16)
	{
		pAttribute->SetValue( mat );
		return true;
	}

	Vector2D scale, center;
	float angle;
	Vector2D translation;
	count = sscanf( pValue, " center %f %f scale %f %f rotate %f translate %f %f",
		&center.x, &center.y, &scale.x, &scale.y, &angle, &translation.x, &translation.y );
	if (count != 7)
		return false;

	VMatrix temp;
	MatrixBuildTranslation( mat, -center.x, -center.y, 0.0f );
	MatrixBuildScale( temp, scale.x, scale.y, 1.0f );
	MatrixMultiply( temp, mat, mat );
	MatrixBuildRotateZ( temp, angle );
	MatrixMultiply( temp, mat, mat );
	MatrixBuildTranslation( temp, center.x + translation.x, center.y + translation.y, 0.0f );
	MatrixMultiply( temp, mat, mat );
	pAttribute->SetValue( mat );
	return true;
}


//-----------------------------------------------------------------------------
// A couple methods to set vmatrix param values from strings (OLD METHOD!)
//-----------------------------------------------------------------------------
bool CVMTDoc::SetVector2DParamValue( CDmAttribute *pAttribute, const char *pValue )
{
	Vector2D vec;
	int count = sscanf( pValue, " [ %f %f ]", &vec[0], &vec[1] );
	if ( count == 2 )
	{
		pAttribute->SetValue( vec );
		return true;
	}

	count = sscanf( pValue, " { %f %f }", &vec[0], &vec[1] );
	if ( count == 2 )
	{
		vec /= 255.0f;
		pAttribute->SetValue( vec );
		return true;
	}

	return false;
}


//-----------------------------------------------------------------------------
// A couple methods to set vmatrix param values from strings (OLD METHOD!)
//-----------------------------------------------------------------------------
bool CVMTDoc::SetVector3DParamValue( CDmAttribute *pAttribute, const char *pValue )
{
	Vector vec;
	int count = sscanf( pValue, " [ %f %f %f ]", &vec[0], &vec[1], &vec[2] );
	if ( count == 3 )
	{
		pAttribute->SetValue( vec );
		return true;
	}

	count = sscanf( pValue, " { %f %f %f }", &vec[0], &vec[1], &vec[2] );
	if ( count == 3 )
	{
		vec /= 255.0f;
		pAttribute->SetValue( vec );
		return true;
	}

	return false;
}


//-----------------------------------------------------------------------------
// A couple methods to set vmatrix param values from strings (OLD METHOD!)
//-----------------------------------------------------------------------------
bool CVMTDoc::SetVector4DParamValue( CDmAttribute *pAttribute, const char *pValue )
{
	Vector4D vec;
	int count = sscanf( pValue, " [ %f %f %f %f ]", &vec[0], &vec[1], &vec[2], &vec[3] );
	if ( count == 4 )
	{
		pAttribute->SetValue( vec );
		return true;
	}

	count = sscanf( pValue, " { %f %f %f %f }", &vec[0], &vec[1], &vec[2], &vec[3] );
	if ( count == 4 )
	{
		vec /= 255.0f;
		pAttribute->SetValue( vec );
		return true;
	}

	return false;
}


//-----------------------------------------------------------------------------
// A couple methods to set vmatrix param values from strings (OLD METHOD!)
//-----------------------------------------------------------------------------
bool CVMTDoc::SetColorParamValue( CDmAttribute *pAttribute, const char *pValue )
{
	Color c;
	int r, g, b;
	Vector vec;
	int count = sscanf( pValue, " [ %f %f %f ]", &vec[0], &vec[1], &vec[2] );
	if ( count == 3 )
	{
		vec *= 255.0f;
		r = clamp( vec[0], 0, 255 );
		g = clamp( vec[1], 0, 255 );
		b = clamp( vec[2], 0, 255 );
		c.SetColor( r, g, b, 255 );
		pAttribute->SetValue( c );
		return true;
	}

	count = sscanf( pValue, " { %d %d %d }", &r, &g, &b );
	if ( count == 3 )
	{
		c.SetColor( r, g, b, 255 );
		pAttribute->SetValue( c );
		return true;
	}

	return false;
}


//-----------------------------------------------------------------------------
// Sets an attribute value from the shader param default
//-----------------------------------------------------------------------------
void CVMTDoc::SetAttributeValueFromDefault( CDmElement *pMaterial, CDmAttribute *pAttribute, const char *pValue )
{
	// FIXME: Change default strings to match DME?
	// Then we could remove this crap
	switch ( pAttribute->GetType() )
	{
	case AT_VMATRIX:
		if ( SetVMatrixParamValue( pAttribute, pValue ) )
			return;
		break;
	case AT_COLOR:
		if ( SetColorParamValue( pAttribute, pValue ) )
			return;
		break;
	case AT_VECTOR2:
		if ( SetVector2DParamValue( pAttribute, pValue ) )
			return;
		break;
	case AT_VECTOR3:
		if ( SetVector3DParamValue( pAttribute, pValue ) )
			return;
		break;
	case AT_VECTOR4:
		if ( SetVector4DParamValue( pAttribute, pValue ) )
			return;
		break;
	}

	pMaterial->SetValueFromString( pAttribute->GetName(), pValue );
}


//-----------------------------------------------------------------------------
// Add a single shader parameter if it doesn't exist
//-----------------------------------------------------------------------------
void CVMTDoc::AddNewShaderParam( CDmElement *pMaterial, const char *pParamName, ShaderParamType_t paramType, const char *pValue )
{
	char temp[512];
	Q_strncpy( temp, pParamName, sizeof(temp) );
	Q_strlower( temp );
	pParamName = temp;

	CDmAttribute* pAttribute = NULL;
	for ( pAttribute = pMaterial->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() )
	{
		// Don't bother testing against name, type, or id
		if ( pAttribute->IsFlagSet( FATTRIB_STANDARD ) )
			continue;

		const char *pAttributeName = pAttribute->GetName();
		if ( !Q_stricmp( pAttributeName, pParamName ) )
			return;
	}

	// No match? Add it!
	pAttribute = AddAttributeForShaderParameter( pMaterial, pParamName, paramType );
	if ( pAttribute )
	{
		SetAttributeValueFromDefault( pMaterial, pAttribute, pValue );
	}
}


//-----------------------------------------------------------------------------
// Add all shader parameters that don't currently exist
//-----------------------------------------------------------------------------
void CVMTDoc::AddNewShaderParams( CDmElement *pMaterial, IShader *pShader )
{
	// First add all flags
	m_pCallback->RemoveAllFlagParameters();
	int nCount = g_pMaterialSystem->ShaderFlagCount();
 	int i;
	for ( i = 0; i < nCount; ++i )
	{
		const char *pParamName = g_pMaterialSystem->ShaderFlagName( i );
		AddNewShaderParam( pMaterial, pParamName, SHADER_PARAM_TYPE_BOOL, "0" );
		m_pCallback->AddFlagParameter( pParamName );
	}

	// Next add all shader-specific parameters
	nCount = pShader->GetNumParams();
	for ( i = 0; i < nCount; ++i )
	{
		const char *pParamName = pShader->GetParamName( i );

		// Don't add parameters that don't want to be editable
		if ( pShader->GetParamFlags( i ) & SHADER_PARAM_NOT_EDITABLE )
			continue;

		ShaderParamType_t paramType = pShader->GetParamType( i );
		const char *pDefault = pShader->GetParamDefault( i );
		AddNewShaderParam( pMaterial, pParamName, paramType, pDefault );
	}
}


//-----------------------------------------------------------------------------
// Sets shader parameters to the default for that shader
//-----------------------------------------------------------------------------
void CVMTDoc::SetParamsToDefault()
{
	// This is undoable
	CAppUndoScopeGuard guard( 0, "Set Params to Default", "Set Params to Default" );

	// Next add all shader-specific parameters
	int nCount = m_pCurrentIShader->GetNumParams();
	for ( int i = 0; i < nCount; ++i )
	{
		const char *pParamName = m_pCurrentIShader->GetParamName( i );

		// Don't set parameters that don't want to be editable
		if ( m_pCurrentIShader->GetParamFlags( i ) & SHADER_PARAM_NOT_EDITABLE )
			continue;

		char pAttributeName[512];
		Q_strncpy( pAttributeName, pParamName, sizeof(pAttributeName) );
		Q_strlower( pAttributeName );

		if ( !m_hRoot->HasAttribute( pAttributeName ) )
			continue;

		CDmAttribute *pAttribute = m_hRoot->GetAttribute( pAttributeName );
		const char *pDefault = m_pCurrentIShader->GetParamDefault( i );
		SetAttributeValueFromDefault( m_hRoot, pAttribute, pDefault );
	}
}

	
//-----------------------------------------------------------------------------
// Sets the shader in the material
//-----------------------------------------------------------------------------
void CVMTDoc::SetShader( const char *pShaderName )
{
	// No change? don't bother
	if ( !Q_stricmp( m_CurrentShader, pShaderName ) )
		return;

	m_CurrentShader = pShaderName;

	// This is undoable
	CAppUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, "Set Shader", "Set Shader" );

	char pActualShaderName[512];
	g_pMaterialSystem->GetShaderFallback( pShaderName, pActualShaderName, sizeof(pActualShaderName) );

	m_hRoot->SetValue( "shader", pShaderName );

	// First, find the shader
	IShader *pShader = FindShader( pActualShaderName );

	// Remove all shader parameters that don't exist in the new shader
	// And also remove shader parameters that do match the default value
	RemoveUnusedShaderParams( m_hRoot, pShader, m_pCurrentIShader );

	// Add all shader parameters that don't currently exist
	AddNewShaderParams( m_hRoot, pShader );

	m_pCurrentIShader = pShader;
}


//-----------------------------------------------------------------------------
// Gets the preview material
//-----------------------------------------------------------------------------
IMaterial *CVMTDoc::GetPreviewMaterial()
{
	return m_pPreviewMaterial;
}

	
//-----------------------------------------------------------------------------
// Updates the preview material
//-----------------------------------------------------------------------------
void CVMTDoc::UpdatePreviewMaterial()
{
	if ( !m_hRoot.Get() )
		return;

	// Update all shader parameters
	SetShader( m_hRoot->GetValueString( "shader" ) );

	// Use the file conversion to write to a text format
	char buf[1024];
	CUtlBuffer vmtBuf( buf, sizeof(buf), CUtlBuffer::TEXT_BUFFER | CUtlBuffer::EXTERNAL_GROWABLE );
	g_pDataModel->Serialize( vmtBuf, "vmt", "vmt", m_hRoot );

	// Now use the text format to create a keyvalues
	KeyValues *pVMTKeyValues = new KeyValues( "ShaderName" );
	pVMTKeyValues->LoadFromBuffer( "VMT Preview", vmtBuf, g_pFileSystem, "GAME" );

	// Finally, hook the keyvalues into the material.
	m_pPreviewMaterial->SetShaderAndParams( pVMTKeyValues );
	pVMTKeyValues->deleteThis();
}

	
//-----------------------------------------------------------------------------
// Returns the root object
//-----------------------------------------------------------------------------
CDmElement *CVMTDoc::GetRootObject()
{
	return m_hRoot;
}

	
//-----------------------------------------------------------------------------
// Called when data changes
//-----------------------------------------------------------------------------
void CVMTDoc::OnDataChanged( const char *pReason, int nNotifySource, int nNotifyFlags )
{
	SetDirty( nNotifyFlags & NOTIFY_SETDIRTYFLAG ? true : false );
	UpdatePreviewMaterial();
	m_pCallback->OnDocChanged( pReason, nNotifySource, nNotifyFlags );
}