// Asura_ServerEntity_DialogueController.cpp
// Thomas Young - 17.11.08
// Copyright (C) 2008 Rebellion, All Rights Reserved.

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

#ifdef ASURA_USE_DIALOGUE_SYSTEM

#include "Entity/Sound/Asura_ServerEntity_DialogueController.h"

#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "Entity/Asura_Entity_Classification.h"
#include "Entity/Asura_Entity_Event.h"
#include "Network/Asura_Message.h"
#include "Network/Asura_Network.h"
#include "Sound/Asura_Dialogue.h"
#include "Sound/Asura_Dialogue_System.h"
#if defined(ASURA_USE_STREAMING)
#include "Streaming/Asura_Streaming_System.h"
#endif

#ifdef ASURA_TOOLS
#include "EntityTemplate/Tools_EntityTemplate_System.h"
#endif

// S T A T I C   M E M B E R   V A R I A B L E S /////////////////////////////

Asura_Hashed_List<Asura_ServerEntity_DialogueController> Asura_ServerEntity_DialogueController::s_xList(5);

// C O N S T A N T S /////////////////////////////////////////////////////////

static const int	iASURA_DIALOGUE_CONTROLLER_CHUNK_VERSION = 2;
static const int	iASURA_DIALOGUE_CONTROLLER_INVALID_LINE = -1;
static const float	fASURA_DIALOGUE_CONTROLLER_INVALID_TIME = -1.0f;

// F U N C T I O N   D E F I N I T I O N S ///////////////////////////////////

Asura_ServerEntity_DialogueController::Asura_ServerEntity_DialogueController( ) : Asura_ServerEntity_Activatable()	
{
	Constructor ();
}


Asura_ServerEntity_DialogueController::Asura_ServerEntity_DialogueController( Asura_Guid uGuid ) : Asura_ServerEntity_Activatable( uGuid )	
{
	Constructor ();
}


void Asura_ServerEntity_DialogueController::Constructor ()
{
	Classification = AsuraEntityClass_DialogueController;

	s_xList.AddToList(this);

	#ifdef ASURA_USE_STREAMING
	m_uHandlerID = Asura_Dialogue_Storage::GetCurrentHandler().GetHandlerID();
	#endif

	m_uEventHash	= ASURA_HASH_ID_UNSET;
	m_pxEvent		= 0;

	m_bDeactivateLinesOnUse = false;
	m_bUseRandomDelay = false;

	m_fMinRandomDelay = 0.0f;
	m_fMaxRandomDelay = 0.0f;

	m_xLines.Clear();
	m_xMappings.Clear();

	m_iLastPlayed = iASURA_DIALOGUE_CONTROLLER_INVALID_LINE;
	m_fDelayTimer = 0.0f;
	m_eState = STATE_NOT_PLAYING;

	m_piCDRandomList = 0;
	m_iCDRandomListSize = 0;
	m_iLastCDRandomPlayed = -1;

	m_bShouldApplyPlayProbability = true;	// Apply the play probability the first time we trigger
	m_fLastPlayTime = fASURA_DIALOGUE_CONTROLLER_INVALID_TIME;
}


void Asura_ServerEntity_DialogueController::CreateFromChunkStream (Asura_Guid uGuid, Asura_Chunk_Stream& xStream)
{
	Asura_ServerEntity_DialogueController* pxEntity = new Asura_ServerEntity_DialogueController( uGuid );
	if (pxEntity)
	{
		pxEntity->ReadFromChunkStream( xStream );
	}
}


Asura_ServerEntity_DialogueController::~Asura_ServerEntity_DialogueController()
{
	delete []m_piCDRandomList;
	m_piCDRandomList = 0;

	s_xList.RemoveFromList(this);
}


void Asura_ServerEntity_DialogueController::WriteToChunkStream ( Asura_Chunk_Stream& xStream ) const
{
	Asura_ServerEntity_Activatable::WriteToChunkStream( xStream );

	int iVersion = iASURA_DIALOGUE_CONTROLLER_CHUNK_VERSION;
	xStream << iVersion;

	xStream << m_bDeactivateLinesOnUse;
	xStream << m_bUseRandomDelay;
	xStream << m_fMinRandomDelay;
	xStream << m_fMaxRandomDelay;
	
	xStream << m_uEventHash;
	
	xStream << m_iLastPlayed;
	xStream << m_fDelayTimer;
	xStream << static_cast<int>( m_eState );

	u_int uNumLines = m_xLines.Size();
	xStream << uNumLines;
	for( u_int uLine = 0; uLine < m_xLines.Size(); ++uLine )
	{
		m_xLines[ uLine ].WriteToChunkStream( xStream );
	}
	
	u_int uNumMappings = GetNumMappings();
	xStream << uNumMappings;

	for (u_int u = 0; u < uNumMappings; ++u)
	{
		xStream << m_xMappings[u].m_uCharacterHashID;
		xStream << m_xMappings[u].m_uCharacterGuid;
	}

	xStream << m_bShouldApplyPlayProbability;
	xStream << m_fLastPlayTime;
}


