//
// Tools_InGameCutsceneAVI.cpp
//
// Created by Richard Rice - 26/09/2005
// 
// Copyright (C) 2005 Rebellion, All Rights Reserved.
//


#include "Core/Asura.h"
#include "Cutscene/Tools_InGameCutsceneAVI.h"

#include "Core/Asura_UserInput.h"
#include "Cutscene/Asura_Cutscene.h"
#include "Cutscene/Asura_Cutscene_System.h"
#include "Debugging/Asura_Assert.h"
#include "Interface/Asura_CommandConsole.h"
#include "Interface/Asura_CommandConsole_IO.h"
#include "Rendering/Asura_Viewport.h"
#include "Rendering/Asura_Render.h"
#include "SpecialFX/Asura_FullScreenSFX_System.h"

#include "Misc/Tools_AVI_Writer.h"
#include "Misc/list_tem.hpp"

Tools_CutsceneAVIConfig* Tools_InGameCutsceneAVI::s_pxCurrentAVI = 0;
List<Tools_CutsceneAVIConfig*> Tools_InGameCutsceneAVI::s_xQueue;
bool Tools_InGameCutsceneAVI::s_bStartOfBatch = true;
Asura_Cutscene* Tools_InGameCutsceneAVI::s_pxCurrentCutscene = 0;

int Tools_InGameCutsceneAVI::s_iInitialVideoWidth = 0;
int Tools_InGameCutsceneAVI::s_iInitialVideoHeight = 0;
bool Tools_InGameCutsceneAVI::s_bInitialIgnoreAssert = false;


#define COMMAND_WRITECUTSCENEAVI	L"WriteCutsceneAVI"



void Tools_InGameCutsceneAVI::AddConsoleCommands()
{
	Asura_CommandConsole::AddCmd( COMMAND_WRITECUTSCENEAVI, ConsoleCmd_WriteCutsceneAVI);
}

void Tools_InGameCutsceneAVI::Update()
{

	//escape aborts current avi , and clears queue
	if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_ESCAPE))
	{
		if(s_pxCurrentAVI || s_xQueue.size())
		{
			Asura_CommandConsole::Printf( L"Aborted AVI");
			Abort();
		}
	}

	if(s_pxCurrentAVI)
	{
		Asura_Assert(s_pxCurrentCutscene,"");
		
		if(s_pxCurrentCutscene->Timer == s_pxCurrentCutscene->CutsceneEndTime)
		{
			//reached end of cutscene
			Asura_CommandConsole::Printf( L"Finished writing %S",s_pxCurrentAVI->m_xCutsceneName.GetString());
			EndAVI();
		}
		else if(!TheAviWriter.IsWritingFile())
		{
			//avi writer stopped of its own accord , probably not good news
			Asura_CommandConsole::Printf( L"Error writing AVI");
			EndAVI();
		}
		else
		{
			s_pxCurrentCutscene->Update();
		}

	}
	else if(s_xQueue.size())
	{
		PlayNextInQueue();
	}
}

void Tools_InGameCutsceneAVI::Render()
{
	if(s_pxCurrentCutscene) s_pxCurrentCutscene->Render();
}



void Tools_InGameCutsceneAVI::WriteFrame()
{
	TheAviWriter.WriteFrame();
}

bool Tools_InGameCutsceneAVI::IsWritingAVI()
{
	return s_pxCurrentAVI || s_xQueue.size();
}

void Tools_InGameCutsceneAVI::EndAVI()
{
	if(TheAviWriter.IsWritingFile())
	{
		TheAviWriter.EndAVI();
	}

	if(s_pxCurrentAVI && !s_xQueue.size())
	{
		//done everything in queue , so reset asserts
		AsuraAssert_SetIgnoreAll(s_bInitialIgnoreAssert);
		//and reset the start of batch flag , so compression options will be asked on next AVI
		s_bStartOfBatch = true;

		//restore original video mode
		if(s_iInitialVideoWidth && s_iInitialVideoHeight)
		{
			if(Asura_Viewport::GetWidth()!=s_iInitialVideoWidth || Asura_Viewport::GetHeight()!=s_iInitialVideoHeight)
			{
				Asura_Render::SetVideoMode(s_iInitialVideoWidth,s_iInitialVideoHeight);
			}
			s_iInitialVideoWidth = 0;
			s_iInitialVideoWidth = 0;
		}

	}

	delete s_pxCurrentAVI;
	s_pxCurrentAVI = 0;


	if(s_pxCurrentCutscene)
	{
		s_pxCurrentCutscene->Finish();
		s_pxCurrentCutscene = 0;
		Asura_Cutscene::DisableSubtitles(false);
		Asura_Cutscene::DisableSoundTrack(false);
		Asura_FullScreenSFX_System::CancelFade();//May as well clear up any fade , even if it would normally be left for this cutscene
	}
}

