//////////////////////////////////////////////////////////////////////////////
//
// Asura_GameSpy_Voice.cpp
//
// Copyright (c) 2004 Rebellion. All rights reserved.
//
// Description:
//
//     Asura interface for GameSpy Voice2 SDK
//
//////////////////////////////////////////////////////////////////////////////

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

#include "Core/Asura.h"

// This file's header.
#include "Network/GameSpy/Asura_GameSpy_Voice.h"

#ifdef ASURA_SUPPORT_GAMESPY

// Asura independent includes
#include "Core/Asura_UserInput.h"
#include "Core/Asura_Timers.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_Logfile.h"
#include "Network/Asura_Message.h"
#include "Network/Asura_Network.h"
#include "ChunkSystem/Asura_Chunk_Macros.h"

// Asura PS2 includes
#include "Input/Asura_PlatformInput.h"

// middleware includes
#include "GameSpy/voice2/gv.h"


// C O N S T A N T S /////////////////////////////////////////////////////////
static const u_int uASURA_GAMESPY_VOICE_MAX_DEVICES = 6;
static const double dfASURA_GAMESPY_CAPTURE_THRESHOLD = 0.1;


// I N T E R N A L   C L A S S E S ///////////////////////////////////////////
class Asura_GameSpy_Voice_Internal
{
 
public:

	static void Initialise()
	{
		s_pxPlayDevice = NULL;
		s_pxCapDevice = NULL;
	}

	static void Shutdown()
	{
		s_pxPlayDevice = NULL;
		s_pxCapDevice = NULL;
	}

	static void Reset()
	{
		s_pxPlayDevice = NULL;
		s_pxCapDevice = NULL;
	}

	static void UnpluggedCallback(GVDevice pxDevice)
	{
		Asura_OutputToDebugger(("voice device unplugged\n"));

		Asura_GameSpy_Voice::s_bHasFoundDevices = false;

		if (pxDevice == s_pxCapDevice)
		{
			s_pxCapDevice = NULL;
		}

		if (pxDevice == s_pxPlayDevice)
		{
			s_pxPlayDevice = NULL;
		}
	}

	static void SetVolume( float fVolume )
	{
		s_afStoredVolume[s_uCurVolIndex] = fVolume;
		s_uCurVolIndex = (s_uCurVolIndex + 1) & 3;
	}

	static float GetVolume()
	{
		float fMax = 0.0f;
		for (u_int u = 0; u < 4; ++u)
		{
			fMax = Asura_Maths::Max(fMax, s_afStoredVolume[u]);
		}
		return fMax;
	}

	static void ResetVolume()
	{
		for (u_int u = 0; u < 4; ++u)
		{
			s_afStoredVolume[u] = 0.0f;
		}
	}
	
	static GVDevice s_pxPlayDevice;
	static GVDevice s_pxCapDevice;

private:

	static float s_afStoredVolume[4];
	static u_int s_uCurVolIndex;
};


// S T A T I C   C L A S S   D A T A /////////////////////////////////////////
bool Asura_GameSpy_Voice::s_bInitialised = false;
bool Asura_GameSpy_Voice::s_bHasFoundDevices = false;
int Asura_GameSpy_Voice::s_iTalkButtonID = ASURA_KEYID_VOID;
bool Asura_GameSpy_Voice::s_bLocalEcho;
bool Asura_GameSpy_Voice::s_bIsTalking;

GVDevice Asura_GameSpy_Voice_Internal::s_pxPlayDevice;
GVDevice Asura_GameSpy_Voice_Internal::s_pxCapDevice;
float Asura_GameSpy_Voice_Internal::s_afStoredVolume[4];
u_int Asura_GameSpy_Voice_Internal::s_uCurVolIndex;

// C L A S S   F U N C T I O N S /////////////////////////////////////////////
void Asura_GameSpy_Voice::Initialise( void )
{
	bool bOK = Platform_Initialise();

	if (!bOK)
	{
		Asura_Assert(false, "failed to platform init GameSpy Voice");
		return;
	}

	if(!gvSetCodec(GVCodecAverage))
	{
		Asura_Assert(false, "failed to set GameSpy Voice codec");
		return;
	}

#if _DEBUG
	int iSamplesPerFrame, iEncodedFrameSize, iBitsPerSecond;
	gvGetCodecInfo( &iSamplesPerFrame, &iEncodedFrameSize, &iBitsPerSecond );
	Asura_OutputToDebugger(("Voice codec: samples/frame = %d, compressed frame size = %d, bits/sec = %d\n", 
		iSamplesPerFrame, iEncodedFrameSize, iBitsPerSecond));
#endif // _DEBUG

	Asura_GameSpy_Voice_Internal::Initialise();

	s_bInitialised = true;

	s_bHasFoundDevices = false;
}


void Asura_GameSpy_Voice::Reset( void )
{
	Asura_GameSpy_Voice_Internal::Reset();
}


void Asura_GameSpy_Voice::Shutdown( void )
{
	gvCleanup();

	Asura_GameSpy_Voice_Internal::Shutdown();

	s_bInitialised = false;
}