void Asura_ServerEntity_DialogueController::ReadFromChunkStream (Asura_Chunk_Stream& xStream)
{
	if( m_piCDRandomList )
	{
		// Just in case
		delete []m_piCDRandomList;
		m_piCDRandomList = 0;
	}

	m_piCDRandomList		= 0;
	m_iCDRandomListSize		= 0;
	m_iLastCDRandomPlayed	= -1;

	m_xLines.Clear		();
	m_xMappings.Clear	();
	
	Asura_ServerEntity_Activatable::ReadFromChunkStream( xStream );

	int iVersion;
	xStream >> iVersion;

	Asura_Assert( iVersion <= iASURA_DIALOGUE_CONTROLLER_CHUNK_VERSION, "Unknown dialogue controller chunk, you probably need new code" );

	xStream >> m_bDeactivateLinesOnUse;
	xStream >> m_bUseRandomDelay;
	xStream >> m_fMinRandomDelay;
	xStream >> m_fMaxRandomDelay;

	xStream >> m_uEventHash;

	xStream >> m_iLastPlayed;
	xStream >> m_fDelayTimer;
	
	int iEnum;
	xStream >> iEnum;
	m_eState = static_cast<CONTROLLER_STATE>( iEnum );

	u_int uNumLines;
	xStream >> uNumLines;
	m_xLines.SetSize( uNumLines );
	for( u_int uLine = 0; uLine < uNumLines; ++uLine )
	{
		m_xLines[ uLine ].ReadFromChunkStream( xStream );
	}

	u_int uNumMappings;
	xStream >> uNumMappings;
	m_xMappings.SetSize( uNumMappings );
	for (u_int uMapping = 0; uMapping < uNumMappings; ++uMapping)
	{
		xStream >> m_xMappings[uMapping].m_uCharacterHashID;
		xStream >> m_xMappings[uMapping].m_uCharacterGuid;
	}

	if( iVersion > 1 )
	{
		xStream >> m_bShouldApplyPlayProbability;
		xStream >> m_fLastPlayTime;
	}

	// Look up our event
	m_pxEvent = 0;
	if( m_uEventHash != ASURA_HASH_ID_UNSET )
	{
		m_pxEvent = Asura_Dialogue_System::FindEvent( m_uEventHash );
		Asura_Assertf ( m_pxEvent, ("Asura_ServerEntity_DialogueController::ReadFromChunkStream: (Guid: %d) Failed to find dialogue event with hash %X! (NumLines:%d) (NumMappings:%d)", GetGuid(), m_uEventHash, uNumLines, uNumMappings ) );
	}

	if( m_pxEvent )
	{
		// Check our lines are valid
		ValidateLines();

		if( m_pxEvent->GetPlaybackType() == ASURA_DPT_CDPLAYERRANDOM )
		{
			// Rebuild our random list if we are CD random (this is not saved to a chunk)
			BuildCDRandomList();
		}
	}
}


u_int Asura_ServerEntity_DialogueController::GetNumMappings() const
{
	if( !m_pxEvent )
	{
		return 0;
	}
	return m_xMappings.Size();
}


void Asura_ServerEntity_DialogueController::OnTrigger()
{
	if( !m_pxEvent )
	{
		return;
	}

	// Check the play probability
	if( m_bShouldApplyPlayProbability )
	{
		if ( Asura_Random::GetInt( 1, 100 ) > m_pxEvent->GetPlayProbability() )
		{
			return;
		}
	}
	m_bShouldApplyPlayProbability = false;

	// Check the replay delay
	if(		( m_fLastPlayTime != fASURA_DIALOGUE_CONTROLLER_INVALID_TIME )
		&&	( ( Asura_Timers::GetServerAbsoluteTime() - m_fLastPlayTime ) < m_pxEvent->GetReplayDelayTime() ) )
	{
		return;
	}

	Asura_ServerEntity_Activatable::OnTrigger();

	if( ( m_eState == STATE_PLAYING ) || ( m_eState == STATE_WAITING_TO_PLAY ) )
	{
		Asura_Assert( false, "Dialogue controller is already playing, why are you triggering it?" );
		return;
	}
	
	// Resume playback of this entity (should we apply a random delay here? I have assumed not)
	PlayNextLine ();
}


bool Asura_ServerEntity_DialogueController::IsTriggered ( )
{
	return ( m_eState != STATE_NOT_PLAYING );
}


