#include <assert.h>
#include "utils/logger.h"
#include "filesystem/stream.h"
#include "filesystem/filemanager.h"
#include "sound/sound_system.h"

#undef Msg
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <mmeapi.h>
#include <dsound.h>

#pragma comment(lib, "dsound.lib")

IDirectSound8* g_DirectSound = nullptr;
IDirectSoundBuffer* g_PrimaryBuffer = nullptr;

/////////////////////////////////////////////////////////////////////////////////////////
// Sound implementation

class Sound_DSound_Impl : public ISound
{
public:
	Sound_DSound_Impl(const char* sSoundFile);
	~Sound_DSound_Impl();

	void	play() override;
	void	Stop() override;
	void	Pause() override;

	bool	isPlaying() const override;
	bool	isPaused() const override;

	bool	IsLooped() const override;
	void	SetLooped(bool bLooped) override;

	float	GetVolume() const override;
	void	SetVolume(float fVolume) override;

	void	OnFrame(float delta) override;

private:
	IDirectSoundBuffer8* m_pBuffer;
	bool m_IsLopped;
};

Sound_DSound_Impl::Sound_DSound_Impl(const char* sSoundFile) : m_pBuffer(nullptr), m_IsLopped(false)
{
	if (!strstr(sSoundFile, ".wav"))
		Logger::error("Failed to load sound file '%s'. Only wav supported", sSoundFile);

	StreamBase* pStream = g_fileManager->OpenStream( sSoundFile, FileAccess::Read );
	assert( pStream );

	pStream->seek(SeekDir::End, 0);
	size_t length = pStream->tell();
	pStream->seek(SeekDir::Begin, 0);

	uint8_t* filedata = (uint8_t*)malloc(length);
	pStream->readBuffer(filedata, length);

	delete pStream;
	pStream = nullptr;

	DSBUFFERDESC bufferDesc = {};


	//g_DirectSound->CreateSoundBuffer( as )

}

Sound_DSound_Impl::~Sound_DSound_Impl()
{

}

void	Sound_DSound_Impl::play()
{
	m_pBuffer->Play(0, 0, m_IsLopped ? DSBPLAY_LOOPING : 0);
}

void	Sound_DSound_Impl::Stop()
{
	m_pBuffer->Stop();
}

void	Sound_DSound_Impl::Pause()
{

}

bool	Sound_DSound_Impl::isPlaying() const
{
	return false;
}

bool	Sound_DSound_Impl::isPaused() const
{
	return false;
}

bool	Sound_DSound_Impl::IsLooped() const
{
	return m_IsLopped;
}

void	Sound_DSound_Impl::SetLooped(bool bLooped)
{
	m_IsLopped = bLooped;
}

float	Sound_DSound_Impl::GetVolume() const
{
	return 1.0f;
}

void	Sound_DSound_Impl::SetVolume(float fVolume)
{

}

void	Sound_DSound_Impl::OnFrame(float delta)
{

}

//bool cSoundBuffer::SetVolume(short percent) 
//{
//	long Volume;		
//	if (m_buffer == NULL)			
//		return false;		
//	
//	if (!percent)			
//		Volume = DSBVOLUME_MIN;		
//	else 			
//		Volume = -20 * (100 - (percent % 101));		
//	
//	if (FAILED(m_buffer->SetVolume(Volume)))			
//		return false;		return true; 
//}	
//
//bool cSoundBuffer::SetPan(signed long level) 
//{ 
//	signed long Pan;	
//
//	if (m_buffer == NULL)			
//		return false;
//
//	if (level < 0) 			
//		Pan = DSBPAN_LEFT / 100 * ((-level) % 101);		
//	else			
//		Pan = DSBPAN_RIGHT / 100 * (level % 101);		
//	
//	if (FAILED(m_buffer->SetPan(Pan)))			
//		return false;		
//	
//	return true; 
//}

/////////////////////////////////////////////////////////////////////////////////////////
// Sound System implementation

class SoundSystem_DSound : public ISoundSystem
{
public:
	SoundSystem_DSound(HWND hWndHandle);
	~SoundSystem_DSound();

	void			InitPrimaryBuffer();

	void			selectDevice(SoundDeviceID deviceId) override;
	SoundDeviceID	selectedDevice() const override;

	ISound*			CreateSound(const char* sSoundFile) override;
	void			DestroySound(ISound*& pSound) override;

	void			Update(float delta) override;

	void			StopAllSounds() override;
	void			PauseAllSounds() override;
	void			ReleaseAllSounds() override;

	float			GetMasterVolume() const override;
	void			SetMasterVolume(float fVolume) override;

private:
	std::vector< Sound_DSound_Impl* > 	m_Sounds;
	float								m_MasterVolume;
};

SoundSystem_DSound::SoundSystem_DSound(HWND hWndHandle) :
	m_MasterVolume(0.0f)
{
	Logger::logPrint("Starting SOUND system ...");

	HRESULT hr = DirectSoundCreate8(NULL, &g_DirectSound, NULL);
	if (FAILED(hr))
		Logger::error("Failed to initialize DirectSound");

	// Set cooperative level to the engine window
	hr = g_DirectSound->SetCooperativeLevel(hWndHandle, DSSCL_PRIORITY);
	if (FAILED(hr))
		Logger::error("g_DirectSound->SetCooperativeLevel(...) Failed!");

	InitPrimaryBuffer();

	Logger::logPrint("SoundSystem_DSound: Created device successfully");
}