void Asura_GameSpy_Voice::Update( void )
{
	if (!s_bInitialised) return;
	
	if (!s_bHasFoundDevices)
	{
		s_bHasFoundDevices = FindDevices();
	}
	else
	{
		if (s_iTalkButtonID > ASURA_KEYID_VOID && s_iTalkButtonID < MAX_NUMBER_OF_ASURA_KEYIDS)
		{
			bool bIsTalkButtonHeld = Asura_UserInput::KeyPressed(s_iTalkButtonID);
			if (bIsTalkButtonHeld != s_bIsTalking)
			{
				ToggleTalking();
			}
		}

		// let gv process the packets
		gvThink();

		// check if any devices were unplugged during that think()
		if (!s_bHasFoundDevices)
		{
			return;
		}

		if (s_bIsTalking)
		{
			// check for captured packets
			GVBool bResult;
			GVByte aucPacket[1024];
			int iPacketLen;
			int iSource = Asura_Network::GetClientID();
			GVFrameStamp usFrameStamp;
			GVScalar fVolume;

			do
			{
				int iHeaderLen = sizeof(GVFrameStamp) + sizeof(int);

				iPacketLen = sizeof(aucPacket) - iHeaderLen;

				bResult = gvCapturePacket(Asura_GameSpy_Voice_Internal::s_pxCapDevice, aucPacket, &iPacketLen, &usFrameStamp, &fVolume);
				
				if(bResult)
				{
					// handle the captured packet - copy the frame stamp to the end of the buffer and send.
					GVByte* pcChunkBuffer = aucPacket + iPacketLen;
					ASURA_CHUNK_FILL(pcChunkBuffer, iSource, int);
					ASURA_CHUNK_FILL(pcChunkBuffer, usFrameStamp, GVFrameStamp);
			
					// handle the captured packet - Send the stuff
					SendVoicePacket(aucPacket, iPacketLen + iHeaderLen);
					
					// store the volume
					Asura_GameSpy_Voice_Internal::SetVolume( fVolume );
				}
			}
			while(bResult);
		}
		else
		{
			Asura_GameSpy_Voice_Internal::ResetVolume();
		}
	}
}


bool Asura_GameSpy_Voice::FindDevices( void )
{
	int iNumDevices;
	GVDeviceInfo axDevices[uASURA_GAMESPY_VOICE_MAX_DEVICES];
	GVDevice pxDevice;
	GVBool bPlaybackDevice, bCaptureDevice, bDefaultPlaybackDevice, bDefaultCaptureDevice;

	GVBool bHaveSetCaptureDevice  = GVFalse;
	GVBool bHaveSetPlaybackDevice = GVFalse;

	iNumDevices = gvListDevices(axDevices, uASURA_GAMESPY_VOICE_MAX_DEVICES, GV_CAPTURE_AND_PLAYBACK);

	if (!iNumDevices)
	{
		return false;
	}

	Asura_OutputToDebugger(("GV Found %d devices\n", iNumDevices));

	for(int i = 0 ; i < iNumDevices ; i++)
	{
		const GVDeviceInfo* pxDeviceInfo = &axDevices[i];
		bPlaybackDevice = (pxDeviceInfo->m_deviceType & GV_PLAYBACK);
		bCaptureDevice = (pxDeviceInfo->m_deviceType & GV_CAPTURE);
		bDefaultPlaybackDevice = (pxDeviceInfo->m_defaultDevice & GV_PLAYBACK);
		bDefaultCaptureDevice = (pxDeviceInfo->m_defaultDevice & GV_CAPTURE);

		Asura_OutputToDebugger(("%d: %s\n\tPlayback: %s\n\tCapture: %s\n",
			i, pxDeviceInfo->m_name,
			bPlaybackDevice?(bDefaultPlaybackDevice?"Default":"Yes"):"No",
			bCaptureDevice?(bDefaultCaptureDevice?"Default":"Yes"):"No"));

		if (!bHaveSetCaptureDevice && bCaptureDevice)
			bDefaultCaptureDevice = bCaptureDevice;
		if (!bHaveSetPlaybackDevice && bPlaybackDevice)
			bDefaultPlaybackDevice = bPlaybackDevice;
			
#if defined(_WIN32)
		if(bDefaultPlaybackDevice || bDefaultCaptureDevice)
#else
			if((bPlaybackDevice && !Asura_GameSpy_Voice_Internal::s_pxPlayDevice) || (bCaptureDevice && !Asura_GameSpy_Voice_Internal::s_pxCapDevice))
#endif
		{
			GVDeviceType iTypes = 0;

			if(bPlaybackDevice)
				iTypes |= GV_PLAYBACK;
			if(bCaptureDevice)
				iTypes |= GV_CAPTURE;

			pxDevice = gvNewDevice(axDevices[i].m_id, iTypes);
			Asura_OutputToDebugger(("\tCreation: %s\n", pxDevice?"succeeded":"failed"));

			if (!pxDevice) 
			{
				Asura_Assert(false, "voice device creation failed.");
				continue;
			}

			gvSetUnpluggedCallback(pxDevice, Asura_GameSpy_Voice_Internal::UnpluggedCallback);

			if(iTypes & GV_CAPTURE)
			{
				Asura_GameSpy_Voice_Internal::s_pxCapDevice = pxDevice;
				bHaveSetCaptureDevice = GVTrue;
				gvSetCaptureThreshold(pxDevice, dfASURA_GAMESPY_CAPTURE_THRESHOLD);
//#if CAPTURE_FILTER
//				gvSetFilter(Asura_GameSpy_Voice_Internal::s_pxCapDevice, GV_CAPTURE, Filter);
//#endif

				// (MH - 23.07.2004) start with the device off (set to on and then call toggle code)
				s_bIsTalking = true;
				ToggleTalking();
			}
			if(iTypes & GV_PLAYBACK)
			{
				Asura_GameSpy_Voice_Internal::s_pxPlayDevice = pxDevice;
				bHaveSetPlaybackDevice = GVTrue;
			}

			GVBool bResult = gvStartDevice(pxDevice, iTypes);
			Asura_OutputToDebugger(("\tStarting: %s\n", bResult ? "succeeded" : "failed"));
		}
	}

	return bHaveSetCaptureDevice && bHaveSetPlaybackDevice;
}