bool Asura_ServerEntity_DialogueController::HandleMessage(const Asura_Message* pxMessage)
{
	if( IsDestroyed() ) 
	{ 
		return true; 
	}

	if ( pxMessage )
	{
		switch ( pxMessage->GetMsgID() )
		{
			case ASURA_MSG_NOTIFYFINISHED:
			{
				// Finished a line
				const Asura_Message_Dialogue_NotifyFinished* pxData = reinterpret_cast<const Asura_Message_Dialogue_NotifyFinished*>( pxMessage->GetData() );

				if(pxData->m_iMessageData)
				{
					//Was just playing a single line as a result of a trigger line message.
					//So just trigger the end of line messages ,and don't check for continuing onto the next
					//RAR 14/04/2009
					int iLineIndex = pxData->m_iMessageData - 1;
					if(iLineIndex>=0 && iLineIndex < static_cast<int>( m_xLines.Size()))
					{
						if( m_xLines[ iLineIndex ].m_usEndMsgBlock != ASURA_STATICMESSAGE_EMPTYBLOCK )
						{
							Asura_StaticMessage_System::SendMessages( m_xLines[ iLineIndex ].m_usEndMsgBlock, Guid );
						}
					}
					else
					{
						Asura_Assert(0, "Invalid line index recieved from notify finished message");
					}

				}
				else
				{
					Asura_Assert(m_iLastPlayed != iASURA_DIALOGUE_CONTROLLER_INVALID_LINE, "Last played line not set");
					// Send the end messages for this line
					if( m_iLastPlayed != iASURA_DIALOGUE_CONTROLLER_INVALID_LINE &&
						m_xLines[ m_iLastPlayed ].m_usEndMsgBlock != ASURA_STATICMESSAGE_EMPTYBLOCK )
					{
						Asura_StaticMessage_System::SendMessages( m_xLines[ m_iLastPlayed ].m_usEndMsgBlock, Guid );
					}

					// Check if we should send our final message block
					CheckSendFinalMessages();

					m_fLastPlayTime = Asura_Timers::GetServerAbsoluteTime();

					// If we weren't told to stop, play the next line
					if( m_eState != STATE_NOT_PLAYING )
					{
						if( m_iLastPlayed == iASURA_DIALOGUE_CONTROLLER_INVALID_LINE || !m_xLines[ m_iLastPlayed ].m_bPlayNext )
						{
							// This line is set to stop playback when completed
							m_eState = STATE_NOT_PLAYING;
						}
						else
						{
							if( m_bUseRandomDelay )
							{
								// Wait a random amount of time before continuing
								m_fDelayTimer = Asura_Random::GetFloat( m_fMinRandomDelay, m_fMaxRandomDelay );
								m_eState = STATE_WAITING_TO_PLAY;

								// Update while we are waiting
								SetNeverUpdates( false );
							}
							else
							{
								PlayNextLine ();
							}
						}
					}
				}
				return true;
			}

			case ASURA_MSG_DIALOGUE_CONTROLLER_TOGGLE_LINE:
			{
				const Asura_Msg_Dialogue_ToggleLine* pxData = reinterpret_cast<const Asura_Msg_Dialogue_ToggleLine*>( pxMessage->GetData() );
				
				// Find this line and set its activation properties
				for( int iLine = 0; iLine < static_cast<int>( m_xLines.Size() ); ++iLine )
				{
					if( ( m_xLines[ iLine ].m_uDialogueLine == pxData->m_uLineID ) || ( pxData->m_uLineID == ASURA_HASH_ID_UNSET ) )
					{
						m_xLines[ iLine ].m_bActive = pxData->m_bEnable;
					}
				}
				return true;
			}

			case ASURA_MSG_DIALOGUE_CONTROLLER_INTERRUPT:
			{
				const Asura_Msg_Dialogue_Interrupt* pxData = reinterpret_cast<const Asura_Msg_Dialogue_Interrupt*>( pxMessage->GetData() );

				if( pxData->m_bImmediate && ( m_eState == STATE_PLAYING ) )
				{
					// We should send a message to the dialogue system to stop playing the current line
					Asura_Message* pxStopLineMessage = Asura_Network::CreateMessage	( ASURA_MSG_DIALOGUE_STOPLINE_CONTROLLER, sizeof( Asura_Msg_Dialogue_StopLine_Controller ) );

					if( pxStopLineMessage )
					{
						Asura_Msg_Dialogue_StopLine_Controller* pxStopLineData = reinterpret_cast<Asura_Msg_Dialogue_StopLine_Controller*>( pxStopLineMessage->GetData() );
						Asura_Assert(m_iLastPlayed != iASURA_DIALOGUE_CONTROLLER_INVALID_LINE, "Last played line not set");
						if (m_iLastPlayed != iASURA_DIALOGUE_CONTROLLER_INVALID_LINE) // not sure what to do here without a valid last played 
						{
							pxStopLineData->m_uLineID = m_xLines[ m_iLastPlayed ].m_uDialogueLine;
						}
												
						#ifdef ASURA_USE_STREAMING
						pxStopLineData->m_uHandlerID = m_uHandlerID;
						#else
						pxStopLineData->m_uHandlerID = 0;
						#endif

						pxStopLineMessage->To = ASURA_GUID_ALL_CLIENTMACHINES;
						pxStopLineMessage->From = GetGuid();

						Asura_Network::Send( pxStopLineMessage );
					}
				}

				m_eState = STATE_NOT_PLAYING;

				return true;
			}

			case ASURA_MSG_DIALOGUE_CONTROLLER_TRIGGER_LINE:
			{
				if(IsActive())
				{
					const Asura_Msg_Dialogue_TriggerLine* pxData = reinterpret_cast<const Asura_Msg_Dialogue_TriggerLine*>( pxMessage->GetData() );
				
					// Find this line and trigger it
					for( int iLine = 0; iLine < static_cast<int>( m_xLines.Size() ); ++iLine )
					{
						if( ( m_xLines[ iLine ].m_uDialogueLine == pxData->m_uLineID ))
						{
							PlayLine(iLine, true);
							break;
						}
					}
				}
				return true;
			}
		}
	}

	return Asura_ServerEntity_Activatable::HandleMessage( pxMessage );
}


void Asura_ServerEntity_DialogueController::PlayNextLine()
{
	const int iLine = GetNextLineToPlay();

	if( iLine == iASURA_DIALOGUE_CONTROLLER_INVALID_LINE )
	{
		// Must have completed playback
		// SendMessagesForEvent( ASURA_ENTITY_EVENT_ONFINISHED );
		m_eState = STATE_NOT_PLAYING;
	}
	else
	{
		Asura_Assert( m_xLines[ iLine ].m_bActive && m_xLines[ iLine ].m_bIsValid, "GetNextLineToPlay returned an invalid line" );

		PlayLine( iLine, false );
	}
}


