#include <windows.h>
#include "main.h"
#include "../Winamp/wa_ipc.h"
#include "config.h"
#include "api__in_wave.h"
#include <shlwapi.h>
#include "VirtualIO.h"

// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F}
static const GUID playbackConfigGroupGUID =
    { 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } };

HANDLE audioThread = INVALID_HANDLE_VALUE;
DWORD WINAPI ThreadProcedure( void *data );

#define kill    events[0]
#define running events[1]

HANDLE stopped = 0;
HANDLE events[ 2 ] = { 0 };

static size_t   bufferSize  = 0;
static char    *audioBuffer = 0;
static int      frameSize   = 0; // in bytes
static int      endOfFile   = 0;
static int      bits        = 0;
static SF_INFO  info;
static void    *reader      = 0;


int CalcBits()
{
	switch (info.format & SF_FORMAT_SUBMASK)
	{
	case SF_FORMAT_DOUBLE:
		return 64;

	case SF_FORMAT_PCM_32:
	case SF_FORMAT_FLOAT:
		return 32;

	case SF_FORMAT_DWVW_24:
	case SF_FORMAT_PCM_24:
		return 24;

	case SF_FORMAT_DPCM_16:
	case SF_FORMAT_DWVW_16:
	case SF_FORMAT_PCM_16:
		return 16;

	//case SF_FORMAT_PCM_S8: // cut, because 8bits is assumed unsigned
	case SF_FORMAT_PCM_U8:
	case SF_FORMAT_DPCM_8:
		return 8;

	default: return 16;
	}
}

int CalcBitRate( const SF_INFO *info )
{
	switch ( info->format & SF_FORMAT_SUBMASK )
	{
		case SF_FORMAT_PCM_S8:
		case SF_FORMAT_PCM_U8:
		case SF_FORMAT_DPCM_8:
			return MulDiv( 8 * info->channels, info->samplerate, 1000 );
		case SF_FORMAT_DWVW_12:
			return MulDiv( 12 * info->channels, info->samplerate, 1000 );
		case SF_FORMAT_DPCM_16:
		case SF_FORMAT_DWVW_16:
		case SF_FORMAT_PCM_16:
			return MulDiv( 16 * info->channels, info->samplerate, 1000 );
		case SF_FORMAT_DWVW_24:
		case SF_FORMAT_PCM_24:
			return MulDiv( 24 * info->channels, info->samplerate, 1000 );
		case SF_FORMAT_PCM_32:
		case SF_FORMAT_FLOAT:
			return MulDiv( 32 * info->channels, info->samplerate, 1000 );
		case SF_FORMAT_DOUBLE:
			return MulDiv( 64 * info->channels, info->samplerate, 1000 );

		case SF_FORMAT_G721_32:
			return 32;

		case SF_FORMAT_G723_24:
			return 24;

		case SF_FORMAT_G723_40:
			return 40;
		case SF_FORMAT_MS_ADPCM:
		case SF_FORMAT_VOX_ADPCM:
		case SF_FORMAT_IMA_ADPCM:
			return MulDiv( 4 * info->channels, info->samplerate, 1000 );
		default:
			return MulDiv( 16 * info->channels, info->samplerate, 1000 );
	}
}


void CALLBACK APCSeek( ULONG_PTR p_data )
{
	endOfFile = 0;

	int time_in_ms = (int)p_data;
	int frames     = MulDiv( time_in_ms, info.samplerate, 1000 ); // TODO: verify calculation

	sf_seek( sndFile, frames, SEEK_SET );

	plugin.outMod->Flush( time_in_ms );
}

void CALLBACK APCPause( ULONG_PTR p_data )
{
	int pause = (int)p_data;
	if ( pause )
		ResetEvent( running );
	else
		SetEvent( running );

	plugin.outMod->Pause( !!pause );
}

