/*
===========================================================================
Copyright (C) 2000 - 2013, Raven Software, Inc.
Copyright (C) 2001 - 2013, Activision, Inc.
Copyright (C) 2013 - 2015, OpenJK contributors

This file is part of the OpenJK source code.

OpenJK is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/

// IcarusImplementation.cpp

#include <memory>
#include "StdAfx.h"
#include "IcarusInterface.h"
#include "IcarusImplementation.h"

#include "blockstream.h"
#include "sequence.h"
#include "taskmanager.h"
#include "sequencer.h"

#include "../qcommon/q_shared.h"
#include "../qcommon/qcommon.h"
#include "qcommon/ojk_saved_game_helper.h"

#define STL_ITERATE( a, b )		for ( a = b.begin(); a != b.end(); ++a )
#define STL_INSERT( a, b )		a.insert( a.end(), b );



//////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// required implementation of CIcarusInterface

IIcarusInterface* IIcarusInterface::GetIcarus(int flavor,bool constructIfNecessary)
{
	if(!CIcarus::s_instances && constructIfNecessary)
	{
		CIcarus::s_flavorsAvailable = IGameInterface::s_IcarusFlavorsNeeded;
		if (!CIcarus::s_flavorsAvailable)
		{
			return NULL;
		}
		CIcarus::s_instances = new CIcarus*[CIcarus::s_flavorsAvailable];
		for (int index = 0; index < CIcarus::s_flavorsAvailable; index++)
		{
			CIcarus::s_instances[index] = new CIcarus(index);
			//OutputDebugString( "ICARUS flavor successfully created\n" );
		}
	}

	if(flavor >= CIcarus::s_flavorsAvailable || !CIcarus::s_instances )
	{
		return NULL;
	}
	return CIcarus::s_instances[flavor];
}

void IIcarusInterface::DestroyIcarus()
{
	for(int index = 0; index < CIcarus::s_flavorsAvailable; index++)
	{
		delete CIcarus::s_instances[index];
	}
	delete[] CIcarus::s_instances;
	CIcarus::s_instances = NULL;
	CIcarus::s_flavorsAvailable = 0;
}

IIcarusInterface::~IIcarusInterface()
{
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// CIcarus

double CIcarus::ICARUS_VERSION = 1.40;

int CIcarus::s_flavorsAvailable = 0;

CIcarus** CIcarus::s_instances = NULL;

CIcarus::CIcarus(int flavor) :
	m_flavor(flavor), m_nextSequencerID(0)
{

	m_GUID = 0;

#ifdef _DEBUG

	m_DEBUG_NumSequencerAlloc		= 0;
	m_DEBUG_NumSequencerFreed		= 0;
	m_DEBUG_NumSequencerResidual	= 0;

	m_DEBUG_NumSequenceAlloc	= 0;
	m_DEBUG_NumSequenceFreed	= 0;
	m_DEBUG_NumSequenceResidual	= 0;

#endif

	m_ulBufferCurPos = 0;
	m_ulBytesRead = 0;
	m_byBuffer = NULL;
}

CIcarus::~CIcarus()
{
	Delete();
}

void CIcarus::Delete( void )
{

	Free();

#ifdef _DEBUG

	Com_Printf( "ICARUS Instance Debug Info:\n" );
	Com_Printf( "---------------------------\n" );
	Com_Printf( "Sequencers Allocated:\t%d\n", m_DEBUG_NumSequencerAlloc );
	Com_Printf( "Sequencers Freed:\t\t%d\n", m_DEBUG_NumSequencerFreed );
	Com_Printf( "Sequencers Residual:\t%d\n\n", m_DEBUG_NumSequencerResidual );
	Com_Printf( "Sequences Allocated:\t%d\n", m_DEBUG_NumSequenceAlloc );
	Com_Printf( "Sequences Freed:\t\t%d\n", m_DEBUG_NumSequenceFreed );
	Com_Printf( "Sequences Residual:\t\t%d\n\n", m_DEBUG_NumSequenceResidual );

#endif
}

void CIcarus::Signal( const char *identifier )
{
	m_signals[ identifier ] = 1;
}

bool CIcarus::CheckSignal( const char *identifier )
{
	signal_m::iterator	smi;

	smi = m_signals.find( identifier );

	if ( smi == m_signals.end() )
		return false;

	return true;
}

void CIcarus::ClearSignal( const char *identifier )
{
	m_signals.erase( identifier );
}

void CIcarus::Free( void )
{
	sequencer_l::iterator	sri;

	//Delete any residual sequencers
	STL_ITERATE( sri, m_sequencers )
	{
		(*sri)->Free(this);

#ifdef _DEBUG

		m_DEBUG_NumSequencerResidual++;

#endif

	}

	m_sequencers.clear();
	m_signals.clear();

	sequence_l::iterator	si;

	//Delete any residual sequences
	STL_ITERATE( si, m_sequences )
	{
		(*si)->Delete(this);
		delete (*si);

#ifdef _DEBUG

		m_DEBUG_NumSequenceResidual++;

#endif

	}

	m_sequences.clear();

	m_sequencerMap.clear();
}

int CIcarus::GetIcarusID( int gameID )
{
	CSequencer		*sequencer = CSequencer::Create();
	CTaskManager	*taskManager = CTaskManager::Create();

	sequencer->Init( gameID, taskManager );

	taskManager->Init( sequencer );

	STL_INSERT( m_sequencers, sequencer );

	m_sequencerMap[sequencer->GetID()] = sequencer;

#ifdef _DEBUG

	m_DEBUG_NumSequencerAlloc++;

#endif

	return sequencer->GetID();
}

void CIcarus::DeleteIcarusID( int& icarusID )
{
	CSequencer* sequencer = FindSequencer(icarusID);
	if(!sequencer)
	{
		icarusID = -1;
		return;
	}

	CTaskManager	*taskManager = sequencer->GetTaskManager();
	if (taskManager->IsResident())
	{
		IGameInterface::GetGame()->DebugPrint( IGameInterface::WL_ERROR, "Refusing DeleteIcarusID(%d) because it is running!\n", icarusID);
		assert(0);
		return;
	}

	m_sequencerMap.erase(icarusID);

	// added 2/12/2 to properly delete blocks that were passed to the task manager
	sequencer->Recall(this);


	if ( taskManager )
	{
		taskManager->Free();
		delete taskManager;
	}

	m_sequencers.remove( sequencer );

	sequencer->Free(this);

#ifdef _DEBUG

	m_DEBUG_NumSequencerFreed++;

#endif
	icarusID = -1;
}

CSequence *CIcarus::GetSequence( void )
{
	CSequence	*sequence = CSequence::Create();

	//Assign the GUID
	sequence->SetID( m_GUID++ );

	STL_INSERT( m_sequences, sequence );

#ifdef _DEBUG

	m_DEBUG_NumSequenceAlloc++;

#endif

	return sequence;
}

CSequence *CIcarus::GetSequence( int id )
{
	sequence_l::iterator	si;
	STL_ITERATE( si, m_sequences )
	{
		if ( (*si)->GetID() == id )
			return (*si);
	}

	return NULL;
}

void CIcarus::DeleteSequence( CSequence *sequence )
{
	m_sequences.remove( sequence );

	sequence->Delete(this);
	delete sequence;

#ifdef _DEBUG

	m_DEBUG_NumSequenceFreed++;

#endif
}

int CIcarus::AllocateSequences( int numSequences, int *idTable )
{
	CSequence	*sequence;

	for ( int i = 0; i < numSequences; i++ )
	{
		//If the GUID of this sequence is higher than the current, take this a the "current" GUID
		if ( idTable[i] > m_GUID )
			m_GUID = idTable[i];

		//Allocate the container sequence
		if ( ( sequence = GetSequence() ) == NULL )
			return false;

		//Override the given GUID with the real one
		sequence->SetID( idTable[i] );
	}

	return true;
}

void CIcarus::Precache(char* buffer, long length)
{
	IGameInterface* game = IGameInterface::GetGame(m_flavor);
	CBlockStream	stream;
	CBlockMember	*blockMember;
	CBlock			block;

	if ( stream.Open( buffer, length ) == 0 )
		return;

	const char	*sVal1, *sVal2;

	//Now iterate through all blocks of the script, searching for keywords
	while ( stream.BlockAvailable() )
	{
		//Get a block
		if ( stream.ReadBlock( &block, this ) == 0 )
			return;

		//Determine what type of block this is
		switch( block.GetBlockID() )
		{
		case ID_CAMERA:	// to cache ROFF files
			{
				float f = *(float *) block.GetMemberData( 0 );

				if (f == TYPE_PATH)
				{
					sVal1 = (const char *) block.GetMemberData( 1 );

					game->PrecacheRoff(sVal1);
				}
			}
			break;

		case ID_PLAY:	// to cache ROFF files

			sVal1 = (const char *) block.GetMemberData( 0 );

			if (!Q_stricmp(sVal1,"PLAY_ROFF"))
			{
				sVal1 = (const char *) block.GetMemberData( 1 );

				game->PrecacheRoff(sVal1);
			}
			break;

		//Run commands
		case ID_RUN:
			sVal1 = (const char *) block.GetMemberData( 0 );
			game->PrecacheScript( sVal1 );
			break;

		case ID_SOUND:
			sVal1 = (const char *) block.GetMemberData( 1 );	//0 is channel, 1 is filename
			game->PrecacheSound(sVal1);
			break;

		case ID_SET:
			blockMember = block.GetMember( 0 );

			//NOTENOTE: This will not catch special case get() inlines! (There's not really a good way to do that)

			//Make sure we're testing against strings
			if ( blockMember->GetID() == TK_STRING )
			{
				sVal1 = (const char *) block.GetMemberData( 0 );
				sVal2 = (const char *) block.GetMemberData( 1 );

				game->PrecacheFromSet( sVal1 , sVal2);
			}
			break;

		default:
			break;
		}

		//Clean out the block for the next pass
		block.Free(this);
	}

	//All done
	stream.Free();
}

CSequencer* CIcarus::FindSequencer(int sequencerID)
{
	sequencer_m::iterator mi = m_sequencerMap.find( sequencerID );

	if ( mi == m_sequencerMap.end() )
		return NULL;

	return (*mi).second;
}

int CIcarus::Run(int icarusID, char* buffer, long length)
{
	CSequencer* sequencer = FindSequencer(icarusID);
	if(sequencer)
	{
		return sequencer->Run(buffer, length, this);
	}
	return ICARUS_INVALID;
}

int CIcarus::SaveSequenceIDTable()
{
	//Save out the number of sequences to follow
	int		numSequences = m_sequences.size();

	BufferWrite( &numSequences, sizeof( numSequences ) );

	//Sequences are saved first, by ID and information
	sequence_l::iterator	sqi;

	//First pass, save all sequences ID for reconstruction
	int	*idTable = new int[ numSequences ];
	int	itr = 0;

	if ( idTable == NULL )
		return false;

	STL_ITERATE( sqi, m_sequences )
	{
		idTable[itr++] = (*sqi)->GetID();
	}

	//game->WriteSaveData( INT_ID('S','Q','T','B'), idTable, sizeof( int ) * numSequences );
	BufferWrite( idTable, sizeof( int ) * numSequences );

	delete[] idTable;

	return true;
}

int CIcarus::SaveSequences()
{
	//Save out a listing of all the used sequences by ID
	SaveSequenceIDTable();

	//Save all the information in order
	sequence_l::iterator	sqi;
	STL_ITERATE( sqi, m_sequences )
	{
		(*sqi)->Save();
	}

	return true;
}

int CIcarus::SaveSequencers()
{
	//Save out the number of sequences to follow
	int		numSequencers = m_sequencers.size();
	BufferWrite( &numSequencers, sizeof( numSequencers ) );

	//The sequencers are then saved
	int sequencessaved = 0;
	sequencer_l::iterator	si;
	STL_ITERATE( si, m_sequencers )
	{
		(*si)->Save();
		sequencessaved++;
	}

	assert( sequencessaved == numSequencers );

	return true;
}

int CIcarus::SaveSignals()
{
	int	numSignals = m_signals.size();

	//game->WriteSaveData( INT_ID('I','S','I','G'), &numSignals, sizeof( numSignals ) );
	BufferWrite( &numSignals, sizeof( numSignals ) );

	signal_m::iterator	si;
	STL_ITERATE( si, m_signals )
	{
		//game->WriteSaveData( INT_ID('I','S','I','G'), &numSignals, sizeof( numSignals ) );
		const char *name = ((*si).first).c_str();

		int length = strlen( name ) + 1;

		//Save out the string size
		BufferWrite( &length, sizeof( length ) );

		//Write out the string
		BufferWrite( (void *) name, length );
	}

	return true;
}

// Get the current Game flavor.
int CIcarus::GetFlavor()
{
	return m_flavor;
}

int CIcarus::Save()
{
	// Allocate the temporary buffer.
	CreateBuffer();

	IGameInterface* game = IGameInterface::GetGame(m_flavor);

	ojk::SavedGameHelper saved_game(
		game->get_saved_game_file());

	//Save out a ICARUS save block header with the ICARUS version
	double	version = ICARUS_VERSION;

	saved_game.write_chunk<double>(
		INT_ID('I', 'C', 'A', 'R'),
		version);

	//Save out the signals
	if ( SaveSignals() == false )
	{
		DestroyBuffer();
		return false;
	}

	//Save out the sequences
	if ( SaveSequences() == false )
	{
		DestroyBuffer();
		return false;
	}

	//Save out the sequencers
	if ( SaveSequencers() == false )
	{
		DestroyBuffer();
		return false;
	}

	// Write out the buffer with all our collected data.
	saved_game.write_chunk(
		INT_ID('I', 'S', 'E', 'Q'),
		m_byBuffer,
		static_cast<int>(m_ulBufferCurPos));

	// De-allocate the temporary buffer.
	DestroyBuffer();

	return true;
}

int CIcarus::LoadSignals()
{
	int numSignals;

	BufferRead( &numSignals, sizeof( numSignals ) );

	for ( int i = 0; i < numSignals; i++ )
	{
		char	buffer[1024];
		int		length = 0;

		//Get the size of the string
		BufferRead( &length, sizeof( length ) );

		//Get the string
		BufferRead( &buffer, length );

		//Turn it on and add it to the system
		Signal( (const char *) &buffer );
	}

	return true;
}

int CIcarus::LoadSequence()
{
	CSequence	*sequence = GetSequence();

	//Load the sequence back in
	sequence->Load(this);

	//If this sequence had a higher GUID than the current, save it
	if ( sequence->GetID() > m_GUID )
		m_GUID = sequence->GetID();

	return true;
}

int CIcarus::LoadSequences()
{
	CSequence	*sequence;
	int			numSequences;

	//Get the number of sequences to read in
	BufferRead( &numSequences, sizeof( numSequences ) );

	int	*idTable = new int[ numSequences ];

	if ( idTable == NULL )
		return false;

	//Load the sequencer ID table
	BufferRead( idTable, sizeof( int ) * numSequences );

	//First pass, allocate all container sequences and give them their proper IDs
	if ( AllocateSequences( numSequences, idTable ) == false )
		return false;

	//Second pass, load all sequences
	for ( int i = 0; i < numSequences; i++ )
	{
		//Get the proper sequence for this load
		if ( ( sequence = GetSequence( idTable[i] ) ) == NULL )
			return false;

		//Load the sequence
		if ( ( sequence->Load(this) ) == false )
			return false;
	}

	//Free the idTable
	delete[] idTable;

	return true;
}

int CIcarus::LoadSequencers()
{
	CSequencer	*sequencer;
	int			numSequencers;
	IGameInterface* game = IGameInterface::GetGame(m_flavor);

	//Get the number of sequencers to load
	BufferRead( &numSequencers, sizeof( numSequencers ) );

	//Load all sequencers
	for ( int i = 0; i < numSequencers; i++ )
	{
		//NOTENOTE: The ownerID will be replaced in the loading process
		int sequencerID = GetIcarusID(-1);
		if ( ( sequencer = FindSequencer(sequencerID) ) == NULL )
			return false;

		if ( sequencer->Load(this, game) == false )
			return false;
	}

	return true;
}

int CIcarus::Load()
{
	CreateBuffer();

	IGameInterface* game = IGameInterface::GetGame(m_flavor);

	ojk::SavedGameHelper saved_game(
		game->get_saved_game_file());

	//Clear out any old information
	Free();

	//Check to make sure we're at the ICARUS save block
	double	version = 0.0;

	saved_game.read_chunk<double>(
		INT_ID('I', 'C', 'A', 'R'),
		version);

	//Versions must match!
	if ( version != ICARUS_VERSION )
	{
		DestroyBuffer();
		game->DebugPrint( IGameInterface::WL_ERROR, "save game data contains outdated ICARUS version information!\n");
		return false;
	}

	// Read into the buffer all our data.
	saved_game.read_chunk(
		INT_ID('I','S','E','Q'));

	const unsigned char* sg_buffer_data = static_cast<const unsigned char*>(
		saved_game.get_buffer_data());

	int sg_buffer_size = saved_game.get_buffer_size();

	if (sg_buffer_size < 0 || static_cast<size_t>(sg_buffer_size) > MAX_BUFFER_SIZE)
	{
		DestroyBuffer();
		game->DebugPrint( IGameInterface::WL_ERROR, "invalid ISEQ length: %d bytes\n", sg_buffer_size);
		return false;
	}

	std::uninitialized_copy_n(
		sg_buffer_data,
		sg_buffer_size,
		m_byBuffer);

	//Load all signals
	if ( LoadSignals() == false )
	{
		DestroyBuffer();
		game->DebugPrint( IGameInterface::WL_ERROR, "failed to load signals from save game!\n");
		return false;
	}

	//Load in all sequences
	if ( LoadSequences() == false )
	{
		DestroyBuffer();
		game->DebugPrint( IGameInterface::WL_ERROR, "failed to load sequences from save game!\n");
		return false;
	}

	//Load in all sequencers
	if ( LoadSequencers() == false )
	{
		DestroyBuffer();
		game->DebugPrint( IGameInterface::WL_ERROR, "failed to load sequencers from save game!\n");
		return false;
	}

	DestroyBuffer();

	return true;
}

int CIcarus::Update(int icarusID)
{
	CSequencer* sequencer = FindSequencer(icarusID);
	if(sequencer)
	{
		return sequencer->GetTaskManager()->Update(this);
	}
	return -1;
}

int CIcarus::IsRunning(int icarusID)
{
	CSequencer* sequencer = FindSequencer(icarusID);
	if(sequencer)
	{
		return sequencer->GetTaskManager()->IsRunning();
	}
	return false;
}

void CIcarus::Completed( int icarusID, int taskID )
{
	CSequencer* sequencer = FindSequencer(icarusID);
	if(sequencer)
	{
		sequencer->GetTaskManager()->Completed(taskID);
	}
}

// Destroy the File Buffer.
void CIcarus::DestroyBuffer()
{
	if ( m_byBuffer )
	{
		IGameInterface::GetGame()->Free( m_byBuffer );
		m_byBuffer = NULL;
	}
}

// Create the File Buffer.
void CIcarus::CreateBuffer()
{
	DestroyBuffer();
	m_byBuffer = (unsigned char *)IGameInterface::GetGame()->Malloc( MAX_BUFFER_SIZE );
	m_ulBufferCurPos = 0;
}

// Write to a buffer.
void CIcarus::BufferWrite( void *pSrcData, unsigned long ulNumBytesToWrite )
{
	if ( !pSrcData )
		return;

	// Make sure we have enough space in the buffer to write to.
	if ( MAX_BUFFER_SIZE - m_ulBufferCurPos < ulNumBytesToWrite )
	{	// Write out the buffer with all our collected data so far...
		IGameInterface::GetGame()->DebugPrint( IGameInterface::WL_ERROR, "BufferWrite: Out of buffer space, Flushing." );

		ojk::SavedGameHelper saved_game(
			IGameInterface::GetGame()->get_saved_game_file());

		saved_game.write_chunk(
			INT_ID('I', 'S', 'E', 'Q'),
			m_byBuffer,
			static_cast<int>(m_ulBufferCurPos));

		m_ulBufferCurPos = 0;	//reset buffer
	}

	assert( MAX_BUFFER_SIZE - m_ulBufferCurPos >= ulNumBytesToWrite );
	{
		memcpy( m_byBuffer + m_ulBufferCurPos, pSrcData, ulNumBytesToWrite );
		m_ulBufferCurPos += ulNumBytesToWrite;
	}
}

// Read from a buffer.
void CIcarus::BufferRead( void *pDstBuff, unsigned long ulNumBytesToRead )
{
	if ( !pDstBuff )
		return;

	// If we can read this data...
	if ( m_ulBytesRead + ulNumBytesToRead > MAX_BUFFER_SIZE )
	{// We've tried to read past the buffer...
		IGameInterface::GetGame()->DebugPrint( IGameInterface::WL_ERROR, "BufferRead: Buffer underflow, Looking for new block." );
		// Read in the next block.

		ojk::SavedGameHelper saved_game(
			IGameInterface::GetGame()->get_saved_game_file());

		saved_game.read_chunk(
			INT_ID('I', 'S', 'E', 'Q'));

		const unsigned char* sg_buffer_data = static_cast<const unsigned char*>(
			saved_game.get_buffer_data());

		int sg_buffer_size = saved_game.get_buffer_size();

		if (sg_buffer_size < 0 || static_cast<size_t>(sg_buffer_size) > MAX_BUFFER_SIZE)
		{
			IGameInterface::GetGame()->DebugPrint( IGameInterface::WL_ERROR, "invalid ISEQ length: %d bytes\n", sg_buffer_size);
			return;
		}

		std::uninitialized_copy_n(
			sg_buffer_data,
			sg_buffer_size,
			m_byBuffer);

		m_ulBytesRead = 0;	//reset buffer
	}

	assert(m_ulBytesRead + ulNumBytesToRead <= MAX_BUFFER_SIZE);
	{
		memcpy( pDstBuff, m_byBuffer + m_ulBytesRead, ulNumBytesToRead );
		m_ulBytesRead += ulNumBytesToRead;
	}
}