void Asura_ServerEntity_DialogueController::PlayLine ( const int iLineIndex, bool bPlaySingleLine )
{
	Asura_Assert( ( static_cast<int>( m_xLines.Size() ) > iLineIndex ) && ( iLineIndex >= 0 ), "Invalid line index" );

	if( !m_xLines[iLineIndex].m_bIsValid )
	{
		if(!bPlaySingleLine) m_eState = STATE_NOT_PLAYING;
		return;
	}

	if( !m_xLines[iLineIndex].m_bActive )
	{
		if(!bPlaySingleLine) m_eState = STATE_NOT_PLAYING;
		return;
	}

	// Find the character to play this dialogue line in the mapping
	Asura_Guid uCharacter = ASURA_GUID_UNREGISTERED;

	const Asura_Dialogue_Line* pxLine = Asura_Dialogue_Storage::FindLine( m_xLines[iLineIndex].m_uDialogueLine );

	Asura_Assertf( pxLine, ( "Failed to find line with hash %u", m_xLines[iLineIndex].m_uDialogueLine ) );

	if( !pxLine )
	{
		if(!bPlaySingleLine) m_eState = STATE_NOT_PLAYING;
		return;
	}

	for( u_int uMapping = 0; uMapping < m_xMappings.Size(); ++uMapping )
	{
		if( m_xMappings[uMapping].m_uCharacterHashID == pxLine->GetCharacter() )
		{
			uCharacter = m_xMappings[uMapping].m_uCharacterGuid;
			break;
		}
	}

	// Send the messages in the 'start' message block
	if( m_xLines[iLineIndex].m_usStartMsgBlock != ASURA_STATICMESSAGE_EMPTYBLOCK )
	{
		Asura_StaticMessage_System::SendMessages( m_xLines[iLineIndex].m_usStartMsgBlock, Guid, 0.0f );
	}

	// Send a message to play this line

	Asura_Message* pxMsg = Asura_Network::CreateMessage	( ASURA_MSG_DIALOGUE_PLAYLINE_CONTROLLER, sizeof( Asura_Msg_Dialogue_PlayLine_Controller ) );

	if( pxMsg )
	{
		Asura_Msg_Dialogue_PlayLine_Controller* pxData = reinterpret_cast<Asura_Msg_Dialogue_PlayLine_Controller*>( pxMsg->GetData() );

		pxData->m_uLineID = m_xLines[iLineIndex].m_uDialogueLine;
		pxData->m_uSpeaker = uCharacter;
		#ifdef ASURA_USE_STREAMING
		pxData->m_uHandlerID = m_uHandlerID;
		#else
		pxData->m_uHandlerID = 0;
		#endif
		if(bPlaySingleLine)
			pxData->m_iNotifyFinishedMessageData = iLineIndex+1;
		else
			pxData->m_iNotifyFinishedMessageData = 0;

		pxMsg->To = ASURA_GUID_ALL_CLIENTMACHINES;
		pxMsg->From = GetGuid();

	}

	if( m_bDeactivateLinesOnUse )
	{
		m_xLines[iLineIndex].m_bActive = false;
	}

	m_iLastPlayed = iLineIndex;

	if(!bPlaySingleLine) m_eState = STATE_PLAYING;

	//Message to play the dialogue line done at the end, in case it finishes/fails to play immediately.
	//RAR 14/04/2009
	if(pxMsg)
	{
		Asura_Network::Send( pxMsg );
	}
}




