#include "stdafx.h"

#if MPT_COMPILER_MSVC
#pragma warning(disable:4800) // 'T' : forcing value to bool 'true' or 'false' (performance warning)
#endif // MPT_COMPILER_MSVC

#include "NativeSoundDevice.h"
#include "NativeUtils.h"

#include "openmpt/sounddevice/SoundDevice.hpp"
#include "openmpt/sounddevice/SoundDeviceManager.hpp"
#include "openmpt/sounddevice/SoundDeviceUtilities.hpp"

#include "../../common/ComponentManager.h"

#include "../../misc/mptOS.h"

#include "NativeSoundDeviceMarshalling.h"

#include <string>
#include <type_traits>

#include <cstdint>
#include <cstdlib>
#include <cstring>

#include <stdint.h>
#include <stdlib.h>
#include <string.h>

OPENMPT_NAMESPACE_BEGIN

namespace C {

class ComponentSoundDeviceManager
	: public ComponentBuiltin
{
	MPT_DECLARE_COMPONENT_MEMBERS(ComponentSoundDeviceManager, "SoundDeviceManager")
private:
	mpt::log::GlobalLogger logger;
	SoundDevice::Manager manager;
private:
	static SoundDevice::SysInfo GetSysInfo()
	{
		mpt::OS::Wine::VersionContext wineVersionContext;
		return SoundDevice::SysInfo(mpt::osinfo::get_class(), mpt::OS::Windows::Version::Current(), mpt::OS::Windows::IsWine(), wineVersionContext.HostClass(), wineVersionContext.Version());
	}

public:
	ComponentSoundDeviceManager()
		: manager(logger, GetSysInfo(), SoundDevice::AppInfo())
	{
		return;
	}
	virtual ~ComponentSoundDeviceManager() { }
	bool DoInitialize() override
	{
		return true;
	}
	SoundDevice::Manager & get() const
	{
		return const_cast<SoundDevice::Manager &>(manager);
	}
};

static mpt::ustring GetTypePrefix()
{
	return U_("Native");
}

static SoundDevice::Info AddTypePrefix(SoundDevice::Info info)
{
	info.type = GetTypePrefix() + U_("-") + info.type;
	info.apiPath.insert(info.apiPath.begin(), U_("Native"));
	return info;
}

static SoundDevice::Info RemoveTypePrefix(SoundDevice::Info info)
{
	info.type = info.type.substr(GetTypePrefix().length() + 1);
	info.apiPath.erase(info.apiPath.begin());
	return info;
}

std::string SoundDevice_EnumerateDevices()
{
	ComponentHandle<ComponentSoundDeviceManager> manager;
	if(!IsComponentAvailable(manager))
	{
		return std::string();
	}
	std::vector<SoundDevice::Info> infos = std::vector<SoundDevice::Info>(manager->get().begin(), manager->get().end());
	for(auto &info : infos)
	{
		info = AddTypePrefix(info);
	}
	return json_cast<std::string>(infos);
}

SoundDevice::IBase * SoundDevice_Construct(std::string info_)
{
	MPT_LOG_GLOBAL(LogDebug, "NativeSupport", MPT_UFORMAT("Contruct: {}")(mpt::ToUnicode(mpt::Charset::UTF8, info_)));
	ComponentHandle<ComponentSoundDeviceManager> manager;
	if(!IsComponentAvailable(manager))
	{
		return nullptr;
	}
	SoundDevice::Info info = json_cast<SoundDevice::Info>(info_);
	info = RemoveTypePrefix(info);
	return manager->get().CreateSoundDevice(info.GetIdentifier());
}

class NativeMessageReceiverProxy
	: public SoundDevice::IMessageReceiver
{
private:
	OpenMPT_SoundDevice_IMessageReceiver impl;
public:
	NativeMessageReceiverProxy(const OpenMPT_SoundDevice_IMessageReceiver * impl_)
	{
		MemsetZero(impl);
		if(impl_)
		{
			impl = *impl_;
		}
	}
	virtual ~NativeMessageReceiverProxy()
	{
		return;
	}
public:
	virtual void SoundDeviceMessage(LogLevel level, const mpt::ustring &str)
	{
		if(!impl.SoundDeviceMessageFunc)
		{
			return;
		}
		return impl.SoundDeviceMessageFunc(impl.inst, level, mpt::ToCharset(mpt::Charset::UTF8, str).c_str());
	}
};

class NativeCallbackProxy
	: public SoundDevice::ICallback
{
private:
	OpenMPT_SoundDevice_ICallback impl;
public:
	NativeCallbackProxy(const OpenMPT_SoundDevice_ICallback * impl_)
	{
		MemsetZero(impl);
		if(impl_)
		{
			impl = *impl_;
		}
	}
	virtual ~NativeCallbackProxy()
	{
		return;
	}
public:
	// main thread
	virtual uint64 SoundCallbackGetReferenceClockNowNanoseconds() const
	{
		if(!impl.SoundCallbackGetReferenceClockNowNanosecondsFunc)
		{
			return 0;
		}
		uint64_t result = 0;
		impl.SoundCallbackGetReferenceClockNowNanosecondsFunc(impl.inst, &result);
		return result;
	}
	virtual void SoundCallbackPreStart()
	{
		if(!impl.SoundCallbackPreStartFunc)
		{
			return;
		}
		return impl.SoundCallbackPreStartFunc(impl.inst);
	}
	virtual void SoundCallbackPostStop()
	{
		if(!impl.SoundCallbackPostStopFunc)
		{
			return;
		}
		return impl.SoundCallbackPostStopFunc(impl.inst);
	}
	virtual bool SoundCallbackIsLockedByCurrentThread() const
	{
		if(!impl.SoundCallbackIsLockedByCurrentThreadFunc)
		{
			return 0;
		}
		uintptr_t result = 0;
		impl.SoundCallbackIsLockedByCurrentThreadFunc(impl.inst, &result);
		return result;
	}
	// audio thread
	virtual void SoundCallbackLock()
	{
		if(!impl.SoundCallbackLockFunc)
		{
			return;
		}
		return impl.SoundCallbackLockFunc(impl.inst);
	}
	virtual uint64 SoundCallbackLockedGetReferenceClockNowNanoseconds() const
	{
		if(!impl.SoundCallbackLockedGetReferenceClockNowNanosecondsFunc)
		{
			return 0;
		}
		uint64_t result = 0;
		impl.SoundCallbackLockedGetReferenceClockNowNanosecondsFunc(impl.inst, &result);
		return result;
	}
	virtual void SoundCallbackLockedProcessPrepare(SoundDevice::TimeInfo timeInfo)
	{
		if(!impl.SoundCallbackLockedProcessPrepareFunc)
		{
			return;
		}
		OpenMPT_SoundDevice_TimeInfo c_timeInfo = C::encode(timeInfo);
		return impl.SoundCallbackLockedProcessPrepareFunc(impl.inst, &c_timeInfo);
	}
	virtual void SoundCallbackLockedProcess(SoundDevice::BufferFormat bufferFormat, std::size_t numFrames, uint8 *buffer, const uint8 *inputBuffer)
	{
		if(!impl.SoundCallbackLockedProcessUint8Func)
		{
			return;
		}
		OpenMPT_SoundDevice_BufferFormat c_bufferFormat = C::encode(bufferFormat);
		return impl.SoundCallbackLockedProcessUint8Func(impl.inst, &c_bufferFormat, numFrames, buffer, inputBuffer);
	}
	virtual void SoundCallbackLockedProcess(SoundDevice::BufferFormat bufferFormat, std::size_t numFrames, int8 *buffer, const int8 *inputBuffer)
	{
		if(!impl.SoundCallbackLockedProcessInt8Func)
		{
			return;
		}
		OpenMPT_SoundDevice_BufferFormat c_bufferFormat = C::encode(bufferFormat);
		return impl.SoundCallbackLockedProcessInt8Func(impl.inst, &c_bufferFormat, numFrames, buffer, inputBuffer);
	}
	virtual void SoundCallbackLockedProcess(SoundDevice::BufferFormat bufferFormat, std::size_t numFrames, int16 *buffer, const int16 *inputBuffer)
	{
		if(!impl.SoundCallbackLockedProcessInt16Func)
		{
			return;
		}
		OpenMPT_SoundDevice_BufferFormat c_bufferFormat = C::encode(bufferFormat);
		return impl.SoundCallbackLockedProcessInt16Func(impl.inst, &c_bufferFormat, numFrames, buffer, inputBuffer);
	}
	virtual void SoundCallbackLockedProcess(SoundDevice::BufferFormat bufferFormat, std::size_t numFrames, int24 *buffer, const int24 *inputBuffer)
	{
		if(!impl.SoundCallbackLockedProcessInt24Func)
		{
			return;
		}
		OpenMPT_SoundDevice_BufferFormat c_bufferFormat = C::encode(bufferFormat);
		return impl.SoundCallbackLockedProcessInt24Func(impl.inst, &c_bufferFormat, numFrames, buffer, inputBuffer);
	}
	virtual void SoundCallbackLockedProcess(SoundDevice::BufferFormat bufferFormat, std::size_t numFrames, int32 *buffer, const int32 *inputBuffer)
	{
		if(!impl.SoundCallbackLockedProcessInt32Func)
		{
			return;
		}
		OpenMPT_SoundDevice_BufferFormat c_bufferFormat = C::encode(bufferFormat);
		return impl.SoundCallbackLockedProcessInt32Func(impl.inst, &c_bufferFormat, numFrames, buffer, inputBuffer);
	}
	virtual void SoundCallbackLockedProcess(SoundDevice::BufferFormat bufferFormat, std::size_t numFrames, float *buffer, const float *inputBuffer)
	{
		if(!impl.SoundCallbackLockedProcessFloatFunc)
		{
			return;
		}
		OpenMPT_SoundDevice_BufferFormat c_bufferFormat = C::encode(bufferFormat);
		return impl.SoundCallbackLockedProcessFloatFunc(impl.inst, &c_bufferFormat, numFrames, buffer, inputBuffer);
	}
	virtual void SoundCallbackLockedProcess(SoundDevice::BufferFormat bufferFormat, std::size_t numFrames, double *buffer, const double *inputBuffer)
	{
		if(!impl.SoundCallbackLockedProcessDoubleFunc)
		{
			return;
		}
		OpenMPT_SoundDevice_BufferFormat c_bufferFormat = C::encode(bufferFormat);
		return impl.SoundCallbackLockedProcessDoubleFunc(impl.inst, &c_bufferFormat, numFrames, buffer, inputBuffer);
	}
	virtual void SoundCallbackLockedProcessDone(SoundDevice::TimeInfo timeInfo)
	{
		if(!impl.SoundCallbackLockedProcessDoneFunc)
		{
			return;
		}
		OpenMPT_SoundDevice_TimeInfo c_timeInfo = C::encode(timeInfo);
		return impl.SoundCallbackLockedProcessDoneFunc(impl.inst, &c_timeInfo);
	}
	virtual void SoundCallbackUnlock()
	{
		if(!impl.SoundCallbackUnlockFunc)
		{
			return;
		}
		return impl.SoundCallbackUnlockFunc(impl.inst);
	}
};

} // namespace C

