//========= 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 );
	}
}