int Asura_ServerEntity_DialogueController::GetNextLineToPlay( )
{
	// Check if we have finished playing back everything in this controller, depending on the playback type
	
	if( !m_pxEvent )
	{
		return true;
	}

	ASURA_DIALOGUE_PLAYBACK_TYPE ePlayType = m_pxEvent->GetPlaybackType();

	switch ( ePlayType )
	{
		case ASURA_DPT_ALLSEQUENTIAL:
		{
			// Was the last played line the final (active) entry?
			bool bPlayedFinalLine = false;

			int iLastLine = m_xLines.Size();
			
			while( iLastLine != 0 ) 
			{
				--iLastLine;

				if( m_xLines[iLastLine].m_bIsValid && m_xLines[iLastLine].m_bActive )
				{
					if( m_iLastPlayed == iLastLine )
					{
						bPlayedFinalLine = true;
					}
					break;
				}
			}

			if( !bPlayedFinalLine )
			{
				const int iNextLine = ( m_iLastPlayed == iASURA_DIALOGUE_CONTROLLER_INVALID_LINE ) ? 0 : ( m_iLastPlayed + 1 );

				// Are there any active, valid lines after the current one?
				for( u_int uLine = static_cast<u_int>( iNextLine ); uLine < m_xLines.Size(); ++uLine )
				{
					if( m_xLines[uLine].m_bIsValid && m_xLines[uLine].m_bActive )
					{
						if( uLine == ( m_xLines.Size() - 1 ) )
						{
							// This is the last line, force play next to true
							// so that we enter this logic again and 'loop' playback
							m_xLines[uLine].m_bPlayNext = true;
						}
						// This is a valid, active line
						return static_cast<int>( uLine );
					}
				}
				// Didnt find any more lines, must be finished...
			}

			m_iLastPlayed = iASURA_DIALOGUE_CONTROLLER_INVALID_LINE;
			return iASURA_DIALOGUE_CONTROLLER_INVALID_LINE;
		}

		case ASURA_DPT_ONEBYONE:
		{
			const int iNextLine = ( m_iLastPlayed == iASURA_DIALOGUE_CONTROLLER_INVALID_LINE ) ? 0 : ( m_iLastPlayed + 1 ) % m_xLines.Size();
			
			// Are there any active, valid lines after the current one?
			const u_int uLineCheckStart = static_cast<u_int>( iNextLine );
			u_int uCheckLine = uLineCheckStart;

			while( !( m_xLines[ uCheckLine ].m_bIsValid && m_xLines[ uCheckLine ].m_bActive ) )
			{
				uCheckLine = ( uCheckLine + 1 ) % m_xLines.Size();
				
				if( uCheckLine == uLineCheckStart )
				{
					// We have checked every line, non are active and valid
					return iASURA_DIALOGUE_CONTROLLER_INVALID_LINE;
				}
			}
			// This is a valid, active line
			return static_cast<int>( uCheckLine );
		}

		case ASURA_DPT_RANDOM:
		{
			// Make an array with [line weight] entries per line and pick from it randomly

			//Get the array size
			int iArraySize = 0;
			for( u_int uLine = 0; uLine < m_xLines.Size(); ++uLine )
			{
				if( m_xLines[uLine].m_bIsValid && m_xLines[uLine].m_bActive )
				{
					// This is a valid, active line
					iArraySize += static_cast<int>( m_xLines[ uLine ].m_ucWeight );
				}
			}

			if( iArraySize == 0 )
			{
				return iASURA_DIALOGUE_CONTROLLER_INVALID_LINE;
			}

			// Create the array and fill it
			int* piRandomArray = new int[iArraySize];

			int iCurrentElement = 0;
			for( u_int uLine = 0; uLine < m_xLines.Size(); ++uLine )
			{
				if( m_xLines[uLine].m_bIsValid && m_xLines[uLine].m_bActive )
				{
					for( int iEntry = 0; iEntry < static_cast<int>( m_xLines[ uLine ].m_ucWeight ); ++iEntry)
					{
						piRandomArray[ iCurrentElement ] = static_cast<int>( uLine );
						++iCurrentElement;
					}
				}
			}

			// Now pick an entry
			const int iLine = piRandomArray[ Asura_Random::GetInt( 0, iArraySize - 1 ) ];
			
			delete []piRandomArray;
			piRandomArray = 0;

			return iLine;
		}

		case ASURA_DPT_CDPLAYERRANDOM:
		{
			if( m_iLastCDRandomPlayed == m_iCDRandomListSize )
			{
				return iASURA_DIALOGUE_CONTROLLER_INVALID_LINE;
			}

			// Get the next CD Random entry (which is active & valid)
			do 
			{
				++ m_iLastCDRandomPlayed;

				if( m_iLastCDRandomPlayed == m_iCDRandomListSize )
				{
					return iASURA_DIALOGUE_CONTROLLER_INVALID_LINE;
				}
			} 
			while	(	!m_xLines[ m_piCDRandomList[ m_iLastCDRandomPlayed ] ].m_bActive 
					||	!m_xLines[ m_piCDRandomList[ m_iLastCDRandomPlayed ] ].m_bIsValid );

			return m_piCDRandomList[ m_iLastCDRandomPlayed ];
		}
	}

	Asura_Assert( false, "Unexpected code path" );
	return iASURA_DIALOGUE_CONTROLLER_INVALID_LINE;
}


void Asura_ServerEntity_DialogueController::BuildCDRandomList ( )
{
	if( m_piCDRandomList )
	{
		delete []m_piCDRandomList;
		m_piCDRandomList = 0;
	}

	// Count how many entries we need
	m_iCDRandomListSize = 0;

	for( u_int uLine = 0; uLine < m_xLines.Size(); ++uLine )
	{
		if( m_xLines[uLine].m_bIsValid && m_xLines[uLine].m_bActive )
		{
			++m_iCDRandomListSize;
		}
	}

	// Now create it and fill it with numbers
	m_piCDRandomList = new int[ m_iCDRandomListSize ];
	int i = m_iCDRandomListSize;
	while( i )
	{
		m_piCDRandomList[i - 1] = ( i - 1 );
		--i;
	}

	// Randomise by looping through and swapping each entry with a random other entry
	for( int iEntry = 0; iEntry < m_iCDRandomListSize; ++iEntry )
	{
		const int iSwap = Asura_Random::GetInt( 0, m_iCDRandomListSize - 1);
		
		if( iSwap != iEntry )
		{
			const int iTemp = m_piCDRandomList[ iSwap ];
			m_piCDRandomList[ iSwap ] = m_piCDRandomList[ iEntry ];
			m_piCDRandomList[ iEntry ] = iTemp;
		}
	}

	m_iLastCDRandomPlayed = -1;
}


void Asura_ServerEntity_DialogueController::Update ( )
{
	PARENT::Update();

	if( m_eState == STATE_WAITING_TO_PLAY )
	{
		m_fDelayTimer -= Asura_Timers::GetFrameTime();
		
		if( m_fDelayTimer < 0.0f )
		{
			m_fDelayTimer = 0.0f;
			
			// Play the next line
			PlayNextLine ();
		}
	}

	if( m_eState != STATE_WAITING_TO_PLAY )
	{
		// If we are not waiting dont bother to update this entity anymore
		SetNeverUpdates( true );
	}
}


void Asura_ServerEntity_DialogueController::ValidateLines ( )
{
	if( !m_pxEvent )
	{
		return;
	}

	for( int iLine = 0; iLine < static_cast<int>( m_xLines.Size() ); ++iLine )
	{
		// Check that this line is present in the dialogue event
		bool bFound = false;

		const u_int uNumItems = m_pxEvent->GetNumPlayables();

		for( u_int uItem = 0; uItem < uNumItems; ++uItem )
		{
			const Asura_Dialogue_Playable* pxPlayable = m_pxEvent->GetPlayable( uItem );
			if( pxPlayable->IsLine() && ( pxPlayable->GetHashID() == m_xLines[ iLine ].m_uDialogueLine ) )
			{
				bFound = true;
				break;
			}
		}
		m_xLines[ iLine ].m_bIsValid = bFound;
	}
}


