#include <iostream>
#ifdef _WIN32
 //#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
# include <windows.h>
#endif
#include "Sounds.h"
#include "MemoryMapping.h"


BEGIN_NAMESPACE

#ifdef _WIN32

#define MPEGLAYER3_WAVE_FORMAT      	0x0055
#define MPEGLAYER3_WFX_EXTRA_BYTES  	12
#define MPEGLAYER3_ID_MPEG          	1
#define MPEGLAYER3_FLAG_PADDING_OFF 	0x00000002
#define MPEGLAYER3_BLOCK_SIZE       	522


struct Sound
{
	MemoryMappingData* m_mappedFile;
	HWAVEOUT	       m_waveHandle;
	WAVEHDR            m_waveHeader;
};


Sound* SoundFilePlay(const char* a_filename, SoundFormat a_fmt, int a_channels, int a_samples)
{
	// Files over 4GiB won't work even for 64bit builds for various reasons such as WAVEHDR length field being a DWORD.
	// If need to support very large audio files, then will need to consider loading and playing the audio in chunks.
	MemoryMappingData* mapping = MemoryMapping_Open(a_filename);
	Sound* sound = SoundPlayFromMemory(MemoryMapping_GetAddress(mapping), (size_t)MemoryMapping_GetSize(mapping), a_fmt, a_channels, a_samples);
	if (sound)
		sound->m_mappedFile = mapping;
	return sound;
}


static void CALLBACK CallBackProc(HWAVEOUT hwo, UINT uMsg, DWORD, DWORD dwParam1, DWORD)
{
   if (uMsg == WOM_DONE)
   {
      std::cout << "finished" << std::endl;
      MMRESULT MMResult;
      WAVEHDR *WaveHeader = (WAVEHDR *)dwParam1;

      if((MMResult = waveOutUnprepareHeader(hwo, WaveHeader, sizeof(WAVEHDR))) != MMSYSERR_NOERROR)
      {
         char Text[256];
         waveOutGetErrorTextA(MMResult, Text, sizeof(Text));
         std::cout << "Failed to unprepare: " << Text << std::endl;
      }
      free(WaveHeader->lpData);
   }
}


Sound* SoundPlayFromMemory(void* a_buffer, size_t a_bufferLength, SoundFormat a_fmt, int a_channels, int a_samples)
{
	WAVEFORMATEX WavFormat = {
		WAVE_FORMAT_PCM,             /* wFormatTag;      */
		2,                           /* nChannels;       */
		44100,                       /* nSamplesPerSec;  */
		44100 * 2,                   /* nAvgBytesPerSec; */
		2,                           /* nBlockAlign;     */
		16,                          /* wBitsPerSample;  */
		0,                           /* cbSize;          */
	};

	struct MPEGLAYER3_WAVEFORMAT
	{
	   WAVEFORMATEX wfx;
	   WORD wID;
	   DWORD fdwFlags;
	   WORD nBlockSize;
	   WORD nFramesPerBlock;
	   WORD nCodecDelay;
	} Mp3Format = {
		{
			MPEGLAYER3_WAVE_FORMAT,      /* wFormatTag;      */
			1,                           /* nChannels;       */
			44100,                       /* nSamplesPerSec;  */
			128 * (1024 / 8),            /* nAvgBytesPerSec; */
			1,                           /* nBlockAlign;     */
			0,                           /* wBitsPerSample;  */
			MPEGLAYER3_WFX_EXTRA_BYTES,  /* cbSize;          */
		},
		MPEGLAYER3_ID_MPEG,           /* wID;             */
		MPEGLAYER3_FLAG_PADDING_OFF,  /* fdwFlags;        */
		MPEGLAYER3_BLOCK_SIZE,        /* nBlockSize;      */
		1,                            /* nFramesPerBlock; */
		1393,                         /* nCodecDelay;     */
	};   

	Sound* sound = new Sound;
	sound->m_mappedFile = 0;
	WAVEFORMATEX *format = (a_fmt == SF_WAV) ? (WAVEFORMATEX *)&WavFormat : (WAVEFORMATEX *)&Mp3Format;

	if (waveOutOpen(&sound->m_waveHandle, WAVE_MAPPER, format, (DWORD)CallBackProc, 0, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
	{
		std::cerr << "Failed to open Wave!" << std::endl;
		return 0;
	}
	std::cerr << "open Wave!" << std::endl;
	MMRESULT MMResult;
	memset(&sound->m_waveHeader, 0, sizeof(sound->m_waveHeader));
	a_buffer = (char*)a_buffer + 4; // Skip header for now
	a_bufferLength -= 4;
	sound->m_waveHeader.lpData = (char *)a_buffer;
	sound->m_waveHeader.dwBufferLength = (DWORD)a_bufferLength;
	sound->m_waveHeader.dwBytesRecorded = (DWORD)a_bufferLength;
	sound->m_waveHeader.dwLoops = 1;
	sound->m_waveHeader.dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP;
	if (waveOutPrepareHeader(sound->m_waveHandle, &sound->m_waveHeader, sizeof(sound->m_waveHeader)) != MMSYSERR_NOERROR)
	{
		std::cout << "Failed to prepare header!" << std::endl;
		return 0;
	}
	std::cerr << "prepared header!" << std::endl;
	if ((MMResult = waveOutWrite(sound->m_waveHandle, &sound->m_waveHeader, sizeof(sound->m_waveHeader))) != MMSYSERR_NOERROR)
	{
		char Text[256];
		waveOutGetErrorTextA(MMResult, Text, sizeof(Text));
		std::cout << "Failed to write: " <<  Text << std::endl;
		return 0;
	}
	std::cerr << "wrote Wave!" << std::endl;
	return sound;
}


void SoundWaitToFinish(Sound* a_sound)
{
	while (!(a_sound->m_waveHeader.dwFlags & WHDR_DONE))
		Sleep(100);
}


void SoundClose(Sound* a_sound)
{
	MMRESULT MMResult;
	if ((MMResult = waveOutClose(a_sound->m_waveHandle)) != MMSYSERR_NOERROR)
	{
		char Text[256];
		waveOutGetErrorTextA(MMResult, Text, sizeof(Text));
		std::cout << "Failed to close: " << Text << std::endl;
	}

	if (a_sound->m_mappedFile)
		MemoryMapping_Close(a_sound->m_mappedFile);

	delete a_sound;
}


#endif


END_NAMESPACE