void Asura_GameSpy_Voice::ToggleTalking( void )
{
	if (IsUnplugged())
	{
		s_bIsTalking = false;
		return;
	}

	s_bIsTalking = !s_bIsTalking;

	if(s_bIsTalking)
	{
		if(!gvIsDeviceStarted(Asura_GameSpy_Voice_Internal::s_pxCapDevice, GV_CAPTURE))
		{
			gvStartDevice(Asura_GameSpy_Voice_Internal::s_pxCapDevice, GV_CAPTURE);
		}
	}
	else
	{
		if(gvIsDeviceStarted(Asura_GameSpy_Voice_Internal::s_pxCapDevice, GV_CAPTURE))
		{
			gvStopDevice(Asura_GameSpy_Voice_Internal::s_pxCapDevice, GV_CAPTURE);
		}
	}
}

float Asura_GameSpy_Voice::GetCaptureVolume()
{
	if (IsTalking())
	{
		return Asura_GameSpy_Voice_Internal::GetVolume();
	}
	else
	{
		return 0.0f;
	}
}


// OR - 22/11/2004 13:12:43 - Send an empty message
void Asura_GameSpy_Voice::SendHeartbeat( void )
{
	// need to interface with networking code.
	Asura_Message* pxMsg = Asura_Network::CreateMessage( ASURA_MSG_VOICEDATA, sizeof(Asura_Guid) );
	
	if (pxMsg)
	{
		char* pcData = static_cast<char*>(pxMsg->GetData());
		ASURA_CHUNK_FILL(pcData, Asura_Network::GetClientID(), Asura_Guid);
		pxMsg->To = ASURA_GUID_SERVER;
		pxMsg->From = Asura_Network::GetClientID();
		Asura_Network::Send( pxMsg );
	}
}


void Asura_GameSpy_Voice::SendVoicePacket( const u_char* pucData, u_int uNumBytes )
{
	// need to interface with networking code.
	Asura_Message* pxMsg = Asura_Network::CreateMessage( ASURA_MSG_VOICEDATA, uNumBytes, 0, pucData );
	if (pxMsg)
	{
		pxMsg->To = ASURA_GUID_SERVER;
		pxMsg->From = Asura_Network::GetClientID();
		Asura_Network::Send( pxMsg );
	}
} 

// OR - 22/11/2004 11:30:01 - Returns the source of the received voice data
int Asura_GameSpy_Voice::PlayVoicePacket( const u_char* pucData, u_int uDataSize, int dummy )
{
	if(!Asura_GameSpy_Voice_Internal::s_pxPlayDevice)
		return 0;

	if(uDataSize <= sizeof(Asura_Guid))
	{
		Asura_Guid iSource = *(reinterpret_cast<const Asura_Guid*>(pucData));
		return iSource;
	}

	GVFrameStamp usFrameStamp;
	int iSource=0; 
	int iHeaderLen = sizeof(GVFrameStamp) + sizeof(int);
	int iVoiceDataSize = uDataSize - iHeaderLen;
	
	// OR - 22/11/2004 12:43:37 - Gather header information
	const u_char* pucChunkBuffer = pucData + iVoiceDataSize;
	ASURA_CHUNK_EXTRACT(pucChunkBuffer, iSource, int);
	ASURA_CHUNK_EXTRACT(pucChunkBuffer, usFrameStamp, GVFrameStamp);

	if (s_bLocalEcho || iSource != static_cast<int>(Asura_Network::GetClientID()))
	{
		// queue it
		gvPlayPacket(Asura_GameSpy_Voice_Internal::s_pxPlayDevice, pucData, iVoiceDataSize, iSource, usFrameStamp);
	}

	return iSource;
}

#endif //#ifdef ASURA_SUPPORT_GAMESPY
/**/