void Asura_ServerEntity_DialogueController::CheckSendFinalMessages ( )
{
	// Check if we have no valid, active lines left to play
	for( u_int uLine = 0; uLine < m_xLines.Size(); ++uLine )
	{
		if( m_xLines[uLine].m_bIsValid && m_xLines[uLine].m_bActive )
		{
			// Not 'finished'
			return;
		}
	}

	// Finished, send messages
	SendMessagesForEvent( ASURA_ENTITY_EVENT_ONFINISHED );
};

#ifdef ASURA_TOOLS //_____________________________________________________________________

bool Asura_ServerEntity_DialogueController::CanBeIncludedInTemplate() const 
{
	return Classification == AsuraEntityClass_DialogueController;
}


void Asura_ServerEntity_DialogueController::ApplyChangesForTemplateInstance(class Tools_EntityTemplateInstance* pxInstance)
{
	Asura_ServerEntity_Activatable::ApplyChangesForTemplateInstance(pxInstance);

	for(u_int u=0; u<m_xLines.Size(); u++)
	{
		m_xLines[u].m_usStartMsgBlock = pxInstance->ConvertMessageBlock(m_xLines[u].m_usStartMsgBlock);
		m_xLines[u].m_usEndMsgBlock = pxInstance->ConvertMessageBlock(m_xLines[u].m_usEndMsgBlock);
	}


	//convert the guids in the mappings list
	u_int uNumMappings = GetNumMappings();
	for (u_int u = 0; u < uNumMappings; ++u)
	{
		m_xMappings[u].m_uCharacterGuid = pxInstance->ConvertGuid( m_xMappings[u].m_uCharacterGuid );
	}
}


void Asura_ServerEntity_DialogueController::HandleEventDeletion(const Asura_Dialogue_Event* pxEvent)
{
	for(Asura_Hashed_List_It<Asura_ServerEntity_DialogueController> xIt(&s_xList);!xIt.Done();xIt.Next())
	{
		Asura_ServerEntity_DialogueController* pxEntity = xIt.GetCurrent();

		if( pxEntity->m_pxEvent == pxEvent )
		{
			pxEntity->m_pxEvent = 0;
		}
	}
}

#ifdef ASURA_TOOLS
int Asura_ServerEntity_DialogueController::GetNumCustomEvents() const
{
	return m_xLines.Size() * 2;
}
#endif

#ifdef ASURA_TOOLS
unsigned short* Asura_ServerEntity_DialogueController::GetCustomEvent(int iIndex)
{
	if(iIndex & 1)
	{
		return &m_xLines[iIndex / 2].m_usEndMsgBlock;
	}
	else
	{
		return &m_xLines[iIndex / 2].m_usStartMsgBlock;
	}
}
#endif

#ifdef ASURA_TOOLS
void Asura_ServerEntity_DialogueController::GetCustomEventName(int iEventIndex, Asura_String& xEventName)
{
	Asura_Char szBuffer[256];

	if(iEventIndex & 1)
	{
		Asura_PrintfToBfr(szBuffer, "On End - line %d", 256, true, (iEventIndex / 2) + 1);
		xEventName = szBuffer;
	}
	else
	{
		Asura_ServerEntity_DialogueController* pxDialogueEntity = static_cast<Asura_ServerEntity_DialogueController*> (this);

		int iLineIndex = (iEventIndex / 2);

		Asura_DialogueController_Line* pxLine = &pxDialogueEntity->m_xLines[iLineIndex];

		char szText[200] = "";

		if(pxLine)
		{
			const Asura_Dialogue_Line* pxDialogueLine = Asura_Dialogue_Storage::FindLine(pxLine->m_uDialogueLine);

			if(pxDialogueLine)
			{
				// Build the preview text for this line
				const Asura_UnicodeChar* wszText = pxDialogueLine ? pxDialogueLine->GetText() : NULL;
				Asura_PrintfToBfr(szText, "%S", sizeof(szText) - 3, true, wszText ? wszText : L"*None*");

				// Add ellipsis if too long
				if(strlen(szText) == sizeof(szText) - 4)
				{
					strcat(szText, "...");
				}

				// Replace newline characters by spaces
				for(char* pcNewLine = strchr(szText, '\n'); pcNewLine; pcNewLine = strchr(pcNewLine, '\n'))
				{
					*pcNewLine = ' ';
				}
			}
		}

		Asura_PrintfToBfr(szBuffer, "On Start - line %d - \"%s\"", 256, true, (iEventIndex / 2) + 1, szText);
		xEventName = szBuffer;
	}
}
#endif

Asura_Hash_ID Asura_ServerEntity_DialogueController::GetEventHashID() const
{
	return m_uEventHash;
}