void Tools_InGameCutsceneAVI::PlayNextInQueue()
{
	Asura_Assert(s_pxCurrentAVI == 0,"");
	if(!s_xQueue.size()) return;

	//get the next queue entry
	s_pxCurrentAVI = s_xQueue.first_entry();
	s_xQueue.delete_first_entry();

	//If starting a batch , may need to change some settings
	//(needs to be done before any EndAVI() cases)
	if(s_bStartOfBatch)
	{
		//disable asserts while recording AVIs
		s_bInitialIgnoreAssert = AsuraAssert_GetIgnoreAll();
		AsuraAssert_SetIgnoreAll(true);

		//record the initial video mode
		s_iInitialVideoWidth = Asura_Viewport::GetWidth();
		s_iInitialVideoHeight = Asura_Viewport::GetHeight();
	}


	//make sure the cutscene is available
	s_pxCurrentCutscene = Asura_Cutscene_System::GetCutscene(s_pxCurrentAVI->m_xCutsceneName.GetString());
	if(!s_pxCurrentCutscene)
	{
		Asura_CommandConsole::Printf( L"Failed to find cutscene %S",s_pxCurrentAVI->m_xCutsceneName.GetString());
		EndAVI();
		return;
	}

	//start the cutscene
	Asura_Cutscene::DisableSubtitles(true);//don't want subtitles in AVI version
	Asura_Cutscene::DisableSoundTrack(true);
	Asura_FullScreenSFX_System::CancelFade();//Just in case a previous cutscene has left the screen black...
	s_pxCurrentCutscene->Initialise();


	int iVideoModeWidth = s_pxCurrentAVI->m_iVideoModeWidth;
	int iVideoModeHeight = s_pxCurrentAVI->m_iVideoModeHeight;

	if(!iVideoModeWidth || !iVideoModeHeight)
	{
		//use the original width/height if none specified
		iVideoModeWidth = s_iInitialVideoWidth;
		iVideoModeHeight = s_iInitialVideoHeight;
	}

	//change video mode if required
	if(Asura_Viewport::GetWidth()!=iVideoModeWidth || Asura_Viewport::GetHeight()!=iVideoModeHeight)
	{
		Asura_Render::SetVideoMode(iVideoModeWidth,iVideoModeHeight);
	}
	if(Asura_Viewport::GetWidth()!=iVideoModeWidth || Asura_Viewport::GetHeight()!=iVideoModeHeight)
	{
		Asura_CommandConsole::Printf( L"Failed to set video mode");
		EndAVI();
		return;
	}

	

	//determine the AVI width/height
	int iWidth,iHeight;
	if(s_pxCurrentAVI->m_iAVIWidth && s_pxCurrentAVI->m_iAVIHeight)
	{
		iWidth = s_pxCurrentAVI->m_iAVIWidth;
		iHeight = s_pxCurrentAVI->m_iAVIHeight;
	}
	else
	{
		iWidth = Asura_Viewport::GetWidth();
		iHeight = Asura_Viewport::GetHeight();

		if(s_pxCurrentAVI->m_bHalfSize)
		{
			iWidth /= 2;
			iHeight /= 2;
		}
	}
	
	//build the cutscene name
	char szFilename[MAX_PATH];

	bool bExtraFormatText = !s_pxCurrentAVI->m_xExtraFormatText.IsBlank();

	Asura_PrintfToBfr(szFilename,"Screenshots\\%s%s%s_%dfps_%d_%d.avi",MAX_PATH,true,
									s_pxCurrentAVI->m_xCutsceneName.GetString(),
									bExtraFormatText ? "_" : "",
									bExtraFormatText ? s_pxCurrentAVI->m_xExtraFormatText.GetString() : "",
									s_pxCurrentAVI->m_iFrameRate,
									iWidth,
									iHeight);

	s_pxCurrentAVI->m_xFilename = szFilename;

	//ensure screenshots directory exists
	CreateDirectory("Screenshots",0);


	//initialise AVI writing
	u_int uFlags = 0;
	if(!s_bStartOfBatch)
	{
		uFlags |= TOOLS_AVIFLAG_USEPREVIOUSCOMPRESSIONSETTINGS;
	}
	s_bStartOfBatch = false;

	if(s_pxCurrentAVI->m_bHalfSize)
	{
		uFlags |= TOOLS_AVIFLAG_HALFSIZE;
	}

	if(!TheAviWriter.StartAVI(szFilename,(float)s_pxCurrentAVI->m_iFrameRate,iWidth,iHeight,uFlags))
	{
		Asura_CommandConsole::Printf( L"Error writing AVI");
		EndAVI();
		return;
	}

	//update with frame time of zero for first frame
	s_pxCurrentCutscene->Update(0);
}


