/* SPU2-X, A plugin for Emulating the Sound Processing Unit of the Playstation 2
 * Developed and maintained by the Pcsx2 Development Team.
 *
 * Original portions from SPU2ghz are (c) 2008 by David Quintana [gigaherz]
 *
 * SPU2-X is free software: you can redistribute it and/or modify it under the terms
 * of the GNU Lesser General Public License as published by the Free Software Found-
 * ation, either version 3 of the License, or (at your option) any later version.
 *
 * SPU2-X 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPU2-X.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <cassert>

//#include "Global.h"
#include "../types.h"
#include "SndOut.h"

//----------------
int SndOutLatencyMS = 160;
bool timeStretchDisabled = false;
//----------------

StereoOut32 StereoOut32::Empty( 0, 0 );

StereoOut32::StereoOut32( const StereoOut16& src ) :
	Left( src.Left ),
	Right( src.Right )
{
}

StereoOut32::StereoOut32( const StereoOutFloat& src ) :
	Left( (int32_t)(src.Left * 2147483647.0f) ),
	Right( (int32_t)(src.Right * 2147483647.0f) )
{
}

StereoOut16 StereoOut32::DownSample() const
{
	return StereoOut16(
		static_cast<int16_t>(Left >> SndOutVolumeShift),
		static_cast<int16_t>(Right >> SndOutVolumeShift)
	);
}

/*StereoOut32 StereoOut16::UpSample() const
{
	return StereoOut32(
		Left << SndOutVolumeShift,
		Right << SndOutVolumeShift
	);

}*/

//class NullOutModule: public SndOutModule
//{
//public:
//	int32_t  Init()  { return 0; }
//	void Close() { }
//	int32_t  Test() const { return 0; }
//	void Configure(uptr parent)  { }
//	bool Is51Out() const { return false; }
//	int GetEmptySampleCount() const { return 0; }
//
//	const wchar_t* GetIdent() const
//	{
//		return L"nullout";
//	}
//
//	const wchar_t* GetLongName() const
//	{
//		return L"No Sound (Emulate SPU2 only)";
//	}
//
//	void ReadSettings()
//	{
//	}
//
//	void WriteSettings() const
//	{
//	}
//
//} NullOut;
//
//SndOutModule* mods[]=
//{
//	&NullOut,
//#ifdef _MSC_VER
//	XAudio2Out,
//	DSoundOut,
//	WaveOut,
//#endif
//	NULL		// signals the end of our list
//};
//
//int FindOutputModuleById( const wchar_t* omodid )
//{
//	int modcnt = 0;
//	while( mods[modcnt] != NULL )
//	{
//		if( wcscmp( mods[modcnt]->GetIdent(), omodid ) == 0 )
//			break;
//		++modcnt;
//	}
//	return modcnt;
//}

StereoOut32 *SndBuffer::m_buffer;
int32_t SndBuffer::m_size;
int32_t SndBuffer::m_rpos;
int32_t SndBuffer::m_wpos;
int32_t SndBuffer::m_data;

bool SndBuffer::m_underrun_freeze;
StereoOut32* SndBuffer::sndTempBuffer = NULL;
StereoOut16* SndBuffer::sndTempBuffer16 = NULL;
int SndBuffer::sndTempProgress = 0;

static int GetAlignedBufferSize( int comp )
{
	return (comp + SndOutPacketSize-1) & ~(SndOutPacketSize-1);
}

