//
// Tools_Dialogue_Export.cpp
//
// Created by Richard Rice - 11/08/2006
// 
// Copyright (C) 2006 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"
#ifdef ASURA_USE_DIALOGUE_SYSTEM

//asura
#include "ChunkSystem/Asura_SaveBlock.h"
#include "ChunkSystem/Asura_Chunk_Dialogue.h"
#include "ChunkSystem/Asura_chunk_lipsync.h"
#include "Core/Asura_HashTree.h"
#include "Sound/Asura_Audio_System.h"
#include "Sound/Asura_Dialogue.h"
#include "Sound/Asura_Sound.h"
#include "Sound/Asura_Sound_Event.h"

//tools
#if defined(ASURA_USE_FACIAL_ANIMATION_SYSTEM)
#include "FacialAnimation/Tools_FacialAnimation_System.h"
#endif	//defined(ASURA_USE_FACIAL_ANIMATION_SYSTEM)
#include "Load&Save/LevelExport/Tools_Level_Export.h"
#include "Load&Save/Tools_ResourceExportLog.h"
//#include "Misc/Tools_Modal_Overlay.h"
#include "PC/FileSystem/Tools_FileSystem.h"
#include "Sound/Tools_Dialogue_System.h"
#include "Sound/Tools_Sound_System.h"
#include "Sound/Tools_SoundEvent_System.h"



void Tools_Level_Export::AddGlobalDialogueEventsToExportList()
{
	if(Asura_Dialogue_Storage::GetNumberOfEvents())
	{
		//Check the events in an order that should improve the chances of getting a balanced tree on reload. RAR 17/08/2006
		for(Asura_TemplateHashTree_SaveIterator xIt(Asura_Dialogue_Storage::GetNumberOfEvents());!xIt.Done();xIt.Next())
		{
			const Asura_Dialogue_Event* pxEvent = Asura_Dialogue_Storage::GetEventByIndex(xIt.GetCurrent());
			if(pxEvent->GetIsGlobal() && !Tools_Dialogue_System::IsLevelSpecific(pxEvent))
			{
				AddDialogueEventToExportList(pxEvent->GetHashID());
			}
		}
	}
}