bool Tools_InGameCutsceneAVI::ConsoleCmd_WriteCutsceneAVI(const char* pszText,bool bInterrogate, bool bHelp)
{
	if(bHelp || bInterrogate)
	{
        Asura_CommandConsole::Printf( L"Records a cutscene as an AVI." );
        Asura_CommandConsole::Printf( L"Syntax: " COMMAND_WRITECUTSCENEAVI L" <CutsceneName> <Optional commands>" );
        Asura_CommandConsole::Printf( L"Cutscene name should be in double quotes if it contains spaces");
        Asura_CommandConsole::Printf( L"Optional commands :");
        Asura_CommandConsole::Printf( L"VideoMode = <WidthxHeight> : Sets the video mode to record in");
        Asura_CommandConsole::Printf( L"AVISize = <WidthxHeight> : Sets the size of the AVI. The central part of the screen will be taken.");
        Asura_CommandConsole::Printf( L"FrameRate = <FrameRate> : Sets the frame rate to record at.");
        Asura_CommandConsole::Printf( L"HalfSize : Halfs the size of the screen so that each avi pixel is the average of 4 from the screen.");
        Asura_CommandConsole::Printf( L"FormatText = <Text> : Some extra text to add to the filename.");
        Asura_CommandConsole::Printf( L"NTSC : Sets a whole load of default settings for NTSC AVI");
        Asura_CommandConsole::Printf( L"PAL : Sets a whole load of default settings for PAL AVI");
        Asura_CommandConsole::Printf( L"eg. VideoMode=1280x960 AviSize=640x288 FrameRate=25 HalfSize FormatText=pal");


	}
	else
	{
		Tools_CutsceneAVIConfig* pxAVIConfig = new Tools_CutsceneAVIConfig;

		if(!ParseCommand(pxAVIConfig,pszText))
		{
			return false;
		}
		s_xQueue.add_entry(pxAVIConfig);

	}
	return true;
}


void Tools_InGameCutsceneAVI::Abort()
{
	if(s_pxCurrentAVI || s_xQueue.size())
	{
		while(s_xQueue.size())
		{
			delete s_xQueue.first_entry();
			s_xQueue.delete_first_entry();
		}	
		
		EndAVI();
	}
}