OPENMPT_NAMESPACE_END

extern "C" {

struct OpenMPT_SoundDevice {
	OPENMPT_NAMESPACE::SoundDevice::IBase * impl;
	OPENMPT_NAMESPACE::C::NativeMessageReceiverProxy * messageReceiver;
	OPENMPT_NAMESPACE::C::NativeCallbackProxy * callback;
};

OPENMPT_WINESUPPORT_API char * OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_EnumerateDevices() {
	return OpenMPT_String_Duplicate( OPENMPT_NAMESPACE::C::SoundDevice_EnumerateDevices().c_str() );
}

OPENMPT_WINESUPPORT_API OpenMPT_SoundDevice * OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_Construct( const char * info ) {
	if ( !info ) {
		return nullptr;
	}
	OpenMPT_SoundDevice * result = reinterpret_cast< OpenMPT_SoundDevice * >( OpenMPT_Alloc( sizeof( OpenMPT_SoundDevice ) ) );
	if ( !result ) {
		return nullptr;
	}
	result->impl = OPENMPT_NAMESPACE::C::SoundDevice_Construct( info );
	if ( !result->impl ) {
		OpenMPT_Free( result );
		return nullptr;
	}
	return result;
}

OPENMPT_WINESUPPORT_API void OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_Destruct( OpenMPT_SoundDevice * sd ) {
	if ( sd ) {
		if ( sd->impl ) {
			sd->impl->SetMessageReceiver( nullptr );
			delete sd->messageReceiver;
			sd->messageReceiver = nullptr;
			delete sd->impl;
			sd->impl = nullptr;
		}
		OpenMPT_Free( sd );
	}
}

OPENMPT_WINESUPPORT_API void OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_SetMessageReceiver( OpenMPT_SoundDevice * sd, const OpenMPT_SoundDevice_IMessageReceiver * receiver ) {
	if ( !sd ) {
		return;
	}
	if ( !sd->impl ) {
		return;
	}
	sd->impl->SetMessageReceiver( nullptr );
	delete sd->messageReceiver;
	sd->messageReceiver = nullptr;
	sd->messageReceiver = new OPENMPT_NAMESPACE::C::NativeMessageReceiverProxy( receiver );
	sd->impl->SetMessageReceiver( sd->messageReceiver );
	return;
}

OPENMPT_WINESUPPORT_API void OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_SetCallback( OpenMPT_SoundDevice * sd, const OpenMPT_SoundDevice_ICallback * callback ) {
	if ( !sd ) {
		return;
	}
	if ( !sd->impl ) {
		return;
	}
	sd->impl->SetCallback( nullptr );
	delete sd->callback;
	sd->callback = nullptr;
	sd->callback = new OPENMPT_NAMESPACE::C::NativeCallbackProxy( callback );
	sd->impl->SetCallback( sd->callback );
	return;
}

OPENMPT_WINESUPPORT_API char * OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_GetDeviceInfo( const OpenMPT_SoundDevice * sd ) {
	OPENMPT_NAMESPACE::SoundDevice::Info info = sd->impl->GetDeviceInfo();
	info = OPENMPT_NAMESPACE::C::AddTypePrefix(info);
	return OpenMPT_String_Duplicate( OPENMPT_NAMESPACE::json_cast<std::string>( sd->impl->GetDeviceInfo() ).c_str() );
}

OPENMPT_WINESUPPORT_API char * OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_GetDeviceCaps( const OpenMPT_SoundDevice * sd ) {
	return OpenMPT_String_Duplicate( OPENMPT_NAMESPACE::json_cast<std::string>( sd->impl->GetDeviceCaps() ).c_str() );
}

OPENMPT_WINESUPPORT_API char * OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_GetDeviceDynamicCaps( OpenMPT_SoundDevice * sd, const char * baseSampleRates ) {
	return OpenMPT_String_Duplicate( OPENMPT_NAMESPACE::json_cast<std::string>( sd->impl->GetDeviceDynamicCaps( OPENMPT_NAMESPACE::json_cast<std::vector<OPENMPT_NAMESPACE::uint32> >( std::string(baseSampleRates) ) ) ).c_str() );
}

OPENMPT_WINESUPPORT_API uintptr_t OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_Init( OpenMPT_SoundDevice * sd, const char * appInfo ) {
	return sd->impl->Init( OPENMPT_NAMESPACE::json_cast<OPENMPT_NAMESPACE::SoundDevice::AppInfo>( std::string(appInfo) ) );
}

OPENMPT_WINESUPPORT_API uintptr_t OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_Open( OpenMPT_SoundDevice * sd, const char * settings ) {
	return sd->impl->Open( OPENMPT_NAMESPACE::json_cast<OPENMPT_NAMESPACE::SoundDevice::Settings>( std::string(settings) ) );
}

OPENMPT_WINESUPPORT_API uintptr_t OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_Close( OpenMPT_SoundDevice * sd ) {
	return sd->impl->Close();
}

OPENMPT_WINESUPPORT_API uintptr_t OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_Start( OpenMPT_SoundDevice * sd ) {
	return sd->impl->Start();
}

OPENMPT_WINESUPPORT_API void OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_Stop( OpenMPT_SoundDevice * sd ) {
	return sd->impl->Stop();
}

OPENMPT_WINESUPPORT_API void OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_GetRequestFlags( const OpenMPT_SoundDevice * sd, uint32_t * result) {
	*result = sd->impl->GetRequestFlags().GetRaw();
}

OPENMPT_WINESUPPORT_API uintptr_t OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_IsInited( const OpenMPT_SoundDevice * sd ) {
	return sd->impl->IsInited();
}

OPENMPT_WINESUPPORT_API uintptr_t OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_IsOpen( const OpenMPT_SoundDevice * sd ) {
	return sd->impl->IsOpen();
}

OPENMPT_WINESUPPORT_API uintptr_t OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_IsAvailable( const OpenMPT_SoundDevice * sd ) {
	return sd->impl->IsAvailable();
}

OPENMPT_WINESUPPORT_API uintptr_t OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_IsPlaying( const OpenMPT_SoundDevice * sd ) {
	return sd->impl->IsPlaying();
}

OPENMPT_WINESUPPORT_API uintptr_t OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_IsPlayingSilence( const OpenMPT_SoundDevice * sd ) {
	return sd->impl->IsPlayingSilence();
}

OPENMPT_WINESUPPORT_API void OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_StopAndAvoidPlayingSilence( OpenMPT_SoundDevice * sd ) {
	return sd->impl->StopAndAvoidPlayingSilence();
}

OPENMPT_WINESUPPORT_API void OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_EndPlayingSilence( OpenMPT_SoundDevice * sd ) {
	return sd->impl->EndPlayingSilence();
}

OPENMPT_WINESUPPORT_API uintptr_t OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_OnIdle( OpenMPT_SoundDevice * sd ) {
	return sd->impl->OnIdle();
}

OPENMPT_WINESUPPORT_API char * OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_GetSettings( const OpenMPT_SoundDevice * sd ) {
	return OpenMPT_String_Duplicate( OPENMPT_NAMESPACE::json_cast<std::string>( sd->impl->GetSettings() ).c_str() );
}

OPENMPT_WINESUPPORT_API void OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_GetActualSampleFormat( const OpenMPT_SoundDevice * sd, int32_t * result ) {
	*result = OPENMPT_NAMESPACE::mpt::to_underlying<OPENMPT_NAMESPACE::SampleFormat::Enum>(sd->impl->GetActualSampleFormat());
}

OPENMPT_WINESUPPORT_API void OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_GetEffectiveBufferAttributes( const OpenMPT_SoundDevice * sd, OpenMPT_SoundDevice_BufferAttributes * result ) {
	*result = OPENMPT_NAMESPACE::C::encode( sd->impl->GetEffectiveBufferAttributes() );
}

OPENMPT_WINESUPPORT_API void OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_GetTimeInfo( const OpenMPT_SoundDevice * sd, OpenMPT_SoundDevice_TimeInfo * result ) {
	*result = OPENMPT_NAMESPACE::C::encode( sd->impl->GetTimeInfo() );
}

OPENMPT_WINESUPPORT_API void OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_GetStreamPosition( const OpenMPT_SoundDevice * sd, OpenMPT_SoundDevice_StreamPosition * result ) {
	*result = OPENMPT_NAMESPACE::C::encode( sd->impl->GetStreamPosition() );
}

OPENMPT_WINESUPPORT_API uintptr_t OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_DebugIsFragileDevice( const OpenMPT_SoundDevice * sd ) {
	return sd->impl->DebugIsFragileDevice();
}

OPENMPT_WINESUPPORT_API uintptr_t OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_DebugInRealtimeCallback( const OpenMPT_SoundDevice * sd ) {
	return sd->impl->DebugInRealtimeCallback();
}

OPENMPT_WINESUPPORT_API char * OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_GetStatistics( const OpenMPT_SoundDevice * sd ) {
	return OpenMPT_String_Duplicate( OPENMPT_NAMESPACE::json_cast<std::string>( sd->impl->GetStatistics() ).c_str() );
}

OPENMPT_WINESUPPORT_API uintptr_t OPENMPT_WINESUPPORT_CALL OpenMPT_SoundDevice_OpenDriverSettings( OpenMPT_SoundDevice * sd ) {
	return sd->impl->OpenDriverSettings();
}

typedef struct OpenMPT_PriorityBooster {
#ifndef _MSC_VER
	OPENMPT_NAMESPACE::SoundDevice::ThreadPriorityGuard * impl;
#else
	void * dummy;
#endif
} OpenMPT_PriorityBooster;

OPENMPT_WINESUPPORT_API OpenMPT_PriorityBooster * OPENMPT_WINESUPPORT_CALL OpenMPT_PriorityBooster_Construct_From_SoundDevice( const OpenMPT_SoundDevice * sd ) {
#if !MPT_OS_WINDOWS
	OpenMPT_PriorityBooster * pb = (OpenMPT_PriorityBooster*)OpenMPT_Alloc( sizeof( OpenMPT_PriorityBooster ) );
	pb->impl = new OPENMPT_NAMESPACE::SoundDevice::ThreadPriorityGuard
		( dynamic_cast<OPENMPT_NAMESPACE::SoundDevice::Base*>(sd->impl)->GetLogger()
		, dynamic_cast<OPENMPT_NAMESPACE::SoundDevice::Base*>(sd->impl)->GetSettings().BoostThreadPriority
		, dynamic_cast<OPENMPT_NAMESPACE::SoundDevice::Base*>(sd->impl)->GetAppInfo().BoostedThreadRealtimePosix
		, dynamic_cast<OPENMPT_NAMESPACE::SoundDevice::Base*>(sd->impl)->GetAppInfo().BoostedThreadNicenessPosix
		, dynamic_cast<OPENMPT_NAMESPACE::SoundDevice::Base*>(sd->impl)->GetAppInfo().BoostedThreadRtprioPosix
		);
	return pb;
#else
	MPT_UNREFERENCED_PARAMETER(sd);
	return nullptr;
#endif
}

OPENMPT_WINESUPPORT_API void OPENMPT_WINESUPPORT_CALL OpenMPT_PriorityBooster_Destruct( OpenMPT_PriorityBooster * pb ) {
#if !MPT_OS_WINDOWS
	delete pb->impl;
	pb->impl = nullptr;
	OpenMPT_Free( pb );
#else
	MPT_UNREFERENCED_PARAMETER(pb);
#endif
}

} // extern "C"