void CALLBACK APCStart( ULONG_PTR p_data )
{
	endOfFile = 0;
	const wchar_t *file = (const wchar_t *)p_data;

	info.format = 0;
	if ( PathIsURLW( file ) )
	{
		reader = CreateReader( file );
		if ( reader )
			sndFile = sf_open_virtual( &httpIO, SFM_READ, &info, reader );
	}
	else  // It's a local file
	{
		sndFile = sf_wchar_open( file, SFM_READ, &info );
	}

	if ( !sndFile )
	{
		if ( WaitForSingleObject( kill, 200 ) == WAIT_TIMEOUT )
			PostMessage( plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0 );

		return;
	}

	currentSongLength = MulDiv( (int)info.frames, 1000, info.samplerate ); // TODO: is this correct?
	switch ( info.format & SF_FORMAT_SUBMASK )
	{
		case SF_FORMAT_FLOAT:
		case SF_FORMAT_DOUBLE:
			sf_command( sndFile, SFC_SET_SCALE_FLOAT_INT_READ, NULL, SF_TRUE );
			break;
	}

	bits = CalcBits();

	size_t config_bits = AGAVE_API_CONFIG->GetUnsigned( playbackConfigGroupGUID, L"bits", 16 );
	if ( config_bits == 16 && config_bits < (size_t)bits )
		bits = (int)config_bits;

	if ( bits < 16 && config_upsample8bit )
		bits = 16;

	int latency = plugin.outMod->Open( info.samplerate, info.channels, bits, -1, -1 );
	if ( latency < 0 )
	{
		sf_close( sndFile );
		if ( reader )
			DestroyReader( reader );

		reader  = 0;
		sndFile = NULL;

		if ( WaitForSingleObject( kill, 200 ) == WAIT_TIMEOUT )
			PostMessage( plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0 );

		return;
	}

	frameSize = ( bits / 8 ) * info.channels;

	plugin.SAVSAInit( latency, info.samplerate );
	plugin.VSASetInfo( info.samplerate, info.channels );

	int bitrate = CalcBitRate( &info );

	plugin.SetInfo( bitrate, info.samplerate / 1000, info.channels, 1 );
	plugin.is_seekable = info.seekable;

	plugin.outMod->SetVolume( volume );
	plugin.outMod->SetPan( pan );

	size_t requiredBufferSize = 576 * frameSize * 2; // * 2 for dsp bullshit
	if ( requiredBufferSize > bufferSize )
	{
		free( audioBuffer );

		audioBuffer = (char *)calloc( requiredBufferSize, sizeof( char ) );
		bufferSize  = requiredBufferSize;
	}

	SetEvent( running );
}

void CALLBACK APCStop( ULONG_PTR p_data )
{
	if ( sndFile )
	{
		sf_close( sndFile );
		if ( reader )
			DestroyReader( reader );

		reader  = 0;
		sndFile = NULL;

	}

	ResetEvent( running );
	SetEvent( stopped );
}


void Kill()
{
	SetEvent( kill );
}

void AudioThreadInit()
{
	DWORD id;

	kill        = CreateEvent( NULL, TRUE, FALSE, NULL );
	running     = CreateEvent( NULL, TRUE, FALSE, NULL );
	stopped     = CreateEvent( NULL, FALSE, FALSE, NULL );
	audioThread = CreateThread( NULL, 0, ThreadProcedure, 0, 0, &id );

	if ( audioThread )
		SetThreadPriority( audioThread, (int)AGAVE_API_CONFIG->GetInt( playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST ) );
}

void AudioThreadQuit()
{
	free( audioBuffer );
	audioBuffer = 0;
	
	bufferSize = 0;
	
	CloseHandle( running );
	running = 0;

	CloseHandle( kill );
	kill = 0;

	CloseHandle( stopped );
	stopped = 0;

	CloseHandle( audioThread );
	audioThread = 0;
}


DWORD WINAPI ThreadProcedure( void *data )
{
	DWORD result;
	sf_count_t framesRead;
	while ( true )
	{
		result = WaitForMultipleObjectsEx( 2, events, FALSE, INFINITE, TRUE );
		if ( result == WAIT_OBJECT_0 ) // kill thread
			return 0;

		if ( result == ( WAIT_OBJECT_0 + 1 ) )
		{
			if ( endOfFile ) // if we hit the end of file previously ...
			{
				if ( plugin.outMod->IsPlaying() ) // see if we're still going
					SleepEx( 10, TRUE ); // sleep for a bit
				else // yay done playing
				{
					PostMessage( plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0 ); // tell winamp we're stopped
					// don't shut down completely yet (mpegeof will trigger a call to stop)
					ResetEvent( running ); // but we can at least sit in waitformultipleobjects ...
				}
			}
			else if ( plugin.outMod->CanWrite() > ( 576 * frameSize ) )
			{
				switch ( bits )
				{
					case 16:
						framesRead = sf_readf_short( sndFile, (short *)audioBuffer, 576 );
						break;
					case 32:
						framesRead = sf_readf_int( sndFile, (int *)audioBuffer, 576 );
						break;
					default:
						framesRead = sf_read_raw( sndFile, (int *)audioBuffer, 576 * frameSize ) / frameSize;
						break;
				}


				if ( framesRead == 0 )
				{
					endOfFile = 1;

					plugin.outMod->Write( NULL, 0 );
				}
				else
				{
					framesRead = plugin.dsp_dosamples( (short *)audioBuffer, (int)framesRead, bits, info.channels, info.samplerate );
					if ( framesRead >= 576 )
					{
						int timestamp = plugin.outMod->GetWrittenTime();
						
						plugin.SAAddPCMData( (char *)audioBuffer, info.channels, bits, timestamp );
						plugin.VSAAddPCMData( (char *)audioBuffer, info.channels, bits, timestamp );
					}

					plugin.outMod->Write( audioBuffer, (int)framesRead * frameSize );
				}
			}
			else
			{
				SleepEx( 10, TRUE );
			}
		}
	}
}