#pragma once

#define BLOCKSIZE (44100/25)
#define NUMBLOCKS 8
#define BUFSIZE (BLOCKSIZE*NUMBLOCKS) // in samples

typedef void CALLBACK FillSoundCB(void *user, stereo *, int len);


struct SoundOut
{
	int inited;
	int mixrate;
	HWAVEOUT dev;
	WAVEFORMATEX	pcmwf;
	WAVEHDR wavehdr;
	int nextblock;
	int closing;
	DWORD threadid;
	volatile HANDLE thread;

	FillSoundCB *fscb;
	void *userdata;

	~SoundOut()
	{
		Close();
	}

	SoundOut()
	{
		fscb=NULL;
		userdata=NULL;
		dev=NULL;
		wavehdr.lpData=NULL;
		inited=0;
		closing=0;
	}

	void Close()
	{
		if (inited)
		{
			closing=1;
			if (thread) WaitForSingleObject(thread, 5000);
			thread=0;
			closing=0;

			if (dev)
			{
				waveOutReset(dev);
				waveOutClose(dev);
				dev=NULL;
			}
			delete [] wavehdr.lpData;
			wavehdr.lpData=NULL;
		}
		closing=0;
		inited=0;
	}

	void Init(int mix=44100)
	{
		Close();

		closing=0;
		mixrate=mix;
	
		UINT			hr;
		pcmwf.wFormatTag		= WAVE_FORMAT_PCM; 
		pcmwf.nChannels			= 2;
		pcmwf.wBitsPerSample	= 16; 
		pcmwf.nBlockAlign		= pcmwf.nChannels * pcmwf.wBitsPerSample / 8;
		pcmwf.nSamplesPerSec	= mixrate; 
		pcmwf.nAvgBytesPerSec	= pcmwf.nSamplesPerSec * pcmwf.nBlockAlign; 
		pcmwf.cbSize			= 0;

		dev=0;
		hr = waveOutOpen(&dev, WAVE_MAPPER, &pcmwf, 0, 0, 0);

		if (hr) return;

		memset(&wavehdr,0,sizeof(wavehdr));
		wavehdr.lpData = new char [ BUFSIZE*4 ];
		wavehdr.dwBufferLength = BUFSIZE*4;
		wavehdr.dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP;
		wavehdr.dwLoops = -1;
		nextblock=0;
		do
		{
			FillSound();
		} while (nextblock);
		
		waveOutPrepareHeader(dev, &wavehdr, sizeof(WAVEHDR));
		waveOutWrite(dev, &wavehdr, sizeof(WAVEHDR));

		closing=0;
		thread = ::CreateThread(NULL, 0, SoundThread, this,0, &threadid);
		//SetThreadPriority(thread, THREAD_PRIORITY_TIME_CRITICAL);	// THREAD_PRIORITY_HIGHEST);
			
		inited=1;

	}

	static DWORD __stdcall SoundThread(void * lpdwParam)
	{
		if (lpdwParam) return ((SoundOut*)lpdwParam)->SoundThreadProc();
		return 0;
	}

	DWORD SoundThreadProc()
	{
		while (!closing)
		{
			UpdateSound();
			_sleep(5);
		}
		return 0;
	}

	void FillSound()
	{
		if (inited)
		{
			stereo *out = ((stereo*)wavehdr.lpData) + nextblock*BLOCKSIZE;
			/*for (int s=0;s<BLOCKSIZE;s++)
			{
				out->l=rand();
				out->r=rand();
				out++;
			}*/
			if (fscb) (*fscb)(userdata,out,BLOCKSIZE); else memset(out,0,BLOCKSIZE*4);
			nextblock=(nextblock+1)%NUMBLOCKS;
		}		
	}

	void UpdateSound()
	{
		MMTIME	mmt;

		mmt.wType = TIME_BYTES;
		waveOutGetPosition(dev, &mmt, sizeof(MMTIME));
		mmt.u.cb >>= 2;
		int cursorpos = mmt.u.cb;
		int cursorblock = (cursorpos / BLOCKSIZE) % NUMBLOCKS;
		cursorpos %= BLOCKSIZE;
		while (nextblock != cursorblock)
		{
			FillSound();
		}		
	}

	
};