// Returns true if there is data to be output, or false if no data
// is available to be copied.
bool SndBuffer::CheckUnderrunStatus( int& nSamples, int& quietSampleCount )
{
	quietSampleCount = 0;
	if( m_underrun_freeze )
	{
		int toFill = (int)(m_size * ( timeStretchDisabled ? 0.50f : 0.1f ) );
		toFill = GetAlignedBufferSize( toFill );

		// toFill is now aligned to a SndOutPacket

		if( m_data < toFill )
		{
			quietSampleCount = nSamples;
			return false;
		}

		m_underrun_freeze = false;
		//TODO
		//if( MsgOverruns() )
			printf(" * SPU2 > Underrun compensation (%d packets buffered)\n", toFill / SndOutPacketSize );
		lastPct = 0.0;		// normalize timestretcher
	}
	else if( m_data < nSamples )
	{
		nSamples = m_data;
		quietSampleCount = SndOutPacketSize - m_data;
		m_underrun_freeze = true;

		if( !timeStretchDisabled )
			timeStretchUnderrun();

		return nSamples != 0;
	}

	return true;
}

void SndBuffer::_InitFail()
{
	// If a failure occurs, just initialize the NoSound driver.  This'll allow
	// the game to emulate properly (hopefully), albeit without sound.
	//OutputModule = FindOutputModuleById( NullOut.GetIdent() );
	//mods[OutputModule]->Init();
}

void SndBuffer::_WriteSamples(StereoOut32 *bData, int nSamples)
{
	int free = m_size-m_data;
	m_predictData = 0;

	assert( m_data <= m_size );

	// Problem:
	//  If the SPU2 gets out of sync with the SndOut device, the writepos of the
	//  circular buffer will overtake the readpos, leading to a prolonged period
	//  of hopscotching read/write accesses (ie, lots of staticy crap sound for
	//  several seconds).
	//
	// Compromise:
	//  When an overrun occurs, we adapt by discarding a portion of the buffer.
	//  The older portion of the buffer is discarded rather than incoming data,
	//  so that the overall audio synchronization is better.

	if( free < nSamples )
	{
		// Buffer overrun!
		// Dump samples from the read portion of the buffer instead of dropping
		// the newly written stuff.

		int32_t comp;

		if( !timeStretchDisabled )
		{
			comp = timeStretchOverrun();
		}
		else
		{
			// Toss half the buffer plus whatever's being written anew:
			comp = GetAlignedBufferSize( (m_size + nSamples ) / 2 );
			if( comp > (m_size-SndOutPacketSize) ) comp = m_size-SndOutPacketSize;
		}

		m_data -= comp;
		m_rpos = (m_rpos+comp) % m_size;
		//TODO
		//if( MsgOverruns() )
			printf(" * SPU2 > Overrun Compensation (%d packets tossed)\n", comp / SndOutPacketSize );
		lastPct = 0.0;		// normalize the timestretcher
	}

	// copy in two phases, since there's a chance the packet
	// wraps around the buffer (it'd be nice to deal in packets only, but
	// the timestretcher and DSP options require flexibility).

	const int endPos = m_wpos + nSamples;
	const int secondCopyLen = endPos - m_size;
	StereoOut32* wposbuffer = &m_buffer[m_wpos];

	m_data += nSamples;
	if( secondCopyLen > 0 )
	{
		nSamples -= secondCopyLen;
		memcpy( m_buffer, &bData[nSamples], secondCopyLen * sizeof( *bData ) );
		m_wpos = secondCopyLen;
	}
	else
		m_wpos += nSamples;

	memcpy( wposbuffer, bData, nSamples * sizeof( *bData ) );
}

