/********************************************************************
	Minecraft: Pocket Edition - Decompilation Project
	Copyright (C) 2023 iProgramInCpp

	The following code is licensed under the BSD 1 clause license.
	SPDX-License-Identifier: BSD-1-Clause
 ********************************************************************/

#define WIN32_LEAN_AND_MEAN
#include "SoundSystemDS.hpp"
#include "common/Utils.hpp"

// @TODO: fix crash in playAt when Asan is active

SoundSystemDS::SoundSystemDS()
{
	LOG_I("Init SoundSystemDS");

	HRESULT result;
	DSBUFFERDESC bufferDesc;
	m_available = false;


	result = DirectSoundCreate8(NULL, &m_directsound, NULL);
	if (FAILED(result))
	{
		LOG_E("SoundSystemDS failed to create DirectSound8 handle");
		return;
	}

	result = m_directsound->SetCooperativeLevel(GetHWND(), DSSCL_NORMAL);
	if (FAILED(result))
	{
		LOG_E("SoundSystemDS failed set cooperation level");
		return;
	}

	bufferDesc.dwSize = sizeof(DSBUFFERDESC);
	bufferDesc.dwFlags = DSBCAPS_CTRL3D | DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLVOLUME;
	bufferDesc.dwBufferBytes = 0;
	bufferDesc.dwReserved = 0;
	bufferDesc.lpwfxFormat = NULL;
	bufferDesc.guid3DAlgorithm = GUID_NULL;

	// Get control of the primary sound buffer on the default sound device.
	IDirectSoundBuffer* primaryBuffer;
	result = m_directsound->CreateSoundBuffer(&bufferDesc, &primaryBuffer, NULL);
	if (FAILED(result))
	{
		LOG_E("SoundSystemDS failed to create primary sound buffer");
		return;
	}

	result = primaryBuffer->QueryInterface(IID_IDirectSound3DListener8,
		(LPVOID*)&m_listener);
	primaryBuffer->Release();

	if (FAILED(result))
	{
		LOG_E("SoundSystemDS failed to create 3D listener\n");
	}

	m_available = true;
}



SoundSystemDS::~SoundSystemDS()
{
	LOG_I("Destroying SoundSystemDS");

	if (!isAvailable())
	{
		return;
	}

	m_directsound->Release();
}


bool SoundSystemDS::isAvailable()
{
	return m_available;
}

void SoundSystemDS::setListenerPos(float x, float y, float z)
{
	if (!isAvailable())
	{
		return;
	}
	m_listener->SetPosition(x, y, -z, DS3D_IMMEDIATE);
}


void SoundSystemDS::setListenerAngle(float degyaw, float degpitch)
{
	if (!isAvailable())
	{
		return;
	}

	float yaw = degyaw * M_PI / 180.f;
	float pitch = degpitch * M_PI / 180.f;

	float lx = cosf(pitch) * sinf(yaw);
	float ly = -sinf(pitch);
	float lz = cosf(yaw);

	float ux = sinf(pitch) * sinf(yaw);
	float uy = cosf(pitch);
	float uz = sinf(pitch) * cosf(yaw);

	m_listener->SetOrientation(-lx,-ly,-lz, ux,uy,uz, DS3D_IMMEDIATE);
}

void SoundSystemDS::load(const std::string& sound)
{
}

void SoundSystemDS::play(const std::string& sound)
{
}

void SoundSystemDS::pause(const std::string& sound)
{
}

void SoundSystemDS::stop(const std::string& sound)
{
}