void Tools_Level_Export::ExcludeGlobalDialogueEvents()
{
	List<const Asura_Dialogue_Event*> xEventList;
	List<Asura_Dialogue_Line*> xLineList;

	//search for all dialogue events that have been flagged as global
	for(u_int i=0;i<Asura_Dialogue_Storage::GetNumberOfEvents();i++)
	{
		const Asura_Dialogue_Event* pxEvent = Asura_Dialogue_Storage::GetEventByIndex(i);
		if(pxEvent->GetIsGlobal() && !Tools_Dialogue_System::IsLevelSpecific(pxEvent))
		{
			xEventList.add_entry(pxEvent);
		}	
	}

	//go through these events and exclude them ,also get a list of lines used by them
	for(LIF<const Asura_Dialogue_Event*> xEventLIF(&xEventList);!xEventLIF.done();xEventLIF.next())
	{
		const Asura_Dialogue_Event* pxEvent = xEventLIF();
		
		//remove from list of events to export
		if(m_xDialogueEventList.contains(pxEvent)) m_xDialogueEventList.delete_entry(pxEvent);
		
		//mark as an event that has been dealt with to prevent subsequent inclusion
		int iJunkIndex;
		m_pxDialogueEventTree->FindNodeIndexByHash(pxEvent->GetHashID(),true,iJunkIndex);

		//find any events/lines that it uses
		for(u_int i=0;i<pxEvent->GetNumPlayables();i++)
		{
			const Asura_Dialogue_Playable* pxPlayable = pxEvent->GetPlayable(i);
			if(pxPlayable->IsLine())
			{
				Asura_Dialogue_Line* pxChildLine = (Asura_Dialogue_Line*) pxPlayable;
				if(!xLineList.contains(pxChildLine)) xLineList.add_entry(pxChildLine);
			}
			else
			{
				const Asura_Dialogue_Event* pxChildEvent = (const Asura_Dialogue_Event*) pxPlayable;
				if(!xEventList.contains(pxChildEvent)) xEventList.add_entry(pxChildEvent);
			}
		}
	}

	//exclude the listed lines ,and the wav files that they use
	for(LIF<Asura_Dialogue_Line*> xLineLIF(&xLineList);!xLineLIF.done();xLineLIF.next())
	{
		Asura_Dialogue_Line* pxLine = xLineLIF();

		//remove from list of line to export
		if(m_xDialogueLineList.contains(pxLine)) m_xDialogueLineList.delete_entry(pxLine);
		
		//mark as a line that has been dealt with to prevent subsequent inclusion
		int iJunkIndex;
		m_pxDialogueLineTree->FindNodeIndexByHash(pxLine->GetHashID(),true,iJunkIndex);

		//exclude the line template as well
		const Asura_Dialogue_Line_Template* pxTemplate = pxLine->GetTemplate();
		if(pxTemplate)
		{
			if(m_xDialogueLineTemplateList.contains(pxTemplate)) m_xDialogueLineTemplateList.delete_entry(pxTemplate);

			Asura_Hash_ID uTemplateHash = Asura_GetHashID(pxTemplate->m_xName.GetString());
			m_pxDialogueLineTemplateTree->FindNodeIndexByHash(uTemplateHash,true,iJunkIndex);
		}

		//get the wav file used and exclude that as well
		if(s_eExportType == Export_PSP)
		{
			//check for the 'no wav for psp' flag
			const Asura_Dialogue_Line_Template* pxTemplate = xLineLIF()->GetTemplate();
			if(pxTemplate && pxTemplate->m_bNoWavForPSP) continue;
		}

		const char* szFilename = xLineLIF()->GetSoundFilename();
		if(!szFilename || !*szFilename) continue;

		//This will result in files that don't exist being added to the ignore list , but this is probably faster than
		//checking for the existence in each case.
		//RAR 06/06/2007
		#ifdef ASURA_USE_AUDIO_SYSTEM
		const int iSoundID = 0;
		#else
		const int iSoundID = ASURA_SID_DYNAMIC;
		#endif
		AddSoundToIgnore(iSoundID,szFilename);		
	}
}

void Tools_Level_Export::AddDialogueEventToExportList(Asura_Hash_ID uEventHash)
{
	Asura_Assert(This,"");
	if(!uEventHash) return;

	Asura_Assert(This->m_pxDialogueEventTree,"Not in the middle of a file export?");

	//event already dealt with?
	int iJunkIndex;
	if(This->m_pxDialogueEventTree->FindNodeIndexByHash(uEventHash,true,iJunkIndex)) return;

	//does the event exist?
	const Asura_Dialogue_Event* pxEvent = Asura_Dialogue_Storage::FindEvent(uEventHash);
	if(!pxEvent)
	{
		Tools_Level_Export::AddMessage(Tools_Level_Export::MT_ERROR, "Failed to find dialogue event 0x%x",uEventHash);
		return;
	}

	TOOLS_RESOURCEEXPORTLOG("Dialogue Event",pxEvent->GetName(),true);

	//add any events/lines this depends on first
	for(u_int i=0;i<pxEvent->GetNumPlayableID();i++)
	{
		const Asura_Dialogue_PlayableIDListItem& xItem = pxEvent->GetPlayableID(i);
		if(xItem.m_xID.m_eType == ASURA_DIALOGUE_PLAYABLE_LINE)
		{
			AddDialogueLineToExportList(xItem.m_xID.m_uHashID);
		}
		else
		{
			Asura_Assert(xItem.m_xID.m_eType == ASURA_DIALOGUE_PLAYABLE_EVENT,"");
			AddDialogueEventToExportList(xItem.m_xID.m_uHashID);
		}
	}

	//and add this event to the list
	This->m_xDialogueEventList.add_entry(pxEvent);

}

