text: //========= Copyright Valve Corporation, All rights reserved. ============//
  //
  // Purpose: 
  //
  //=============================================================================
  
  #include "movieobjects/dmeparticlesystemdefinition.h"
  #include "datamodel/dmelementfactoryhelper.h"
  #include "movieobjects/dmeeditortypedictionary.h"
  #include "toolutils/enginetools_int.h"
  #include "tier1/KeyValues.h"
  #include "tier1/utlbuffer.h"
  #include "tier1/convar.h"
  #include "particles/particles.h"
  #include "dme_controls/attributeintchoicepanel.h"
  #include "dme_controls/attributeboolchoicepanel.h"
  #include "dme_controls/attributestringchoicepanel.h"
  
  // memdbgon must be the last include file in a .cpp file!!!
  #include "tier0/memdbgon.h"
  
  
  
  //-----------------------------------------------------------------------------
  // Human readable string for the particle functions
  //-----------------------------------------------------------------------------
  static const char *s_pParticleFuncTypeName[PARTICLE_FUNCTION_COUNT] =
  {
  	"Renderer",												// FUNCTION_RENDERER = 0,
  	"Operator",												// FUNCTION_OPERATOR,
  	"Initializer",											// FUNCTION_INITIALIZER,
  	"Emitter",												// FUNCTION_EMITTER,
  	"Children",												// FUNCTION_CHILDREN,
  	"ForceGenerator",										// FUNCTION_FORCEGENERATOR
  	"Constraint",											// FUNCTION_CONSTRAINT
  };
  
  const char *GetParticleFunctionTypeName( ParticleFunctionType_t type )
  {
  	return s_pParticleFuncTypeName[type];
  }
  
  
  //-----------------------------------------------------------------------------
  // Expose this class to the scene database 
  //-----------------------------------------------------------------------------
  IMPLEMENT_ELEMENT_FACTORY_INSTALL_EXPLICITLY( DmeParticleFunction, CDmeParticleFunction );
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CDmeParticleFunction::OnConstruction()
  {
  	m_bSkipNextResolve = false;
  }
  
  void CDmeParticleFunction::OnDestruction()
  {
  	DestroyElement( m_hTypeDictionary, TD_DEEP );
  }
  
  
  //-----------------------------------------------------------------------------
  // Construct an appropriate editor attribute info
  //-----------------------------------------------------------------------------
  static void CreateEditorAttributeInfo( CDmeEditorType *pEditorType, const char *pAttributeName, const char *pWidgetInfo )
  {
  	if ( !pWidgetInfo )
  		return;
  
  	CCommand parse;
  	parse.Tokenize( pWidgetInfo );
  	if ( parse.ArgC() == 1 )
  	{
  		CDmeEditorAttributeInfo *pInfo = CreateElement< CDmeEditorAttributeInfo >( "field info" );
  		pEditorType->AddAttributeInfo( pAttributeName, pInfo ); 
  		pInfo->m_Widget = parse[0];
  		return;
  	}
  
  	if ( parse.ArgC() == 2 )
  	{
  		CDmeEditorChoicesInfo *pInfo = NULL;
  		if ( !Q_stricmp( parse[0], "intchoice" ) )
  		{
  			pInfo = CreateElement< CDmeEditorIntChoicesInfo >( "field info" );
  		}
  
  		if ( !Q_stricmp( parse[0], "boolchoice" ) )
  		{
  			pInfo = CreateElement< CDmeEditorBoolChoicesInfo >( "field info" );
  		}
  
  		if ( !Q_stricmp( parse[0], "stringchoice" ) )
  		{
  			pInfo = CreateElement< CDmeEditorStringChoicesInfo >( "field info" );
  		}
  
  		if ( !Q_stricmp( parse[0], "elementchoice" ) )
  		{
  			pInfo = CreateElement< CDmeEditorChoicesInfo >( "field info" );
  		}
  
  		if ( pInfo )
  		{
  			pInfo->SetChoiceType( parse[1] );
  			pEditorType->AddAttributeInfo( pAttributeName, pInfo ); 
  			pInfo->m_Widget = parse[0];
  			return;
  		}
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Used for backward compat
  //-----------------------------------------------------------------------------
  void CDmeParticleFunction::AddMissingFields( const DmxElementUnpackStructure_t *pUnpack )
  {
  	DestroyElement( m_hTypeDictionary, TD_DEEP );
  	m_hTypeDictionary = CreateElement< CDmeEditorTypeDictionary >( "particleFunctionDict" );
  	CDmeEditorType *pEditorType = CreateElement< CDmeEditorType >( GetTypeString() );
  
  	for ( ; pUnpack->m_pAttributeName; ++pUnpack )
  	{
  		CreateEditorAttributeInfo( pEditorType, pUnpack->m_pAttributeName, (const char *)pUnpack->m_pUserData );
  
  		// Can happen if 'name' or 'functionName' is used
  		if ( HasAttribute( pUnpack->m_pAttributeName ) )
  			continue;
  
  		CDmAttribute *pAttribute = AddAttribute( pUnpack->m_pAttributeName, pUnpack->m_AttributeType );
  		if ( pUnpack->m_pDefaultString )
  		{
  			int nLen = Q_strlen( pUnpack->m_pDefaultString );
  			CUtlBuffer bufParse( pUnpack->m_pDefaultString, nLen, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY );
  			pAttribute->Unserialize( bufParse );
  		}
  	}
  
  	m_hTypeDictionary->AddEditorType( pEditorType );
  }
  
  
  //-----------------------------------------------------------------------------
  // Sets the particle operator
  //-----------------------------------------------------------------------------
  void CDmeParticleFunction::UpdateAttributes( const DmxElementUnpackStructure_t *pUnpack )
  {
  	// Delete all old attributes
  	CDmAttribute *pNext;
  	for( CDmAttribute *pAttr = FirstAttribute(); pAttr; pAttr = pNext )
  	{
  		pNext = pAttr->NextAttribute();
  		if ( pAttr->IsFlagSet( FATTRIB_EXTERNAL | FATTRIB_STANDARD ) )
  			continue;
  
  		RemoveAttributeByPtr( pAttr );
  	}
  
  	AddMissingFields( pUnpack );
  }
  
  
  //-----------------------------------------------------------------------------
  // Marks a particle system as a new instance
  // This is basically a workaround to prevent newly-copied particle functions
  // from recompiling themselves a zillion times
  //-----------------------------------------------------------------------------
  void CDmeParticleFunction::MarkNewInstance()
  {
  	m_bSkipNextResolve = true;
  }
  
  
  //-----------------------------------------------------------------------------
  // Don't bother resolving during unserialization, the owning def will handle it
  //-----------------------------------------------------------------------------
  void CDmeParticleFunction::OnElementUnserialized()
  {
  	BaseClass::OnElementUnserialized();
  	MarkNewInstance();
  }
  
  
  //-----------------------------------------------------------------------------
  // Recompiles the particle system when a change occurs
  //-----------------------------------------------------------------------------
  void CDmeParticleFunction::Resolve()
  {
  	BaseClass::Resolve();
  
  	if ( m_bSkipNextResolve )
  	{
  		m_bSkipNextResolve = false;
  		return;
  	}
  
  	for( CDmAttribute* pAttr = FirstAttribute(); pAttr; pAttr = pAttr->NextAttribute() )
  	{
  		if ( !pAttr->IsFlagSet( FATTRIB_DIRTY ) )
  			continue;
  
  		// Find all CDmeParticleSystemDefinitions referring to this function 
  		DmAttributeReferenceIterator_t i = g_pDataModel->FirstAttributeReferencingElement( GetHandle() );
  		while ( i != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID )
  		{
  			CDmAttribute *pAttribute = g_pDataModel->GetAttribute( i );
  
  			// NOTE: This could cause the same particle system definition to recompile
  			// multiple times if it refers to the same function multiple times,
  			// but we don't expect that to happen, so we won't bother checking for it
  			CDmeParticleSystemDefinition *pDef = CastElement<CDmeParticleSystemDefinition>( pAttribute->GetOwner() );
  			if ( pDef && pDef->GetFileId() == GetFileId() )
  			{
  				pDef->RecompileParticleSystem();
  			}
  			i = g_pDataModel->NextAttributeReferencingElement( i );
  		}
  		break;
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Returns the editor type dictionary
  //-----------------------------------------------------------------------------
  CDmeEditorTypeDictionary* CDmeParticleFunction::GetEditorTypeDictionary()
  {
  	return m_hTypeDictionary;
  }
  
  
  
  //-----------------------------------------------------------------------------
  // Expose this class to the scene database 
  //-----------------------------------------------------------------------------
  IMPLEMENT_ELEMENT_FACTORY_INSTALL_EXPLICITLY( DmeParticleOperator, CDmeParticleOperator );
  
  
  //-----------------------------------------------------------------------------
  // Constructor, destructor 
  //-----------------------------------------------------------------------------
  void CDmeParticleOperator::OnConstruction()
  {
  	m_FunctionName.Init( this, "functionName" );
  }
  
  void CDmeParticleOperator::OnDestruction()
  {
  }
  
  
  //-----------------------------------------------------------------------------
  // Sets the particle operator
  //-----------------------------------------------------------------------------
  void CDmeParticleOperator::SetFunction( IParticleOperatorDefinition *pDefinition )
  {
  	m_FunctionName = pDefinition->GetName();
  	const DmxElementUnpackStructure_t *pUnpack = pDefinition->GetUnpackStructure();
  	UpdateAttributes( pUnpack );
  }
  
  const char *CDmeParticleOperator::GetFunctionType() const
  {
  	return m_FunctionName;
  }
  
  
  //-----------------------------------------------------------------------------
  // Expose this class to the scene database 
  //-----------------------------------------------------------------------------
  IMPLEMENT_ELEMENT_FACTORY_INSTALL_EXPLICITLY( DmeParticleChild, CDmeParticleChild );
  
  
  //-----------------------------------------------------------------------------
  // Constructor, destructor 
  //-----------------------------------------------------------------------------
  void CDmeParticleChild::OnConstruction()
  {
  	m_Child.Init( this, "child", FATTRIB_NEVERCOPY );
  }
  
  void CDmeParticleChild::OnDestruction()
  {
  }
  
  
  //-----------------------------------------------------------------------------
  // Sets the particle system child
  //-----------------------------------------------------------------------------
  void CDmeParticleChild::SetChildParticleSystem( CDmeParticleSystemDefinition *pDef, IParticleOperatorDefinition *pDefinition )
  {
  	// FIXME: Convert system name into a 
  	m_Child = pDef;
  	const DmxElementUnpackStructure_t *pUnpack = pDefinition->GetUnpackStructure();
  	UpdateAttributes( pUnpack );
  }
  
  const char *CDmeParticleChild::GetFunctionType() const
  {
  	const CDmeParticleSystemDefinition *pChild = m_Child;
  	return pChild ? pChild->GetName() : "";
  }
  
  
  //-----------------------------------------------------------------------------
  // Expose this class to the scene database 
  //-----------------------------------------------------------------------------
  IMPLEMENT_ELEMENT_FACTORY_INSTALL_EXPLICITLY( DmeParticleSystemDefinition, CDmeParticleSystemDefinition );
  
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CDmeParticleSystemDefinition::OnConstruction()
  {
  	m_ParticleFunction[FUNCTION_RENDERER].Init( this, "renderers" );
  	m_ParticleFunction[FUNCTION_OPERATOR].Init( this, "operators" );
  	m_ParticleFunction[FUNCTION_INITIALIZER].Init( this, "initializers" );
  	m_ParticleFunction[FUNCTION_EMITTER].Init( this, "emitters" );
  	m_ParticleFunction[FUNCTION_CHILDREN].Init( this, "children" );
  	m_ParticleFunction[FUNCTION_FORCEGENERATOR].Init( this, "forces" );
  	m_ParticleFunction[FUNCTION_CONSTRAINT].Init( this, "constraints" );
  	m_bPreventNameBasedLookup.Init( this, "preventNameBasedLookup" );
  
  	m_hTypeDictionary = CreateElement< CDmeEditorTypeDictionary >( "particleSystemDefinitionDict" );
  	CDmeEditorType *pEditorType = CreateElement< CDmeEditorType >( "DmeParticleSystemDefinition" );
  
  	const DmxElementUnpackStructure_t *pUnpack = g_pParticleSystemMgr->GetParticleSystemDefinitionUnpackStructure();
  	for ( ; pUnpack->m_pAttributeName; ++pUnpack )
  	{
  		CreateEditorAttributeInfo( pEditorType, pUnpack->m_pAttributeName, (const char *)pUnpack->m_pUserData );
  
  		CDmAttribute *pAttribute = AddAttribute( pUnpack->m_pAttributeName, pUnpack->m_AttributeType );
  		if ( pUnpack->m_pDefaultString )
  		{
  			int nLen = Q_strlen( pUnpack->m_pDefaultString );
  			CUtlBuffer bufParse( pUnpack->m_pDefaultString, nLen, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY );
  			pAttribute->Unserialize( bufParse );
  		}
  	}
  
  	m_hTypeDictionary->AddEditorType( pEditorType );
  }
  
  void CDmeParticleSystemDefinition::OnDestruction()
  {
  	DestroyElement( m_hTypeDictionary, TD_DEEP );
  }
  
  
  //-----------------------------------------------------------------------------
  // Returns the editor type dictionary
  //-----------------------------------------------------------------------------
  CDmeEditorTypeDictionary* CDmeParticleSystemDefinition::GetEditorTypeDictionary()
  {
  	return m_hTypeDictionary;
  }
  
  
  //-----------------------------------------------------------------------------
  // Remove obsolete attributes
  //-----------------------------------------------------------------------------
  static void RemoveObsoleteAttributes( CDmElement *pElement, const DmxElementUnpackStructure_t *pUnpack )
  {
  	// Delete all obsolete attributes
  	CDmAttribute *pNext;
  	for( CDmAttribute *pAttr = pElement->FirstAttribute(); pAttr; pAttr = pNext )
  	{
  		pNext = pAttr->NextAttribute();
  		if ( pAttr->IsFlagSet( FATTRIB_EXTERNAL | FATTRIB_STANDARD ) )
  			continue;
  
  		bool bFound = false;
  		for ( const DmxElementUnpackStructure_t *pTrav = pUnpack; pTrav->m_pAttributeName; ++pTrav )
  		{
  			if ( !Q_stricmp( pTrav->m_pAttributeName, pAttr->GetName() ) )
  			{
  				bFound = true;
  				break;
  			}
  		}
  
  		if ( !bFound )
  		{
  			pElement->RemoveAttributeByPtr( pAttr );
  		}
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Used for automatic handling of backward compatability
  //-----------------------------------------------------------------------------
  void CDmeParticleSystemDefinition::OnElementUnserialized()
  {
  	BaseClass::OnElementUnserialized();
  
  	RemoveObsoleteAttributes( this, g_pParticleSystemMgr->GetParticleSystemDefinitionUnpackStructure() );
  
  	// Add missing fields that are new
  	for ( int i = 0; i < PARTICLE_FUNCTION_COUNT; ++i )
  	{
  		ParticleFunctionType_t type = (ParticleFunctionType_t)i;
  		CUtlVector< IParticleOperatorDefinition *> &list = g_pParticleSystemMgr->GetAvailableParticleOperatorList( type );
  		int nAvailType = list.Count();
  		int nCount = GetParticleFunctionCount( type );
  		for ( int j = 0; j < nCount; ++j )
  		{
  			CDmeParticleFunction *pFunction = GetParticleFunction( type, j );
  
  			if ( i == FUNCTION_CHILDREN )
  			{
  				RemoveObsoleteAttributes( pFunction, list[0]->GetUnpackStructure() );
  				pFunction->AddMissingFields( list[0]->GetUnpackStructure() );
  				continue;
  			}
  
  			for ( int k = 0; k < nAvailType; ++k )
  			{
  				if ( Q_stricmp( pFunction->GetName(), list[k]->GetName() ) ) 
  					continue;
  
  				RemoveObsoleteAttributes( pFunction, list[k]->GetUnpackStructure() );
  				pFunction->AddMissingFields( list[k]->GetUnpackStructure() );
  				break;
  			}
  		}
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Check to see if any attributes changed
  //-----------------------------------------------------------------------------
  void CDmeParticleSystemDefinition::Resolve()
  {
  	BaseClass::Resolve();
  	for( CDmAttribute* pAttr = FirstAttribute(); pAttr; pAttr = pAttr->NextAttribute() )
  	{
  		if ( pAttr->IsFlagSet( FATTRIB_DIRTY ) )
  		{
  			RecompileParticleSystem();
  			break;
  		}
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Add, remove
  //-----------------------------------------------------------------------------
  CDmeParticleFunction* CDmeParticleSystemDefinition::AddOperator( ParticleFunctionType_t type, const char *pFunctionName )
  {
  	CUtlVector< IParticleOperatorDefinition *> &list = g_pParticleSystemMgr->GetAvailableParticleOperatorList( type );
  
  	int nCount = list.Count();
  	for ( int i = 0; i < nCount; ++i )
  	{
  		if ( Q_stricmp( pFunctionName, list[i]->GetName() ) ) 
  			continue;
  
  		CDmeParticleOperator *pFunction = CreateElement< CDmeParticleOperator >( pFunctionName, GetFileId() );
  		m_ParticleFunction[type].AddToTail( pFunction );
  		pFunction->SetFunction( list[i] );
  		return pFunction;
  	}
  	return NULL;
  }
  
  CDmeParticleFunction* CDmeParticleSystemDefinition::AddChild( CDmeParticleSystemDefinition *pChild )
  {
  	Assert( pChild );
  
  	CUtlVector< IParticleOperatorDefinition *> &list = g_pParticleSystemMgr->GetAvailableParticleOperatorList( FUNCTION_CHILDREN );
  	Assert( list.Count() == 1 );
  	CDmeParticleChild *pFunction = CreateElement< CDmeParticleChild >( pChild->GetName(), GetFileId() );
  	m_ParticleFunction[FUNCTION_CHILDREN].AddToTail( pFunction );
  	pFunction->SetChildParticleSystem( pChild, list[0] );
  	return pFunction;
  }
  
  //-----------------------------------------------------------------------------
  // Remove
  void CDmeParticleSystemDefinition::RemoveFunction( ParticleFunctionType_t type, CDmeParticleFunction *pFunction )
  {
  	int nIndex = FindFunction( type, pFunction );
  	RemoveFunction( type, nIndex );
  }
  
  void CDmeParticleSystemDefinition::RemoveFunction( ParticleFunctionType_t type, int nIndex )
  {
  	if ( nIndex >= 0 )
  	{
  		m_ParticleFunction[type].Remove(nIndex);
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Find
  //-----------------------------------------------------------------------------
  int CDmeParticleSystemDefinition::FindFunction( ParticleFunctionType_t type, CDmeParticleFunction *pParticleFunction )
  {
  	int nCount = m_ParticleFunction[type].Count();
  	for ( int i = 0; i < nCount; ++i )
  	{
  		if ( pParticleFunction == m_ParticleFunction[type][i] )
  			return i;
  	}
  	return -1;
  }
  
  int CDmeParticleSystemDefinition::FindFunction( ParticleFunctionType_t type, const char *pFunctionName )
  {
  	int nCount = m_ParticleFunction[type].Count();
  	for ( int i = 0; i < nCount; ++i )
  	{
  		if ( !Q_stricmp( pFunctionName, m_ParticleFunction[type][i]->GetFunctionType() ) )
  			return i;
  	}
  	return -1;
  }
  
  
  //-----------------------------------------------------------------------------
  // Iteration
  //-----------------------------------------------------------------------------
  int CDmeParticleSystemDefinition::GetParticleFunctionCount( ParticleFunctionType_t type ) const
  {
  	return m_ParticleFunction[type].Count();
  }
  
  CDmeParticleFunction *CDmeParticleSystemDefinition::GetParticleFunction( ParticleFunctionType_t type, int nIndex )
  {
  	return m_ParticleFunction[type][nIndex];
  }
  
  
  //-----------------------------------------------------------------------------
  // Reordering
  //-----------------------------------------------------------------------------
  void CDmeParticleSystemDefinition::MoveFunctionUp( ParticleFunctionType_t type, CDmeParticleFunction *pElement )
  {
  	int nIndex = FindFunction( type, pElement );
  	if ( nIndex > 0 )
  	{
  		m_ParticleFunction[type].Swap( nIndex, nIndex - 1 );
  	}
  }
  
  void CDmeParticleSystemDefinition::MoveFunctionDown( ParticleFunctionType_t type, CDmeParticleFunction *pElement )
  {
  	int nIndex = FindFunction( type, pElement );
  	int nLastIndex = m_ParticleFunction[type].Count() - 1;
  	if ( nIndex >= 0 && nIndex < nLastIndex )
  	{
  		m_ParticleFunction[type].Swap( nIndex, nIndex + 1 );
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Marks a particle system as a new instance
  // This is basically a workaround to prevent newly-copied particle functions
  // from recompiling themselves a zillion times
  //-----------------------------------------------------------------------------
  void CDmeParticleSystemDefinition::MarkNewInstance()
  {
  	for ( int i = 0; i < PARTICLE_FUNCTION_COUNT; ++i )
  	{
  		int nCount = m_ParticleFunction[i].Count();
  		for ( int j = 0; j < nCount; ++j )
  		{
  			m_ParticleFunction[i][j]->MarkNewInstance();
  		}
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Recompiles the particle system when a change occurs
  //-----------------------------------------------------------------------------
  void CDmeParticleSystemDefinition::RecompileParticleSystem()
  {
  	const char *pFileFormat = "pcf";
  	const char *pEncoding = g_pDataModel->GetDefaultEncoding( pFileFormat );
  	int nFlags = g_pDataModel->IsEncodingBinary( pEncoding ) ? 0 : CUtlBuffer::TEXT_BUFFER;
  	CUtlBuffer buf( 0, 0, nFlags );
  	if ( g_pDataModel->Serialize( buf, pEncoding, pFileFormat, GetHandle() ) )
  	{
  		g_pParticleSystemMgr->ReadParticleConfigFile( buf, true, NULL );
  	}
  }
  
  
  