void Asura_ServerEntity_DialogueController::SetEventHashID ( const Asura_Hash_ID uEventHash, const bool bCopyAndClearLineMsgs )
{
	// Clear down
	m_pxEvent				= 0;
	m_bDeactivateLinesOnUse = false;
	m_bUseRandomDelay		= false;
	m_fMinRandomDelay		= 0.0f;
	m_fMaxRandomDelay		= 0.0f;

	m_xMappings.Clear();

	bool bShouldCopyClearMsgs = bCopyAndClearLineMsgs;

	// Clear any static message blocks
	for( u_int uLine = 0; uLine < m_xLines.Size(); ++uLine )
	{
		if( m_xLines[uLine].m_usStartMsgBlock != ASURA_STATICMESSAGE_EMPTYBLOCK )
		{
			Asura_StaticMessage_System::DeleteMessageBlock( m_xLines[uLine].m_usStartMsgBlock );
		}
		if( m_xLines[uLine].m_usEndMsgBlock != ASURA_STATICMESSAGE_EMPTYBLOCK )
		{
			Asura_StaticMessage_System::DeleteMessageBlock( m_xLines[uLine].m_usEndMsgBlock );
		}
	}
	m_xLines.Clear();

	// Set our event
	m_uEventHash = uEventHash;
	if( m_uEventHash != ASURA_HASH_ID_UNSET )
	{
		m_pxEvent = Asura_Dialogue_System::FindEvent( m_uEventHash );
		Asura_Assert( m_pxEvent, "Failed to find dialogue event" );
	}

	// Make new lines
	if( m_pxEvent )
	{
		Asura_StaticMessageSetIndex eSet = ASURA_STATICMESSAGE_ENTITYSET;

		// Check where to copy messages
		if( bShouldCopyClearMsgs )
		{
			ListSelection_Overlay xMessageQuery("Static messages");
			xMessageQuery.AddItem( "Move to Cutscene message set", ASURA_STATICMESSAGE_CUTSCENESET, false, "Add static messages from dialogue lines to the cutscene set" );
			xMessageQuery.AddItem( "Move to Entity message set", ASURA_STATICMESSAGE_ENTITYSET, true, "Add static messages from dialogue lines to the entity set" );
			xMessageQuery.AddItem( "Ignore static messages", -1, false, "Add static messages from dialogue lines to the cutscene set" );
			
			bShouldCopyClearMsgs = false;

			if( xMessageQuery.DoModal() )
			{
				if( xMessageQuery.GetSelectionData() != -1 )
				{
					eSet = static_cast<Asura_StaticMessageSetIndex>( xMessageQuery.GetSelectionData() );
					bShouldCopyClearMsgs = true;
				}
			}
		}

		// Count how many lines are in this dialogue event
		const u_int uNumItems = m_pxEvent->GetNumPlayables();
		int iNumLines = 0;
		for( u_int uItem = 0; uItem < uNumItems; ++uItem )
		{
			const Asura_Dialogue_Playable* pxPlayable = m_pxEvent->GetPlayable( uItem );
			if( pxPlayable && pxPlayable->IsLine() )
			{
				++iNumLines;
			}
		}

		// Fill our line data
		m_xLines.SetSize( iNumLines );

		int iLineIndex = 0;
		for( u_int uItem = 0; uItem < uNumItems; ++uItem )
		{
			const Asura_Dialogue_Playable* pxPlayable = m_pxEvent->GetPlayable( uItem );
			if( pxPlayable && pxPlayable->IsLine() )
			{
				const Asura_Dialogue_Line* pxLine = reinterpret_cast<const Asura_Dialogue_Line*>( pxPlayable );
				if( pxLine)
				{
					m_xLines[iLineIndex].SetDefaults();
					
					m_xLines[iLineIndex].m_uDialogueLine = pxLine->GetHashID();

					if( m_pxEvent->GetPlaybackType() == ASURA_DPT_ONEBYONE )
					{
						m_xLines[iLineIndex].m_bPlayNext = false;
					}

					if( bShouldCopyClearMsgs )
					{
						// Grab a non-const copy of the event so we can clear the messages
						Asura_Dialogue_Event* pxEvent = Asura_Dialogue_Storage::GetEventByHash( m_uEventHash );

						Asura_Dialogue_PlayableIDListItem* pxPlayable = pxEvent->GetPlayableIDPointer( uItem );
						
						if( pxPlayable->m_uStartMessageID != ASURA_HASH_ID_UNSET )
						{
							// Copy this message block
							const Asura_Dialogue_Message* pxMessage = m_pxEvent->GetMessageFromStoreByHash( pxPlayable->m_uStartMessageID );
							if( pxMessage )
							{
								m_xLines[iLineIndex].m_usStartMsgBlock = Asura_StaticMessage_System::CopyMessageBlock( &pxMessage->m_xMessageBlock, eSet, ASURA_GUID_UNREGISTERED, GetGuid() );
							}
							pxPlayable->m_uStartMessageID = ASURA_HASH_ID_UNSET;
						}
						if( pxPlayable->m_uEndMessageID != ASURA_HASH_ID_UNSET )
						{
							// Copy this message block
							const Asura_Dialogue_Message* pxMessage = m_pxEvent->GetMessageFromStoreByHash( pxPlayable->m_uEndMessageID );
							if( pxMessage )
							{
								m_xLines[iLineIndex].m_usEndMsgBlock = Asura_StaticMessage_System::CopyMessageBlock( &pxMessage->m_xMessageBlock, eSet, ASURA_GUID_UNREGISTERED, GetGuid() );
							}
							pxPlayable->m_uEndMessageID = ASURA_HASH_ID_UNSET;
						}
						if( m_pxEvent->GetInitialMessageID() != ASURA_HASH_ID_UNSET )
						{
							// Depending on our playback type, copy over the initial message block to this lines start messages
							bool bShouldCopyMessage = false;
							switch( m_pxEvent->GetPlaybackType() )
							{
								case ASURA_DPT_ALLSEQUENTIAL:
								{
									bShouldCopyMessage = ( iLineIndex == 0 );
								}
								break;
								case ASURA_DPT_ONEBYONE:		// drop down
								case ASURA_DPT_RANDOM:			// drop down
								case ASURA_DPT_CDPLAYERRANDOM:
								{
									bShouldCopyMessage = true;
								}
								break;
							}
							if( bShouldCopyMessage )
							{
								Asura_StaticMessage_Handler xHandler = Asura_StaticMessage_System::GetCurrentHandler();
								const Asura_Dialogue_Message* pxMessage = m_pxEvent->GetMessageFromStoreByHash( m_pxEvent->GetInitialMessageID() );
								if( pxMessage )
								{
									if( m_xLines[iLineIndex].m_usStartMsgBlock == ASURA_STATICMESSAGE_EMPTYBLOCK )
									{
										// The line has no start messages, just copy the message block wholesale
										m_xLines[iLineIndex].m_usStartMsgBlock = Asura_StaticMessage_System::CopyMessageBlock( &pxMessage->m_xMessageBlock, eSet, ASURA_GUID_UNREGISTERED, GetGuid() );
									}
									else
									{
										// The line has start messages already, add each message individually
										for( u_int uMessage = 0; uMessage < pxMessage->m_xMessageBlock.GetNumberOfMessagesInBlock(); ++uMessage )
										{
											xHandler.AddMessage( eSet, *pxMessage->m_xMessageBlock.GetMessagePointer( uMessage), m_xLines[iLineIndex].m_usStartMsgBlock );
										}
									}
								}
							}
						}
						if( m_pxEvent->GetFinalMessageID() != ASURA_HASH_ID_UNSET )
						{
							// Depending on our playback type, copy over the final message block to this lines end messages
							bool bShouldCopyMessage = false;
							switch( m_pxEvent->GetPlaybackType() )
							{
								case ASURA_DPT_ALLSEQUENTIAL:
								{
									bShouldCopyMessage = ( iLineIndex == static_cast<int>( uNumItems - 1 ) );
								}
								break;
								case ASURA_DPT_ONEBYONE:		// drop down
								case ASURA_DPT_RANDOM:			// drop down
								case ASURA_DPT_CDPLAYERRANDOM:
								{
									bShouldCopyMessage = true;
								}
								break;
							}
							if( bShouldCopyMessage )
							{
								Asura_StaticMessage_Handler xHandler = Asura_StaticMessage_System::GetCurrentHandler();
								const Asura_Dialogue_Message* pxMessage = m_pxEvent->GetMessageFromStoreByHash( m_pxEvent->GetFinalMessageID() );
								if( pxMessage )
								{
									if( m_xLines[iLineIndex].m_usEndMsgBlock == ASURA_STATICMESSAGE_EMPTYBLOCK )
									{
										// The line has no start messages, just copy the message block wholesale
										m_xLines[iLineIndex].m_usEndMsgBlock = Asura_StaticMessage_System::CopyMessageBlock( &pxMessage->m_xMessageBlock, eSet, ASURA_GUID_UNREGISTERED, GetGuid() );
									}
									else
									{
										// The line has end messages already, add each message individually
										for( u_int uMessage = 0; uMessage < pxMessage->m_xMessageBlock.GetNumberOfMessagesInBlock(); ++uMessage )
										{
											xHandler.AddMessage( eSet, *pxMessage->m_xMessageBlock.GetMessagePointer( uMessage), m_xLines[iLineIndex].m_usEndMsgBlock );
										}
									}
								}
							}
						}
					}
					
					++iLineIndex;
				}
			}
		}

		// Clear any unused messages from the event and update it
		if( bShouldCopyClearMsgs )
		{
			Asura_Dialogue_Event* pxEvent = Asura_Dialogue_Storage::GetEventByHash( m_uEventHash );

			if( pxEvent )
			{
				// Clear the 'initial' and 'final' blocks
				pxEvent->SetInitialMessageID	( ASURA_HASH_ID_UNSET );
				pxEvent->SetFinalMessageID		( ASURA_HASH_ID_UNSET );

				pxEvent->RemoveUnusedMessageBlocks();
			}
		}

		// Check our lines are valid
		ValidateLines();

		if( m_pxEvent->GetPlaybackType() == ASURA_DPT_CDPLAYERRANDOM )
		{
			// Rebuild our random list if we are CD random (this is not saved to a chunk)
			BuildCDRandomList();
		}
	}
}