void Tools_Level_Export::AddDialogueEventToExportList(const Asura_Dialogue_Event* pxEvent)
{
	if(!pxEvent)
	{
		Tools_Level_Export::AddMessage(Tools_Level_Export::MT_ERROR, "Dialogue event is NULL on export");
		return;
	}

	AddDialogueEventToExportList(pxEvent->GetHashID());

}

void Tools_Level_Export::AddDialogueLineToExportList(Asura_Hash_ID uLineHash)
{
	Asura_Assert(This,"");
	if(!uLineHash) return;

	Asura_Assert(This->m_pxDialogueLineTree,"Not in the middle of a file export?");

	//line already dealt with?
	int iJunkIndex;
	if(This->m_pxDialogueLineTree->FindNodeIndexByHash(uLineHash,true,iJunkIndex)) return;

	//does the line exist?
	Asura_Dialogue_Line* pxLine = Asura_Dialogue_Storage::GetLineByHash(uLineHash);
	if(!pxLine)
	{
		Tools_Level_Export::AddMessage(Tools_Level_Export::MT_ERROR, "Failed to find dialogue line 0x%x",uLineHash);
		return;
	}

	TOOLS_RESOURCEEXPORTLOG("Dialogue Line",pxLine->GetName(),true);

	//add this line to the list
	This->m_xDialogueLineList.add_entry(pxLine);

	//add its template
	if(pxLine->GetTemplate())
	{
		AddDialogueLineTemplateToExportList(pxLine->GetTemplate());
	}

#if defined(ASURA_USE_FACIAL_ANIMATION_SYSTEM)
	// Add any facial-animation coming from dialogue lines...
	if(pxLine->HasFaceType())
	{
		const char*			szSoundFilename(pxLine->GetSoundFilename());
		const Asura_String	xFaceAnimationName(Tools_FacialAnimation_System::ConvertSoundFilenameToFaceAnimationName(szSoundFilename));

		const Tools_FacialAnimation_FaceAnimation*	pxToolsFaceAnimation	= Tools_FacialAnimation_System::FindOrLoadAnimation(xFaceAnimationName);
		
		if(pxToolsFaceAnimation != NULL)
		{
			const char*			szFaceAnimationName	= xFaceAnimationName.GetString();
			const Asura_Hash_ID	uFaceAnimationHash	= xFaceAnimationName.GetHash();

			if(AddLocalisedFaceAnimationToExportList(szFaceAnimationName, uFaceAnimationHash))
			{
				pxLine->SetFaceAnimationHash(uFaceAnimationHash);
			}
			else
			{
				pxLine->SetFaceAnimationHash(ASURA_HASH_ID_UNSET);
			}
		}
	}
#endif	//defined(ASURA_USE_FACIAL_ANIMATION_SYSTEM)
}

void Tools_Level_Export::AddDialogueLineTemplateToExportList(const Asura_Dialogue_Line_Template* pxTemplate)
{
	Asura_Assert(This->m_pxDialogueLineTemplateTree,"Not in the middle of a file export?");

	//template already dealt with?
	int iJunkIndex;
	if(This->m_pxDialogueLineTemplateTree->FindNodeIndexByHash(pxTemplate->m_uHashID,true,iJunkIndex)) return;

	TOOLS_RESOURCEEXPORTLOG("Dialogue Line Template",pxTemplate->m_xName.GetString(),true);

	//add this template to the list
	This->m_xDialogueLineTemplateList.add_entry(pxTemplate);

}