SoundSystem_DSound::~SoundSystem_DSound()
{
	ReleaseAllSounds();

	if (g_PrimaryBuffer)
	{
		g_PrimaryBuffer->Release();
		g_PrimaryBuffer = nullptr;
	}

	if (g_DirectSound)
	{
		g_DirectSound->Release();
		g_DirectSound = nullptr;
	}
}

void SoundSystem_DSound::InitPrimaryBuffer()
{
	// Create primary buffer.
	DSBUFFERDESC dsbd;
	memset(&dsbd, 0, sizeof(DSBUFFERDESC));
	dsbd.dwSize = sizeof(DSBUFFERDESC);
	dsbd.dwFlags = DSBCAPS_CTRL3D | DSBCAPS_PRIMARYBUFFER;
	dsbd.dwBufferBytes = 0;
	
	HRESULT hr = g_DirectSound->CreateSoundBuffer(&dsbd, &g_PrimaryBuffer, NULL);
	if (FAILED(hr))
		Logger::error("SoundSystem_DSound::InitPrimaryBuffer: Failed to create primary buffer");

	const int bitCount = 16;
	const int channelCount = 2;
	const int sampleRate22k = 22050;
	const int sampleRate44k = 44100;

	// calculate buffer format

	WAVEFORMATEX waveFormat;
	memset(&waveFormat, 0, sizeof(waveFormat));
	waveFormat.cbSize = sizeof(WAVEFORMATEX);
	waveFormat.wFormatTag = WAVE_FORMAT_PCM;
	waveFormat.nChannels = channelCount;
	waveFormat.nSamplesPerSec = sampleRate44k;
	waveFormat.nBlockAlign = channelCount * bitCount / 8;
	waveFormat.nAvgBytesPerSec = sampleRate44k * waveFormat.nBlockAlign;
	waveFormat.wBitsPerSample = sampleRate44k;

	// For safety only :)
	g_PrimaryBuffer->SetFormat(&waveFormat);
	g_PrimaryBuffer->Play(0, 0, DSBPLAY_LOOPING);

	Logger::logPrint("SoundSystem_DSound::InitPrimaryBuffer: Created primary buffer with %i sample rate", waveFormat.nSamplesPerSec);
}

void SoundSystem_DSound::selectDevice(SoundDeviceID deviceId)
{
	// Stub method
}

SoundDeviceID SoundSystem_DSound::selectedDevice() const
{
	// Stub method
	return 0;
}

// SOUND Creation

ISound* SoundSystem_DSound::CreateSound(const char* sSoundFile)
{
	Sound_DSound_Impl* pSoundImpl = new Sound_DSound_Impl(sSoundFile);
	m_Sounds.push_back(pSoundImpl);
	return pSoundImpl;
}

void SoundSystem_DSound::DestroySound(ISound*& pSound)
{
	// find sound in the cache

	std::vector< Sound_DSound_Impl* >::iterator it = std::find(m_Sounds.begin(), m_Sounds.end(), pSound);
	if (it != m_Sounds.end())
	{
		m_Sounds.erase( it );
	}

	// delete sound
	delete pSound;
	pSound = nullptr;
}

LONG  dsu_CalcVolume(const float Volume)
{
	LONG Result = 0;
	if (Volume == 0)
		Result = -10000;
	else
		Result = -round(1000 * log(1 / Volume));
	return Result;
}

void SoundSystem_DSound::Update(float delta)
{
	if (!g_PrimaryBuffer)
		Logger::error("SoundSystem_DSound::Update: Canno't update sound system without primary buffer.");

	g_PrimaryBuffer->SetVolume(dsu_CalcVolume(m_MasterVolume));

	int soundsNbr = m_Sounds.size();
	for (int i = 0; i < soundsNbr; i++)
	{
		// Sanity check
		if (m_Sounds[i])
			m_Sounds[i]->OnFrame( delta );
	}
}

void SoundSystem_DSound::StopAllSounds()
{
	int soundsNbr = m_Sounds.size();
	for (int i = 0; i < soundsNbr; i++)
	{
		// Sanity check
		if (m_Sounds[i])
			m_Sounds[i]->Stop();
	}
}

void SoundSystem_DSound::PauseAllSounds()
{
	int soundsNbr = m_Sounds.size();
	for (int i = 0; i < soundsNbr; i++)
	{
		// Sanity check
		if (m_Sounds[i])
			m_Sounds[i]->Pause();
	}
}

void SoundSystem_DSound::ReleaseAllSounds()
{
	int soundsNbr = m_Sounds.size();
	for (int i = 0; i < soundsNbr; i++)
	{
		if (m_Sounds[i])
		{
			delete m_Sounds[i];
			m_Sounds[i] = nullptr;
		}
	}

	m_Sounds.clear();

	Logger::logPrint("SoundSystem_DSound::ReleaseAllSounds: Cleared %i sounds", soundsNbr);
}

float SoundSystem_DSound::GetMasterVolume() const
{
	return m_MasterVolume;
}

void SoundSystem_DSound::SetMasterVolume(float fVolume)
{
	m_MasterVolume = fVolume;
}

// Sound System Factory
ISoundSystem* CreateSoundSystem_DirectSound(void* wndHandle)
{
	return new SoundSystem_DSound( (HWND)wndHandle );
}