bool Asura_ServerEntity_DialogueController::IsEveryLinePresent () const
{
	if( !m_pxEvent )
	{
		return true;
	}

	const u_int uNumItems = m_pxEvent->GetNumPlayables();
	for( u_int uItem = 0; uItem < uNumItems; ++uItem )
	{
		const Asura_Dialogue_Playable* pxPlayable = m_pxEvent->GetPlayable( uItem );
		if( pxPlayable && pxPlayable->IsLine() )
		{
			if( !ContainsLine( pxPlayable->GetHashID() ) )
			{
				return false;
			}
		}
	}
	return true;
}


bool Asura_ServerEntity_DialogueController::IsEveryLineValid ( ) const
{
	for( int iLine = 0; iLine < static_cast<int>( m_xLines.Size() ); ++iLine )
	{
		if( !m_xLines[iLine].m_bIsValid )
		{
			return false;
		}
	}
	return true;
}


bool Asura_ServerEntity_DialogueController::ContainsLine ( Asura_Hash_ID uLineHash ) const
{
	for( int iLine = 0; iLine < static_cast<int>( m_xLines.Size() ); ++iLine )
	{
		if( m_xLines[ iLine ].m_uDialogueLine == uLineHash )
		{
			return true;
		}
	}
	return false;
}

#endif // ASURA_TOOLS

#endif //ASURA_USE_DIALOGUE_SYSTEM