bool Tools_Level_Export::AddDialogueEvents(HANDLE pSaveHandle)
{
	if(m_xDialogueLineList.size())
	{
		//If there are any dialogue lines , then we'll need to create localised files
		SetCreateLanguageFiles(pSaveHandle);
	}
	
	//Add all wav files used by the lines
//	UpdateStatus(Tools_Level_Export::EXPORT_STAGE_DIALOGUE_EVENT_SOUNDS, "Dialogue Event Sounds", 0);

	for(LIF<Asura_Dialogue_Line*> xLineLIF(&m_xDialogueLineList);!xLineLIF.done() && !IsAbortFlagSet() ;xLineLIF.next())
	{
//		UpdateStatus(Tools_Level_Export::EXPORT_STAGE_DIALOGUE_EVENT_SOUNDS, "Dialogue Event Sounds", iCount*100/m_xDialogueLineList.size());

		#if defined ASURA_USE_AUDIO_SYSTEM
			// The wave stored in the dialogue line might not actually exist, because it's
			// initialised based on the line name, but the line might be text only. Need to check
			// for that here, since under the old sound system it's sorted out by GetSoundID()

			const char* const szFilename = xLineLIF()->GetSoundFilename();
			if(!Tools_FileSystem::FileExists_CheckNetwork(szFilename)) continue;
		#else
			Asura_Sound_ID eSoundID = (Asura_Sound_ID) xLineLIF()->GetSoundID();
			if(eSoundID == ASURA_SID_NO_SOUND) continue;

			const char* szFilename = Asura_Sound_System::GetFileNameFromSoundID(eSoundID);
		#endif

		if(!szFilename)
		{
			Asura_Assertf(0,("Failed to get sound filename for dialogue event '%s'",xLineLIF()->GetName()));
			continue;
		}

		//Update the sound length stored in the dialogue line
		//Avoiding using Asura_Sound_System::GetSoundLength() since that won't work if sounds aren't being loaded.
		//RAR 01/03/2007
		xLineLIF()->SetSoundLength(Tools_Sound_System::GetSoundLength(szFilename));


		const Asura_Dialogue_Line_Template* pxTemplate = xLineLIF()->GetTemplate();
		if (!GetExportDialogueLineSound(pxTemplate))
		{
			continue;
		}

		TOOLS_RESOURCEEXPORTLOG("Dialogue Line",xLineLIF()->GetName(),false);


		SoundExportData xSound;
		#ifdef ASURA_USE_AUDIO_SYSTEM
		xSound.m_iID = 0;
		#else
		xSound.m_iID = ASURA_SID_DYNAMIC;
		#endif
		xSound.m_xFilename = szFilename;
		xSound.m_bOnlyUsedByHash = true;
		xSound.m_bLocalise = true;
		xSound.m_bStreamed = pxTemplate ? pxTemplate->m_bStreamed : true;
		if( Tools_SoundEvent_System::DoesPlatformUseSampleRate( s_eExportType ) )
		{
			xSound.m_iFrequency = Tools_Dialogue_System::GetSampleRate(s_eExportType,xLineLIF());
		}
		else if( Tools_SoundEvent_System::DoesPlatformUseCompressionQuality( s_eExportType ) )
		{
			if( s_eExportType == Tools_Level_Export::Export_PS3 )
			{
				xSound.m_iFrequency = pxTemplate ? pxTemplate->m_usCompressionQualityPS3 : TSS_QUALITY_MEDIUM;
			}
			else if( s_eExportType == Tools_Level_Export::Export_Xbox360 )
			{
				xSound.m_iFrequency = pxTemplate ? pxTemplate->m_usCompressionQuality360 : TSS_QUALITY_MEDIUM;
			}
			else
			{
				Asura_Assert( false, "Failed to find compression quality setting in dialogue line template" );
				xSound.m_iFrequency = TSS_QUALITY_MEDIUM;
			}
		}
		AddSound(pSaveHandle,&xSound);
	}

	//	UpdateStatus(Tools_Level_Export::EXPORT_STAGE_END, 0, 0);

	if(IsAbortFlagSet()) return false;

	if(pSaveHandle != INVALID_HANDLE_VALUE)
	{
		//Fill in a save block containing event chunks
		Asura_SaveBlock xSaveBlock;

		for(LIF<const Asura_Dialogue_Event*> xEventLIF(&m_xDialogueEventList);!xEventLIF.done();xEventLIF.next())
		{
			Asura_Chunk_Dialogue_Event::Create(xEventLIF(),&xSaveBlock);
		}

		//write the chunks to the file
		u_int uDataSize = xSaveBlock.GetSize() - ASURA_FILE_ID_NUM_CHARS;

		DWORD uBytesWritten;
		WriteFile( pSaveHandle, xSaveBlock.GetStartPtr() + ASURA_FILE_ID_NUM_CHARS, uDataSize, &uBytesWritten, NULL );

		if(uBytesWritten != uDataSize)
		{
			AddMessage( MT_ERROR, "I/O error." );
			return false;
		}
	}
	return true;
}