void SoundSystemDS::playAt(const SoundDesc& sound, float x, float y, float z, float volume, float pitch)
{
	//Directsound failed to initialize return to avoid crash.
	if (!isAvailable())
	{
		return;
	}

	//Release sounds that finished playing
	for (size_t i = 0; i < m_buffers.size(); i++)
	{
		DWORD status;
		m_buffers[i].buffer->GetStatus(&status);
		if (status != DSBSTATUS_PLAYING) {
			m_buffers[i].buffer->Release();
			if (m_buffers[i].object3d != NULL)
			{
				m_buffers[i].object3d->Release();
			}
			m_buffers.erase(m_buffers.begin() + i);
			i--;
		}
	}

	HRESULT result;
	IDirectSoundBuffer* tempBuffer;
	unsigned char* bufferPtr;
	unsigned long bufferSize;

	int length = sound.m_pHeader->m_length * sound.m_pHeader->m_bytes_per_sample;
	bool is2D = sqrtf(x * x + y * y + z * z) == 0.f;

	//For some reason mojang made 3D sounds are REALLY quiet, with some of their volumes literally going below 0.1
	if (!is2D)
	{
		volume *= 5.f;
	}

	LPDIRECTSOUNDBUFFER soundbuffer; //= (LPDIRECTSOUNDBUFFER*)calloc(1, sizeof(LPDIRECTSOUNDBUFFER));

	WAVEFORMATEX waveFormat;
	waveFormat.wFormatTag = WAVE_FORMAT_PCM;
	waveFormat.nSamplesPerSec = DWORD(float(sound.m_pHeader->m_sample_rate) * pitch);
	waveFormat.wBitsPerSample = 8 * sound.m_pHeader->m_bytes_per_sample;
	waveFormat.nChannels = sound.m_pHeader->m_channels;
	waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels;
	waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
	waveFormat.cbSize = 0;

	// Set the buffer description of the secondary sound buffer that the wave file will be loaded onto.
	DSBUFFERDESC bufferDesc;
	bufferDesc.dwSize = sizeof(DSBUFFERDESC);

	//Because directsound does not support DSBCAPS_CTRL3D on a sound with 2 channels we can only do it on sounds with 1 channel
	if (sound.m_header.m_channels == 1)
	{
		bufferDesc.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRL3D;
	}
	else
	{
		bufferDesc.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_GLOBALFOCUS;
	}

	bufferDesc.dwBufferBytes = length;
	bufferDesc.dwReserved = 0;
	bufferDesc.lpwfxFormat = &waveFormat;
	bufferDesc.guid3DAlgorithm = GUID_NULL;

	// Create a temporary sound buffer with the specific buffer settings.
	result = m_directsound->CreateSoundBuffer(&bufferDesc, &tempBuffer, NULL);
	if (FAILED(result))
	{
		LOG_E("SoundSystemDS CreateSoundBuffer failed");
		return;
	}

	// Test the buffer format against the direct sound 8 interface and create the secondary buffer.
	result = tempBuffer->QueryInterface(IID_IDirectSoundBuffer8, (LPVOID*)&soundbuffer);
	if (FAILED(result))
	{
		LOG_E("SoundSystemDS tempBuffer QueryInterface failed");
		return;
	}

	// Release the temporary buffer.
	tempBuffer->Release();
	tempBuffer = 0;


	// Lock the secondary buffer to write wave data into it.
	result = soundbuffer->Lock(0, length, (void**)&bufferPtr, (DWORD*)&bufferSize, NULL, 0, 0);
	if (FAILED(result))
	{
		LOG_E("SoundSystemDS lock failed");
		return;
		//return false;
	}

	//Move the wave data into the buffer.
	memcpy(bufferPtr, sound.m_pData, length);

	// Unlock the secondary buffer after the data has been written to it.
	result = soundbuffer->Unlock((void*)bufferPtr, bufferSize, NULL, 0);
	if (FAILED(result))
	{
		LOG_E("SoundSystemDS unlock failed");
		return;
	}

	// references:
	// https://gamedev.net/forums/topic/337397-sound-volume-question-directsound/3243306/
	// https://learn.microsoft.com/en-us/previous-versions/windows/desktop/mt708939(v=vs.85)
	// Conversion from 0-1 linear volume to directsound logarithmic volume..
	// This seems to work for the most part, but accuracy testing should be done for actual MCPE, water splashing is pretty quiet.
	float attenuation = volume;//Lerp(DSBVOLUME_MIN, DSBVOLUME_MAX, volume);
	// clamp the attenuation value
	if (attenuation < 0.0f)
		attenuation = 0.0f;
	else if (attenuation > 1.0f)
		attenuation = 1.0f;

	if (attenuation == 0)
	{
		// no sound would come out, maybe skip playing this sound?
		attenuation = DSBVOLUME_MIN;
	}
	else
	{
		attenuation = floorf(2000.0f * log10f(attenuation) + 0.5f);
	}
	soundbuffer->SetVolume(LONG(attenuation));

	BufferInfo info;
	info.buffer = soundbuffer;
	info.object3d = NULL;

	//Check if position is not 0,0,0 and for mono to play 3D sound
	if (!is2D && sound.m_pHeader->m_channels == 1) 
	{
		LPDIRECTSOUND3DBUFFER8 object3d;

		HRESULT hr = soundbuffer->QueryInterface(IID_IDirectSound3DBuffer8,
			(LPVOID*)&object3d);
		if (FAILED(hr)) {
			LOG_E("SoundSystemDS QueryInterface failed for 3D Object");
			return;
		}

		object3d->SetPosition(
			x, 
			y,
			-z, 
		DS3D_IMMEDIATE); 

		//Im not really sure what values original MCPE would use.
		object3d->SetMinDistance(2.f, DS3D_IMMEDIATE); 
		object3d->SetMaxDistance(100.f, DS3D_IMMEDIATE);

		info.object3d = object3d;
	}

	soundbuffer->Play(0, 0, 0);

	m_buffers.push_back(info);
}