bool Tools_InGameCutsceneAVI::ParseCommand(Tools_CutsceneAVIConfig* pxAVIConfig,const char* pszText)
{
	//set default values
	pxAVIConfig->m_iVideoModeWidth = 0;	
	pxAVIConfig->m_iVideoModeHeight = 0;	

	pxAVIConfig->m_iAVIWidth = 0;	
	pxAVIConfig->m_iAVIHeight = 0;	

	pxAVIConfig->m_bHalfSize = false;

	pxAVIConfig->m_iFrameRate = 25;

	//first string should be the cutscene name
	if(!GetNextString(pszText,&pxAVIConfig->m_xCutsceneName))
	{
		Asura_CommandConsole::Printf( L"Failed to get cutscene name");
		return false;
	}

	//confirm that the cutscene exists
	if(!Asura_Cutscene_System::GetCutscene(pxAVIConfig->m_xCutsceneName.GetString()))
	{
		Asura_CommandConsole::Printf( L"Failed to find cutscene %S",pxAVIConfig->m_xCutsceneName.GetString());
		return false;
	}

	//now search for any other instructions on the line
	Asura_String xCommand;
	while(GetNextString(pszText,&xCommand))
	{
		if(!xCommand.Compare("VideoMode"))
		{
			//next string should be width and height
			Asura_String xString;
			bool bOkay = GetNextString(pszText,&xString);
			if(bOkay) bOkay = (sscanf(xString.GetString(),"%dx%d",&pxAVIConfig->m_iVideoModeWidth,&pxAVIConfig->m_iVideoModeHeight) == 2);

			if(!bOkay)
			{
				Asura_CommandConsole::Printf( L"Failed to get width/height after VideoMode");
				return false;
			}
		}
		else if(!xCommand.Compare("AVISize"))
		{
			//next string should be avi width and height
			Asura_String xString;
			bool bOkay = GetNextString(pszText,&xString);
			if(bOkay) bOkay = (sscanf(xString.GetString(),"%dx%d",&pxAVIConfig->m_iAVIWidth,&pxAVIConfig->m_iAVIHeight) == 2);

			if(!bOkay)
			{
				Asura_CommandConsole::Printf( L"Failed to get width/height after AVISize");
				return false;
			}
		}
		else if(!xCommand.Compare("FrameRate"))
		{
			//next string should be the frame rate
			Asura_String xString;
			bool bOkay = GetNextString(pszText,&xString);
			if(bOkay) bOkay = (sscanf(xString.GetString(),"%d",&pxAVIConfig->m_iFrameRate) == 1);

			if(!bOkay)
			{
				Asura_CommandConsole::Printf( L"Failed to get frame rate after FrameRate");
				return false;
			}
		}
		else if(!xCommand.Compare("HalfSize"))
		{
			pxAVIConfig->m_bHalfSize = true;
		}
		else if(!xCommand.Compare("FormatText"))
		{
			if(!GetNextString(pszText,&pxAVIConfig->m_xExtraFormatText))
			{
				Asura_CommandConsole::Printf( L"Failed to get string after FormatText");
			}
		}
		else if(!xCommand.Compare("NTSC"))
		{
			pxAVIConfig->m_iVideoModeWidth = 1280;	
			pxAVIConfig->m_iVideoModeHeight = 960;	
			pxAVIConfig->m_iAVIWidth = 640;	
			pxAVIConfig->m_iAVIHeight = 256;	
			pxAVIConfig->m_bHalfSize = true;
			pxAVIConfig->m_iFrameRate = 30;
			pxAVIConfig->m_xExtraFormatText = "ntsc";
		
		}
		else if(!xCommand.Compare("PAL"))
		{
			pxAVIConfig->m_iVideoModeWidth = 1280;	
			pxAVIConfig->m_iVideoModeHeight = 960;	
			pxAVIConfig->m_iAVIWidth = 640;	
			pxAVIConfig->m_iAVIHeight = 288;	
			pxAVIConfig->m_bHalfSize = true;
			pxAVIConfig->m_iFrameRate = 25;
			pxAVIConfig->m_xExtraFormatText = "pal";
		}
		else
		{
			//unknown command
			Asura_CommandConsole::Printf( L"Unrecognised option '%S'",xCommand.GetString());
			return false;
		}
	}
	return true;
}

bool Tools_InGameCutsceneAVI::GetNextString(const char*& pszText,Asura_String* pxOutput)
{
	//search for the start
	while(*pszText==' ' || *pszText=='=') pszText++;
	if(!*pszText) return false;

	//in quotes?
	bool bQuoted = false;
	if(*pszText == '\"')
	{
		bQuoted = true;
		pszText++;
		if(!*pszText) return false;
	}
	const char* pszStart = pszText;
	const char* pszEnd = 0;

	//search for the end of the string
	while(*pszText)
	{
		if(bQuoted)
		{
			if(*pszText == '\"')
			{
				//end of quotes
				pszEnd = pszText;
				pszText++;
				break;
			}
		}
		else if(*pszText == ' ' || *pszText=='=')
		{
			//if not in quotes , then stop at a space or an equals
			break;
		}
		pszText++;
	}
	if(!pszEnd) pszEnd = pszText;

	//somewhat wasteful way of copying just part of the string , but the what the heck
	*pxOutput = pszStart;
	pxOutput->Truncate(pszEnd-pszStart);

	return true;
}