bool Tools_Level_Export::AddLocalisedDialogueEvents(HANDLE pSaveHandle, int iLanguage)
{
	if(m_xDialogueLineList.size())
	{
		//If there are any dialogue lines , then we'll need to create localised files
		m_bCreateLanguageFiles = true;
	}
	

	//Fill in a save block containing the template and dialogue line chunks
	//The templates aren't actually localised , but are being put in each language file to make sure
	//they get loaded before the lines.
	Asura_SaveBlock xSaveBlock;

	for(LIF<const Asura_Dialogue_Line_Template*> xTemplateLIF(&m_xDialogueLineTemplateList);!xTemplateLIF.done();xTemplateLIF.next())
	{
		Asura_Chunk_Dialogue_Line_Template::Create(xTemplateLIF(),&xSaveBlock);
	}

	for(LIF<Asura_Dialogue_Line*> xLineLIF(&m_xDialogueLineList);!xLineLIF.done();xLineLIF.next())
	{ 

#ifdef ASURA_USE_ARTICULATION_SYSTEM
		// Current must go first so articulation loads and gets added to the system before the line does.
		Asura_Dialogue_Line* pLine = xLineLIF();
		if(pLine->HasArticulation())
		{
			// Do a frame lookup to force a "load on demand" if the data isn't already loaded.
			u_int uLastFrame = 0;

			Asura_LipSync_Manager::GetArticulationByHash(pLine->GetHashID());

			const Asura_LipSync_Articulation_FrameData* const pxFrame = pLine->GetArticulation()->GetFrame(0 , uLastFrame);
			if (!pxFrame)
			{
				const char* const szName = pLine->GetSoundFilename();
				Asura_Assertf(pxFrame, ("No lipsync articulation frame data for %s", (szName ? szName : "<unknown>")));
			}

			Asura_Chunk_SaveBlock_Stream xStream(&xSaveBlock);
			Asura_Chunk_LipSync::Create( xStream, pLine->GetArticulation(), iLanguage );
		}
#endif

		Asura_Chunk_Dialogue_Line::Create(xLineLIF(), iLanguage, &xSaveBlock);
	}

	//write the chunks to the file
    u_int uDataSize = xSaveBlock.GetSize() - ASURA_FILE_ID_NUM_CHARS;

    DWORD uBytesWritten;
	WriteFile( pSaveHandle, xSaveBlock.GetStartPtr() + ASURA_FILE_ID_NUM_CHARS, uDataSize, &uBytesWritten, NULL );

	if(uBytesWritten != uDataSize)
	{
        AddMessage( MT_ERROR, "I/O error." );
		return false;
	}
	return true;
}

bool Tools_Level_Export::GetExportDialogueLineSound(const Asura_Dialogue_Line_Template* pxTemplate) const
{
	if(s_eExportType == Export_PSP)
	{
		//check for the 'no wav for psp' flag
		if(pxTemplate && pxTemplate->m_bNoWavForPSP) return false;
	}

	return true;
}

#endif //ASURA_USE_DIALOGUE_SYSTEM