void SndBuffer::Init()
{
	//if( mods[OutputModule] == NULL )
	//{
	//	_InitFail();
	//	return;
	//}

	// initialize sound buffer
	// Buffer actually attempts to run ~50%, so allocate near double what
	// the requested latency is:


	m_rpos = 0;
	m_wpos = 0;
	m_data = 0;

	try
	{
		const float latencyMS = SndOutLatencyMS * (timeStretchDisabled ? 1.5f : 2.0f );
		m_size = GetAlignedBufferSize( (int)(latencyMS * SampleRate / 1000.0f ) );
		m_buffer = new StereoOut32[m_size];
		m_underrun_freeze = false;

		sndTempBuffer = new StereoOut32[SndOutPacketSize];
		sndTempBuffer16 = new StereoOut16[SndOutPacketSize];
	}
	catch( std::bad_alloc& )
	{
		// out of memory exception (most likely)

		printf( "Out of memory error occurred while initializing SPU2." );
		_InitFail();
		return;
	}

	// clear buffers!
	// Fixes loopy sounds on emu resets.
	memset( sndTempBuffer, 0, sizeof(StereoOut32) * SndOutPacketSize );
	memset( sndTempBuffer16, 0, sizeof(StereoOut16) * SndOutPacketSize );

	sndTempProgress = 0;

	soundtouchInit();		// initializes the timestretching

	// some crap
	//spdif_set51(mods[OutputModule]->Is51Out());

	// initialize module
	//if( mods[OutputModule]->Init() == -1 ) _InitFail();
}

/*void SndBuffer::Cleanup()
{
	//mods[OutputModule]->Close();

	soundtouchCleanup();

	//safe_delete_array( m_buffer );
	//safe_delete_array( sndTempBuffer );
	//safe_delete_array( sndTempBuffer16 );
	delete[] m_buffer;
	delete[] sndTempBuffer;
	delete[] sndTempBuffer16;
}*/

//int SndBuffer::m_dsp_progress = 0;

//int SndBuffer::m_timestretch_progress = 0;
//int SndBuffer::ssFreeze = 0;

/*void SndBuffer::ClearContents()
{
	SndBuffer::soundtouchClearContents();
	SndBuffer::ssFreeze = 30; //Delays sound output for about half a second.
}*/

void SndBuffer::Write( const StereoOut32& Sample )
{
	// Log final output to wavefile.
	//WaveDump::WriteCore( 1, CoreSrc_External, Sample.DownSample() );

	//RecordWrite( Sample.DownSample() );

	//if(mods[OutputModule] == &NullOut) // null output doesn't need buffering or stretching! :p
	//	return;

	sndTempBuffer[sndTempProgress++] = Sample;

	// If we haven't accumulated a full packet yet, do nothing more:
	if(sndTempProgress < SndOutPacketSize) return;
	sndTempProgress = 0;

	//Don't play anything directly after loading a savestate, avoids static killing your speakers.
//	if ( ssFreeze > 0 )
//	{
//		ssFreeze--;
//		return;
//	}
//#ifndef __LINUX__
//	else if( dspPluginEnabled )
//	{
//		// Convert in, send to winamp DSP, and convert out.
//
//		for( int i=0; i<SndOutPacketSize; ++i ) { sndTempBuffer16[i] = sndTempBuffer[i].DownSample(); }
//		m_dsp_progress += DspProcess( (s16*)sndTempBuffer16, SndOutPacketSize );
//
//		// Some ugly code to ensure full packet handling:
//		int ei = 0;
//		while( m_dsp_progress >= SndOutPacketSize )
//		{
//			for( int i=0; i<SndOutPacketSize; ++i, ++ei ) { sndTempBuffer[i] = sndTempBuffer16[ei].UpSample(); }
//
//			if( !timeStretchDisabled )
//				timeStretchWrite();
//			else
//				_WriteSamples(sndTempBuffer, sndTempProgress);
//
//			m_dsp_progress -= SndOutPacketSize;
//		}
//
//		// copy any leftovers to the front of the dsp buffer.
//		if( m_dsp_progress > 0 )
//		{
//			memcpy( &sndTempBuffer16[ei], sndTempBuffer16,
//				sizeof(sndTempBuffer16[0]) * m_dsp_progress
//			);
//		}
//	}
//#endif
//	else
	{
		if( !timeStretchDisabled )
			timeStretchWrite();
		else
			_WriteSamples(sndTempBuffer, SndOutPacketSize);
	}
}

/*int32_t SndBuffer::Test()
{
	//if( mods[OutputModule] == NULL )
	//	return -1;

	//return mods[OutputModule]->Test();
	return 0;
}*/