/*
 * BridgeWrapper.cpp
 * -----------------
 * Purpose: VST plugin bridge wrapper (host side)
 * Notes  : (currently none)
 * Authors: OpenMPT Devs
 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
 */


#include "stdafx.h"

#ifdef MPT_WITH_VST
#include "BridgeWrapper.h"
#include "../soundlib/plugins/PluginManager.h"
#include "../mptrack/Mainfrm.h"
#include "../mptrack/Mptrack.h"
#include "../mptrack/Vstplug.h"
#include "../mptrack/ExceptionHandler.h"
#include "../common/mptFileIO.h"
#include "../common/mptStringBuffer.h"
#include "../common/misc_util.h"

using namespace Vst;


OPENMPT_NAMESPACE_BEGIN

std::vector<BridgeCommon *> BridgeCommon::m_plugins;
HWND BridgeCommon::m_communicationWindow = nullptr;
int BridgeCommon::m_instanceCount = 0;
thread_local bool BridgeCommon::m_isAudioThread = false;

std::size_t GetPluginArchPointerSize(PluginArch arch)
{
	std::size_t result = 0;
	switch(arch)
	{
	case PluginArch_x86:
		result = 4;
		break;
	case PluginArch_amd64:
		result = 8;
		break;
	case PluginArch_arm:
		result = 4;
		break;
	case PluginArch_arm64:
		result = 8;
		break;
	default:
		result = 0;
		break;
	}
	return result;
}


ComponentPluginBridge::ComponentPluginBridge(PluginArch arch, Generation generation)
	: ComponentBase(ComponentTypeBundled)
	, arch(arch)
	, generation(generation)
{
}


bool ComponentPluginBridge::DoInitialize()
{
	mpt::PathString archName;
	switch(arch)
	{
	case PluginArch_x86:
		if(mpt::OS::Windows::HostCanRun(mpt::OS::Windows::GetHostArchitecture(), mpt::OS::Windows::Architecture::x86) == mpt::OS::Windows::EmulationLevel::NA)
		{
			return false;
		}
		archName = P_("x86");
		break;
	case PluginArch_amd64:
		if(mpt::OS::Windows::HostCanRun(mpt::OS::Windows::GetHostArchitecture(), mpt::OS::Windows::Architecture::amd64) == mpt::OS::Windows::EmulationLevel::NA)
		{
			return false;
		}
		archName = P_("amd64");
		break;
	case PluginArch_arm:
		if(mpt::OS::Windows::HostCanRun(mpt::OS::Windows::GetHostArchitecture(), mpt::OS::Windows::Architecture::arm) == mpt::OS::Windows::EmulationLevel::NA)
		{
			return false;
		}
		archName = P_("arm");
		break;
	case PluginArch_arm64:
		if(mpt::OS::Windows::HostCanRun(mpt::OS::Windows::GetHostArchitecture(), mpt::OS::Windows::Architecture::arm64) == mpt::OS::Windows::EmulationLevel::NA)
		{
			return false;
		}
		archName = P_("arm64");
		break;
	default:
		break;
	}
	if(archName.empty())
	{
		return false;
	}
	exeName = mpt::PathString();
	const mpt::PathString generationSuffix = (generation == Generation::Legacy) ? P_("Legacy") : P_("");
	const mpt::PathString exeNames[] =
	{
		theApp.GetInstallPath() + P_("PluginBridge") + generationSuffix + P_("-") + archName + P_(".exe"),                          // Local
		theApp.GetInstallBinPath() + archName + P_("\\") + P_("PluginBridge") + generationSuffix + P_(".exe"),                      // Multi-arch
		theApp.GetInstallBinPath() + archName + P_("\\") + P_("PluginBridge") + generationSuffix + P_("-") + archName + P_(".exe")  // Multi-arch transitional
	};
	for(const auto &candidate : exeNames)
	{
		if(candidate.IsFile())
		{
			exeName = candidate;
			break;
		}
	}
	if(exeName.empty())
	{
		availability = AvailabilityMissing;
		return false;
	}
	std::vector<WCHAR> exePath(MAX_PATH);
	while(GetModuleFileNameW(0, exePath.data(), mpt::saturate_cast<DWORD>(exePath.size())) >= exePath.size())
	{
		exePath.resize(exePath.size() * 2);
	}
	uint64 mptVersion = BridgeWrapper::GetFileVersion(exePath.data());
	uint64 bridgeVersion = BridgeWrapper::GetFileVersion(exeName.ToWide().c_str());
	if(bridgeVersion != mptVersion)
	{
		availability = AvailabilityWrongVersion;
		return false;
	}
	availability = AvailabilityOK;
	return true;
}


PluginArch BridgeWrapper::GetNativePluginBinaryType()
{
	PluginArch result = PluginArch_unknown;
	switch(mpt::OS::Windows::GetProcessArchitecture())
	{
	case mpt::OS::Windows::Architecture::x86:
		result = PluginArch_x86;
		break;
	case mpt::OS::Windows::Architecture::amd64:
		result = PluginArch_amd64;
		break;
	case mpt::OS::Windows::Architecture::arm:
		result = PluginArch_arm;
		break;
	case mpt::OS::Windows::Architecture::arm64:
		result = PluginArch_arm64;
		break;
	default:
		result = PluginArch_unknown;
		break;
	}
	return result;
}


// Check whether we need to load a 32-bit or 64-bit wrapper.
PluginArch BridgeWrapper::GetPluginBinaryType(const mpt::PathString &pluginPath)
{
	PluginArch type = PluginArch_unknown;
	mpt::ifstream file(pluginPath, std::ios::in | std::ios::binary);
	if(file.is_open())
	{
		IMAGE_DOS_HEADER dosHeader;
		IMAGE_NT_HEADERS ntHeader;
		file.read(reinterpret_cast<char *>(&dosHeader), sizeof(dosHeader));
		if(dosHeader.e_magic == IMAGE_DOS_SIGNATURE)
		{
			file.seekg(dosHeader.e_lfanew);
			file.read(reinterpret_cast<char *>(&ntHeader), sizeof(ntHeader));

			MPT_ASSERT((ntHeader.FileHeader.Characteristics & IMAGE_FILE_DLL) != 0);
			switch(ntHeader.FileHeader.Machine)
			{
			case IMAGE_FILE_MACHINE_I386:
				type = PluginArch_x86;
				break;
			case IMAGE_FILE_MACHINE_AMD64:
				type = PluginArch_amd64;
				break;
#if defined(MPT_WITH_WINDOWS10)
			case IMAGE_FILE_MACHINE_ARM:
				type = PluginArch_arm;
				break;
			case IMAGE_FILE_MACHINE_ARM64:
				type = PluginArch_arm64;
				break;
#endif  // MPT_WITH_WINDOWS10
			default:
				type = PluginArch_unknown;
				break;
			}
		}
	}
	return type;
}


uint64 BridgeWrapper::GetFileVersion(const WCHAR *exePath)
{
	DWORD verHandle = 0;
	DWORD verSize = GetFileVersionInfoSizeW(exePath, &verHandle);
	uint64 result = 0;
	if(verSize == 0)
	{
		return result;
	}

	char *verData = new(std::nothrow) char[verSize];
	if(verData && GetFileVersionInfoW(exePath, verHandle, verSize, verData))
	{
		UINT size = 0;
		void *lpBuffer = nullptr;
		if(VerQueryValue(verData, _T("\\"), &lpBuffer, &size) && size != 0)
		{
			auto *verInfo = static_cast<const VS_FIXEDFILEINFO *>(lpBuffer);
			if(verInfo->dwSignature == 0xfeef04bd)
			{
				result = (uint64(HIWORD(verInfo->dwFileVersionMS)) << 48)
				         | (uint64(LOWORD(verInfo->dwFileVersionMS)) << 32)
				         | (uint64(HIWORD(verInfo->dwFileVersionLS)) << 16)
				         | uint64(LOWORD(verInfo->dwFileVersionLS));
			}
		}
	}
	delete[] verData;
	return result;
}


// Create a plugin bridge object
AEffect *BridgeWrapper::Create(const VSTPluginLib &plugin, bool forceLegacy)
{
	BridgeWrapper *wrapper = new(std::nothrow) BridgeWrapper();
	BridgeWrapper *sharedInstance = nullptr;
	const Generation wantedGeneration = (plugin.modernBridge && !forceLegacy) ? Generation::Modern : Generation::Legacy;

	// Should we share instances?
	if(plugin.shareBridgeInstance)
	{
		// Well, then find some instance to share with!
		CVstPlugin *vstPlug = dynamic_cast<CVstPlugin *>(plugin.pPluginsList);
		while(vstPlug != nullptr)
		{
			if(vstPlug->isBridged)
			{
				BridgeWrapper *instance = FromIntPtr<BridgeWrapper>(vstPlug->Dispatch(effVendorSpecific, kVendorOpenMPT, kGetWrapperPointer, nullptr, 0.0f));
				if(wantedGeneration == instance->m_Generation)
				{
					sharedInstance = instance;
					break;
				}
			}
			vstPlug = dynamic_cast<CVstPlugin *>(vstPlug->GetNextInstance());
		}
	}

	try
	{
		if(wrapper != nullptr && wrapper->Init(plugin.dllPath, wantedGeneration, sharedInstance) && wrapper->m_queueMem.Good())
		{
			return &wrapper->m_sharedMem->effect;
		}
		delete wrapper;
		return nullptr;
	} catch(BridgeException &)
	{
		delete wrapper;
		throw;
	}
}


BridgeWrapper::BridgeWrapper()
{
	m_thisPluginID = static_cast<int32>(m_plugins.size());
	m_plugins.push_back(this);

	if(m_instanceCount == 1)
		CreateCommunicationWindow(WindowProc);
}


BridgeWrapper::~BridgeWrapper()
{
	if(m_instanceCount == 1)
		DestroyWindow(m_communicationWindow);
}


// Initialize and launch bridge
bool BridgeWrapper::Init(const mpt::PathString &pluginPath, Generation bridgeGeneration, BridgeWrapper *sharedInstace)
{
	static uint32 plugId = 0;
	plugId++;
	const DWORD procId = GetCurrentProcessId();

	const std::wstring mapName = MPT_WFORMAT("Local\\openmpt-{}-{}")(procId, plugId);

	// Create our shared memory object.
	if(!m_queueMem.Create(mapName.c_str(), sizeof(SharedMemLayout))
	   || !CreateSignals(mapName.c_str()))
	{
		throw BridgeException("Could not initialize plugin bridge memory.");
	}
	m_sharedMem = m_queueMem.Data<SharedMemLayout>();

	if(sharedInstace == nullptr)
	{
		// Create a new bridge instance
		const PluginArch arch = GetPluginBinaryType(pluginPath);
		bool available = false;
		ComponentPluginBridge::Availability availability = ComponentPluginBridge::AvailabilityUnknown;
		switch(arch)
		{
		case PluginArch_x86:
			if(available = (bridgeGeneration == Generation::Modern) ? IsComponentAvailable(pluginBridge_x86) : IsComponentAvailable(pluginBridgeLegacy_x86); !available)
				availability = (bridgeGeneration == Generation::Modern) ? pluginBridge_x86->GetAvailability() : pluginBridgeLegacy_x86->GetAvailability();
			break;
		case PluginArch_amd64:
			if(available = (bridgeGeneration == Generation::Modern) ? IsComponentAvailable(pluginBridge_amd64) : IsComponentAvailable(pluginBridgeLegacy_amd64); !available)
				availability = (bridgeGeneration == Generation::Modern) ? pluginBridge_amd64->GetAvailability() : pluginBridgeLegacy_amd64->GetAvailability();
			break;
#if defined(MPT_WITH_WINDOWS10)
		case PluginArch_arm:
			if(available = (bridgeGeneration == Generation::Modern) ? IsComponentAvailable(pluginBridge_arm) : IsComponentAvailable(pluginBridgeLegacy_arm); !available)
				availability = (bridgeGeneration == Generation::Modern) ? pluginBridge_arm->GetAvailability() : pluginBridgeLegacy_arm->GetAvailability();
			break;
		case PluginArch_arm64:
			if(available = (bridgeGeneration == Generation::Modern) ? IsComponentAvailable(pluginBridge_arm64) : IsComponentAvailable(pluginBridgeLegacy_arm64); !available)
				availability = (bridgeGeneration == Generation::Modern) ? pluginBridge_arm64->GetAvailability() : pluginBridgeLegacy_arm64->GetAvailability();
			break;
#endif  // MPT_WITH_WINDOWS10
		default:
			break;
		}
		if(arch == PluginArch_unknown)
		{
			return false;
		}
		if(!available)
		{
			switch(availability)
			{
			case ComponentPluginBridge::AvailabilityMissing:
				// Silently fail if bridge is missing.
				throw BridgeNotFoundException();
				break;
			case ComponentPluginBridge::AvailabilityWrongVersion:
				throw BridgeException("The plugin bridge version does not match your OpenMPT version.");
				break;
			default:
				throw BridgeNotFoundException();
				break;
			}
		}
		const ComponentPluginBridge *const pluginBridge =
				(arch == PluginArch_x86 && bridgeGeneration == Generation::Modern) ? static_cast<const ComponentPluginBridge *>(pluginBridge_x86.get()) :
				(arch == PluginArch_x86 && bridgeGeneration == Generation::Legacy) ? static_cast<const ComponentPluginBridge *>(pluginBridgeLegacy_x86.get()) :
				(arch == PluginArch_amd64 && bridgeGeneration == Generation::Modern) ? static_cast<const ComponentPluginBridge *>(pluginBridge_amd64.get()) :
				(arch == PluginArch_amd64 && bridgeGeneration == Generation::Legacy) ? static_cast<const ComponentPluginBridge *>(pluginBridgeLegacy_amd64.get()) :
#if defined(MPT_WITH_WINDOWS10)
				(arch == PluginArch_arm && bridgeGeneration == Generation::Modern) ? static_cast<const ComponentPluginBridge *>(pluginBridge_arm.get()) :
				(arch == PluginArch_arm && bridgeGeneration == Generation::Legacy) ? static_cast<const ComponentPluginBridge *>(pluginBridgeLegacy_arm.get()) :
				(arch == PluginArch_arm64 && bridgeGeneration == Generation::Modern) ? static_cast<const ComponentPluginBridge *>(pluginBridge_arm64.get()) :
				(arch == PluginArch_arm64 && bridgeGeneration == Generation::Legacy) ? static_cast<const ComponentPluginBridge *>(pluginBridgeLegacy_arm64.get()) :
#endif  // MPT_WITH_WINDOWS10
		    nullptr;
		if(!pluginBridge)
		{
			return false;
		}
		m_Generation = bridgeGeneration;
		const mpt::PathString exeName = pluginBridge->GetFileName();

		m_otherPtrSize = static_cast<int32>(GetPluginArchPointerSize(arch));

		std::wstring cmdLine = MPT_WFORMAT("{} {}")(mapName, procId);

		STARTUPINFOW info;
		MemsetZero(info);
		info.cb = sizeof(info);
		PROCESS_INFORMATION processInfo;
		MemsetZero(processInfo);

		if(!CreateProcessW(exeName.ToWide().c_str(), cmdLine.data(), NULL, NULL, FALSE, 0, NULL, NULL, &info, &processInfo))
		{
			throw BridgeException("Failed to launch plugin bridge.");
		}
		CloseHandle(processInfo.hThread);
		m_otherProcess = processInfo.hProcess;
	} else
	{
		// Re-use existing bridge instance
		m_otherPtrSize = sharedInstace->m_otherPtrSize;
		m_otherProcess.DuplicateFrom(sharedInstace->m_otherProcess);

		BridgeMessage msg;
		msg.NewInstance(mapName.c_str());
		if(!sharedInstace->SendToBridge(msg))
		{
			// Something went wrong, try a new instance
			return Init(pluginPath, bridgeGeneration, nullptr);
		}
	}

	// Initialize bridge
	m_sharedMem->effect.object = this;
	m_sharedMem->effect.dispatcher = DispatchToPlugin;
	m_sharedMem->effect.setParameter = SetParameter;
	m_sharedMem->effect.getParameter = GetParameter;
	m_sharedMem->effect.process = Process;
	std::memcpy(&(m_sharedMem->effect.reservedForHost2), "OMPT", 4);

	m_sigAutomation.Create(true);

	m_sharedMem->hostCommWindow = m_communicationWindow;

	const HANDLE objects[] = {m_sigBridgeReady, m_otherProcess};
	if(WaitForMultipleObjects(mpt::saturate_cast<DWORD>(std::size(objects)), objects, FALSE, 10000) != WAIT_OBJECT_0)
	{
		throw BridgeException("Could not connect to plugin bridge, it probably crashed.");
	}
	m_otherPluginID = m_sharedMem->bridgePluginID;

	BridgeMessage initMsg;
	initMsg.Init(pluginPath.ToWide().c_str(), MIXBUFFERSIZE, m_thisPluginID, ExceptionHandler::fullMemDump);

	if(!SendToBridge(initMsg))
	{
		throw BridgeException("Could not initialize plugin bridge, it probably crashed.");
	} else if(initMsg.init.result != 1)
	{
		throw BridgeException(mpt::ToCharset(mpt::Charset::UTF8, initMsg.init.str).c_str());
	}

	if(m_sharedMem->effect.flags & effFlagsCanReplacing)
		m_sharedMem->effect.processReplacing = ProcessReplacing;
	if(m_sharedMem->effect.flags & effFlagsCanDoubleReplacing)
		m_sharedMem->effect.processDoubleReplacing = ProcessDoubleReplacing;
	return true;
}


// Send an arbitrary message to the bridge.
// Returns true if the message was processed by the bridge.
bool BridgeWrapper::SendToBridge(BridgeMessage &sendMsg)
{
	const bool inAudioThread = m_isAudioThread || CMainFrame::GetMainFrame()->InAudioThread();
	auto &messages = m_sharedMem->ipcMessages;
	const auto msgID = CopyToSharedMemory(sendMsg, messages);
	if(msgID < 0)
		return false;
	BridgeMessage &sharedMsg = messages[msgID];

	if(!inAudioThread)
	{
		if(SendMessage(m_sharedMem->bridgeCommWindow, WM_BRIDGE_MESSAGE_TO_BRIDGE, m_otherPluginID, msgID) == WM_BRIDGE_SUCCESS)
		{
			sharedMsg.CopyTo(sendMsg);
			return true;
		}
		return false;
	}

	// Audio thread: Use signals instead of window messages
	m_sharedMem->audioThreadToBridgeMsgID = msgID;
	m_sigToBridgeAudio.Send();

	// Wait until we get the result from the bridge
	DWORD result;
	const HANDLE objects[] = {m_sigToBridgeAudio.confirm, m_sigToHostAudio.send, m_otherProcess};
	do
	{
		result = WaitForMultipleObjects(mpt::saturate_cast<DWORD>(std::size(objects)), objects, FALSE, INFINITE);
		if(result == WAIT_OBJECT_0)
		{
			// Message got answered
			sharedMsg.CopyTo(sendMsg);
			break;
		} else if(result == WAIT_OBJECT_0 + 1)
		{
			ParseNextMessage(m_sharedMem->audioThreadToHostMsgID);
			m_sigToHostAudio.Confirm();
		}
	} while(result != WAIT_OBJECT_0 + 2 && result != WAIT_FAILED);

	return (result == WAIT_OBJECT_0);
}


// Receive a message from the host and translate it.
void BridgeWrapper::ParseNextMessage(int msgID)
{
	auto &msg = m_sharedMem->ipcMessages[msgID];
	switch(msg.header.type)
	{
	case MsgHeader::dispatch:
		DispatchToHost(msg.dispatch);
		break;

	case MsgHeader::errorMsg:
		// TODO Showing a message box here will deadlock as the main thread can be in a waiting state
		//throw BridgeErrorException(msg.error.str);
		break;
	}
}


void BridgeWrapper::DispatchToHost(DispatchMsg &msg)
{
	// Various dispatch data - depending on the opcode, one of those might be used.
	std::vector<char> extraData;

	MappedMemory auxMem;

	// Content of ptr is usually stored right after the message header, ptr field indicates size.
	void *ptr = (msg.ptr != 0) ? (&msg + 1) : nullptr;
	if(msg.size > sizeof(BridgeMessage))
	{
		if(!auxMem.Open(static_cast<const wchar_t *>(ptr)))
		{
			return;
		}
		ptr = auxMem.Data();
	}
	void *origPtr = ptr;

	switch(msg.opcode)
	{
	case audioMasterProcessEvents:
		// VstEvents* in [ptr]
		TranslateBridgeToVstEvents(extraData, ptr);
		ptr = extraData.data();
		break;

	case audioMasterVendorSpecific:
		if(msg.index != kVendorOpenMPT || msg.value != kUpdateProcessingBuffer)
		{
			break;
		}
		[[fallthrough]];
	case audioMasterIOChanged:
	{
		// If the song is playing, the rendering thread might be active at the moment,
		// so we should keep the current processing memory alive until it is done for sure.
		const CVstPlugin *plug = static_cast<CVstPlugin *>(m_sharedMem->effect.reservedForHost1);
		const bool isPlaying = plug != nullptr && plug->IsResumed();
		if(isPlaying)
		{
			m_oldProcessMem.CopyFrom(m_processMem);
		}
		// Set up new processing file
		m_processMem.Open(static_cast<wchar_t *>(ptr));
		if(isPlaying)
		{
			msg.result = 1;
			return;
		}
	}
	break;

	case audioMasterUpdateDisplay:
		m_cachedProgNames.clear();
		m_cachedParamInfo.clear();
		break;

	case audioMasterOpenFileSelector:
		TranslateBridgeToVstFileSelect(extraData, ptr, static_cast<size_t>(msg.ptr));
		ptr = extraData.data();
		break;
	}

	intptr_t result = CVstPlugin::MasterCallBack(&m_sharedMem->effect, static_cast<VstOpcodeToHost>(msg.opcode), msg.index, static_cast<intptr_t>(msg.value), ptr, msg.opt);
	msg.result = static_cast<int32>(result);

	// Post-fix some opcodes
	switch(msg.opcode)
	{
	case audioMasterGetTime:
		// VstTimeInfo* in [return value]
		if(msg.result != 0)
		{
			m_sharedMem->timeInfo = *FromIntPtr<VstTimeInfo>(result);
		}
		break;

	case audioMasterGetDirectory:
		// char* in [return value]
		if(msg.result != 0)
		{
			char *target = static_cast<char *>(ptr);
			strncpy(target, FromIntPtr<const char>(result), static_cast<size_t>(msg.ptr - 1));
			target[msg.ptr - 1] = 0;
		}
		break;

	case audioMasterOpenFileSelector:
		if(msg.result != 0)
		{
			std::vector<char> fileSelect;
			TranslateVstFileSelectToBridge(fileSelect, *static_cast<const VstFileSelect *>(ptr), m_otherPtrSize);
			std::memcpy(origPtr, fileSelect.data(), std::min(fileSelect.size(), static_cast<size_t>(msg.ptr)));
			// Directly free memory on host side, we don't need it anymore
			CVstPlugin::MasterCallBack(&m_sharedMem->effect, audioMasterCloseFileSelector, msg.index, static_cast<intptr_t>(msg.value), ptr, msg.opt);
		}
		break;
	}
}


intptr_t VSTCALLBACK BridgeWrapper::DispatchToPlugin(AEffect *effect, VstOpcodeToPlugin opcode, int32 index, intptr_t value, void *ptr, float opt)
{
	BridgeWrapper *that = static_cast<BridgeWrapper *>(effect->object);
	if(that != nullptr)
	{
		return that->DispatchToPlugin(opcode, index, value, ptr, opt);
	}
	return 0;
}


intptr_t BridgeWrapper::DispatchToPlugin(VstOpcodeToPlugin opcode, int32 index, intptr_t value, void *ptr, float opt)
{
	std::vector<char> dispatchData(sizeof(DispatchMsg), 0);
	int64 ptrOut = 0;
	bool copyPtrBack = false, ptrIsSize = true;
	char *ptrC = static_cast<char *>(ptr);

	switch(opcode)
	{
	case effGetParamLabel:
	case effGetParamDisplay:
	case effGetParamName:
		if(index >= m_cachedParamInfoStart && index < m_cachedParamInfoStart + mpt::saturate_cast<int32>(m_cachedParamInfo.size()))
		{
			if(opcode == effGetParamLabel)
				strcpy(ptrC, m_cachedParamInfo[index - m_cachedParamInfoStart].label);
			else if(opcode == effGetParamDisplay)
				strcpy(ptrC, m_cachedParamInfo[index - m_cachedParamInfoStart].display);
			else if(opcode == effGetParamName)
				strcpy(ptrC, m_cachedParamInfo[index - m_cachedParamInfoStart].name);
			return 1;
		}
		[[fallthrough]];
	case effGetProgramName:
	case effString2Parameter:
	case effGetProgramNameIndexed:
	case effGetEffectName:
	case effGetErrorText:
	case effGetVendorString:
	case effGetProductString:
	case effShellGetNextPlugin:
		// Name in [ptr]
		if(opcode == effGetProgramNameIndexed && !m_cachedProgNames.empty())
		{
			// First check if we have cached this program name
			if(index >= m_cachedProgNameStart && index < m_cachedProgNameStart + mpt::saturate_cast<int32>(m_cachedProgNames.size() / kCachedProgramNameLength))
			{
				strcpy(ptrC, &m_cachedProgNames[(index - m_cachedProgNameStart) * kCachedProgramNameLength]);
				return 1;
			}
		}
		ptrOut = 256;
		copyPtrBack = true;
		break;

	case effSetProgramName:
		m_cachedProgNames.clear();
		[[fallthrough]];
	case effCanDo:
		// char* in [ptr]
		ptrOut = strlen(ptrC) + 1;
		dispatchData.insert(dispatchData.end(), ptrC, ptrC + ptrOut);
		break;

	case effIdle:
		// The plugin bridge will generate these messages by itself
		return 0;

	case effEditGetRect:
		// ERect** in [ptr]
		ptrOut = sizeof(ERect);
		copyPtrBack = true;
		break;

	case effEditOpen:
		// HWND in [ptr] - Note: Window handles are interoperable between 32-bit and 64-bit applications in Windows (http://msdn.microsoft.com/en-us/library/windows/desktop/aa384203%28v=vs.85%29.aspx)
		ptrOut = reinterpret_cast<int64>(ptr);
		ptrIsSize = false;
		m_cachedProgNames.clear();
		m_cachedParamInfo.clear();
		break;

	case effEditIdle:
		// The plugin bridge will generate these messages by itself
		return 0;

	case effGetChunk:
		// void** in [ptr] for chunk data address
		{
			static uint32 chunkId = 0;
			const std::wstring mapName = L"Local\\openmpt-" + mpt::wfmt::val(GetCurrentProcessId()) + L"-chunkdata-" + mpt::wfmt::val(chunkId++);
			ptrOut = (mapName.length() + 1) * sizeof(wchar_t);
			PushToVector(dispatchData, *mapName.c_str(), static_cast<size_t>(ptrOut));
		}
		break;

	case effSetChunk:
		// void* in [ptr] for chunk data
		ptrOut = value;
		dispatchData.insert(dispatchData.end(), ptrC, ptrC + value);
		m_cachedProgNames.clear();
		m_cachedParamInfo.clear();
		break;

	case effProcessEvents:
		// VstEvents* in [ptr]
		// We process in a separate memory segment to save a bridge communication message.
		{
			std::vector<char> events;
			TranslateVstEventsToBridge(events, *static_cast<VstEvents *>(ptr), m_otherPtrSize);
			if(m_eventMem.Size() < events.size())
			{
				// Resize memory
				static uint32 chunkId = 0;
				const std::wstring mapName = L"Local\\openmpt-" + mpt::wfmt::val(GetCurrentProcessId()) + L"-events-" + mpt::wfmt::val(chunkId++);
				ptrOut = (mapName.length() + 1) * sizeof(wchar_t);
				PushToVector(dispatchData, *mapName.c_str(), static_cast<size_t>(ptrOut));
				m_eventMem.Create(mapName.c_str(), static_cast<uint32>(events.size() + 1024));

				opcode = effVendorSpecific;
				index = kVendorOpenMPT;
				value = kUpdateEventMemName;
			}
			std::memcpy(m_eventMem.Data(), events.data(), events.size());
		}
		if(opcode != effVendorSpecific)
		{
			return 1;
		}
		break;

	case effGetInputProperties:
	case effGetOutputProperties:
		// VstPinProperties* in [ptr]
		ptrOut = sizeof(VstPinProperties);
		copyPtrBack = true;
		break;

	case effOfflineNotify:
		// VstAudioFile* in [ptr]
		ptrOut = sizeof(VstAudioFile) * value;
		// TODO
		return 0;
		break;

	case effOfflinePrepare:
	case effOfflineRun:
		// VstOfflineTask* in [ptr]
		ptrOut = sizeof(VstOfflineTask) * value;
		// TODO
		return 0;
		break;

	case effProcessVarIo:
		// VstVariableIo* in [ptr]
		ptrOut = sizeof(VstVariableIo);
		// TODO
		return 0;
		break;

	case effSetSpeakerArrangement:
		// VstSpeakerArrangement* in [value] and [ptr]
		ptrOut = sizeof(VstSpeakerArrangement) * 2;
		PushToVector(dispatchData, *static_cast<VstSpeakerArrangement *>(ptr));
		PushToVector(dispatchData, *FromIntPtr<VstSpeakerArrangement>(value));
		break;

	case effVendorSpecific:
		if(index == kVendorOpenMPT)
		{
			switch(value)
			{
			case kGetWrapperPointer:
				return ToIntPtr<BridgeWrapper>(this);

			case kCloseOldProcessingMemory:
			{
				intptr_t result = m_oldProcessMem.Good();
				m_oldProcessMem.Close();
				return result;
			}

			case kCacheProgramNames:
			{
				int32 *prog = static_cast<int32 *>(ptr);
				m_cachedProgNameStart = prog[0];
				ptrOut = std::max(static_cast<int64>(sizeof(int32) * 2), static_cast<int64>((prog[1] - prog[0]) * kCachedProgramNameLength));
				dispatchData.insert(dispatchData.end(), ptrC, ptrC + 2 * sizeof(int32));
			}
			break;

			case kCacheParameterInfo:
			{
				int32 *param = static_cast<int32 *>(ptr);
				m_cachedParamInfoStart = param[0];
				ptrOut = std::max(static_cast<int64>(sizeof(int32) * 2), static_cast<int64>((param[1] - param[0]) * sizeof(ParameterInfo)));
				dispatchData.insert(dispatchData.end(), ptrC, ptrC + 2 * sizeof(int32));
			}
			break;

			case kBeginGetProgram:
				ptrOut = m_sharedMem->effect.numParams * sizeof(float);
				break;

			case kEndGetProgram:
				m_cachedParamValues.clear();
				return 1;
			}
		}
		break;

	case effGetTailSize:
		return m_sharedMem->tailSize;

	case effGetParameterProperties:
		// VstParameterProperties* in [ptr]
		if(index >= m_cachedParamInfoStart && index < m_cachedParamInfoStart + mpt::saturate_cast<int32>(m_cachedParamInfo.size()))
		{
			*static_cast<VstParameterProperties *>(ptr) = m_cachedParamInfo[index - m_cachedParamInfoStart].props;
			return 1;
		}
		ptrOut = sizeof(VstParameterProperties);
		copyPtrBack = true;
		break;

	case effGetMidiProgramName:
	case effGetCurrentMidiProgram:
		// MidiProgramName* in [ptr]
		ptrOut = sizeof(MidiProgramName);
		copyPtrBack = true;
		break;

	case effGetMidiProgramCategory:
		// MidiProgramCategory* in [ptr]
		ptrOut = sizeof(MidiProgramCategory);
		copyPtrBack = true;
		break;

	case effGetMidiKeyName:
		// MidiKeyName* in [ptr]
		ptrOut = sizeof(MidiKeyName);
		copyPtrBack = true;
		break;

	case effBeginSetProgram:
		m_isSettingProgram = true;
		break;

	case effEndSetProgram:
		m_isSettingProgram = false;
		if(m_sharedMem->automationQueue.pendingEvents)
		{
			SendAutomationQueue();
		}
		m_cachedProgNames.clear();
		m_cachedParamInfo.clear();
		break;

	case effGetSpeakerArrangement:
		// VstSpeakerArrangement* in [value] and [ptr]
		ptrOut = sizeof(VstSpeakerArrangement) * 2;
		copyPtrBack = true;
		break;

	case effBeginLoadBank:
	case effBeginLoadProgram:
		// VstPatchChunkInfo* in [ptr]
		ptrOut = sizeof(VstPatchChunkInfo);
		m_cachedProgNames.clear();
		m_cachedParamInfo.clear();
		break;

	default:
		MPT_ASSERT(ptr == nullptr);
	}

	if(ptrOut != 0 && ptrIsSize)
	{
		// In case we only reserve space and don't copy stuff over...
		dispatchData.resize(sizeof(DispatchMsg) + static_cast<size_t>(ptrOut), 0);
	}

	uint32 extraSize = static_cast<uint32>(dispatchData.size() - sizeof(DispatchMsg));

	// Create message header
	BridgeMessage &msg = *reinterpret_cast<BridgeMessage *>(dispatchData.data());
	msg.Dispatch(opcode, index, value, ptrOut, opt, extraSize);

	const bool useAuxMem = dispatchData.size() > sizeof(BridgeMessage);
	AuxMem *auxMem = nullptr;
	if(useAuxMem)
	{
		// Extra data doesn't fit in message - use secondary memory
		if(dispatchData.size() > std::numeric_limits<uint32>::max())
			return 0;
		auxMem = GetAuxMemory(mpt::saturate_cast<uint32>(dispatchData.size()));
		if(auxMem == nullptr)
			return 0;

		// First, move message data to shared memory...
		std::memcpy(auxMem->memory.Data(), &dispatchData[sizeof(DispatchMsg)], extraSize);
		// ...Now put the shared memory name in the message instead.
		std::memcpy(&dispatchData[sizeof(DispatchMsg)], auxMem->name, sizeof(auxMem->name));
	}

	try
	{
		if(!SendToBridge(msg) && opcode != effClose)
		{
			return 0;
		}
	} catch(...)
	{
		// Don't do anything for now.
#if 0
		if(opcode != effClose)
		{
			throw;
		}
#endif
	}

	const DispatchMsg &resultMsg = msg.dispatch;

	// cppcheck false-positive
	// cppcheck-suppress nullPointerRedundantCheck
	const void *extraData = useAuxMem ? auxMem->memory.Data<const char>() : reinterpret_cast<const char *>(&resultMsg + 1);
	// Post-fix some opcodes
	switch(opcode)
	{
	case effClose:
		m_sharedMem->effect.object = nullptr;
		delete this;
		return 0;

	case effGetProgramName:
	case effGetParamLabel:
	case effGetParamDisplay:
	case effGetParamName:
	case effString2Parameter:
	case effGetProgramNameIndexed:
	case effGetEffectName:
	case effGetErrorText:
	case effGetVendorString:
	case effGetProductString:
	case effShellGetNextPlugin:
		// Name in [ptr]
		strcpy(ptrC, static_cast<const char *>(extraData));
		break;

	case effEditGetRect:
		// ERect** in [ptr]
		m_editRect = *static_cast<const ERect *>(extraData);
		*static_cast<const ERect **>(ptr) = &m_editRect;
		break;

	case effGetChunk:
		// void** in [ptr] for chunk data address
		if(const wchar_t *str = static_cast<const wchar_t *>(extraData); m_getChunkMem.Open(str))
			*static_cast<void **>(ptr) = m_getChunkMem.Data();
		else
			return 0;
		break;

	case effVendorSpecific:
		if(index == kVendorOpenMPT && resultMsg.result == 1)
		{
			switch(value)
			{
			case kCacheProgramNames:
				m_cachedProgNames.assign(static_cast<const char *>(extraData), static_cast<const char *>(extraData) + ptrOut);
				break;
			case kCacheParameterInfo:
			{
				const ParameterInfo *params = static_cast<const ParameterInfo *>(extraData);
				m_cachedParamInfo.assign(params, params + ptrOut / sizeof(ParameterInfo));
				break;
			}
			case kBeginGetProgram:
				m_cachedParamValues.assign(static_cast<const float *>(extraData), static_cast<const float *>(extraData) + ptrOut / sizeof(float));
				break;
			}
		}
		break;

	case effGetSpeakerArrangement:
		// VstSpeakerArrangement* in [value] and [ptr]
		m_speakers[0] = *static_cast<const VstSpeakerArrangement *>(extraData);
		m_speakers[1] = *(static_cast<const VstSpeakerArrangement *>(extraData) + 1);
		*static_cast<VstSpeakerArrangement *>(ptr) = m_speakers[0];
		*FromIntPtr<VstSpeakerArrangement>(value) = m_speakers[1];
		break;

	default:
		// TODO: Translate VstVariableIo, offline tasks
		if(copyPtrBack)
		{
			std::memcpy(ptr, extraData, static_cast<size_t>(ptrOut));
		}
	}

	if(auxMem != nullptr)
	{
		auxMem->used = false;
	}

	return static_cast<intptr_t>(resultMsg.result);
}


// Allocate auxiliary shared memory for too long bridge messages
BridgeWrapper::AuxMem *BridgeWrapper::GetAuxMemory(uint32 size)
{
	std::size_t index = std::size(m_auxMems);
	for(int pass = 0; pass < 2; pass++)
	{
		for(std::size_t i = 0; i < std::size(m_auxMems); i++)
		{
			if(m_auxMems[i].size >= size || pass == 1)
			{
				// Good candidate - is it taken yet?
				bool expected = false;
				if(m_auxMems[i].used.compare_exchange_strong(expected, true))
				{
					index = i;
					break;
				}
			}
		}
		if(index != std::size(m_auxMems))
			break;
	}
	if(index == std::size(m_auxMems))
		return nullptr;

	AuxMem &auxMem = m_auxMems[index];
	if(auxMem.size >= size && auxMem.memory.Good())
	{
		// Re-use as-is
		return &auxMem;
	}
	// Create new memory with appropriate size
	static_assert(sizeof(DispatchMsg) + sizeof(auxMem.name) <= sizeof(BridgeMessage), "Check message sizes, this will crash!");
	static unsigned int auxMemCount = 0;
	mpt::String::WriteAutoBuf(auxMem.name) = MPT_WFORMAT("Local\\openmpt-{}-auxmem-{}")(GetCurrentProcessId(), auxMemCount++);
	if(auxMem.memory.Create(auxMem.name, size))
	{
		auxMem.size = size;
		return &auxMem;
	} else
	{
		auxMem.used = false;
		return nullptr;
	}
}


// Send any pending automation events
void BridgeWrapper::SendAutomationQueue()
{
	m_sigAutomation.Reset();
	BridgeMessage msg;
	msg.Automate();
	if(!SendToBridge(msg))
	{
		// Failed (plugin probably crashed) - auto-fix event count
		m_sharedMem->automationQueue.pendingEvents = 0;
	}
	m_sigAutomation.Trigger();
}

void VSTCALLBACK BridgeWrapper::SetParameter(AEffect *effect, int32 index, float parameter)
{
	BridgeWrapper *that = static_cast<BridgeWrapper *>(effect->object);
	if(that)
	{
		try
		{
			that->SetParameter(index, parameter);
		} catch(...)
		{
			// Be quiet about exceptions here
		}
	}
}


void BridgeWrapper::SetParameter(int32 index, float parameter)
{
	const CVstPlugin *plug = static_cast<CVstPlugin *>(m_sharedMem->effect.reservedForHost1);
	AutomationQueue &autoQueue = m_sharedMem->automationQueue;
	if(m_isSettingProgram || (plug && plug->IsResumed()))
	{
		// Queue up messages while rendering to reduce latency introduced by every single bridge call
		uint32 i;
		while((i = autoQueue.pendingEvents.fetch_add(1)) >= std::size(autoQueue.params))
		{
			// Queue full!
			if(i == std::size(autoQueue.params))
			{
				// We're the first to notice that it's full
				SendAutomationQueue();
			} else
			{
				// Wait until queue is emptied by someone else (this branch is very unlikely to happen)
				WaitForSingleObject(m_sigAutomation, INFINITE);
			}
		}

		autoQueue.params[i].index = index;
		autoQueue.params[i].value = parameter;
		return;
	} else if(autoQueue.pendingEvents)
	{
		// Actually, this should never happen as pending events are cleared before processing and at the end of a set program event.
		SendAutomationQueue();
	}

	BridgeMessage msg;
	msg.SetParameter(index, parameter);
	SendToBridge(msg);
}


float VSTCALLBACK BridgeWrapper::GetParameter(AEffect *effect, int32 index)
{
	BridgeWrapper *that = static_cast<BridgeWrapper *>(effect->object);
	if(that)
	{
		if(static_cast<size_t>(index) < that->m_cachedParamValues.size())
			return that->m_cachedParamValues[index];

		try
		{
			return that->GetParameter(index);
		} catch(...)
		{
			// Be quiet about exceptions here
		}
	}
	return 0.0f;
}


float BridgeWrapper::GetParameter(int32 index)
{
	BridgeMessage msg;
	msg.GetParameter(index);
	if(SendToBridge(msg))
	{
		return msg.parameter.value;
	}
	return 0.0f;
}


void VSTCALLBACK BridgeWrapper::Process(AEffect *effect, float **inputs, float **outputs, int32 sampleFrames)
{
	BridgeWrapper *that = static_cast<BridgeWrapper *>(effect->object);
	if(sampleFrames != 0 && that != nullptr)
	{
		that->BuildProcessBuffer(ProcessMsg::process, effect->numInputs, effect->numOutputs, inputs, outputs, sampleFrames);
	}
}


void VSTCALLBACK BridgeWrapper::ProcessReplacing(AEffect *effect, float **inputs, float **outputs, int32 sampleFrames)
{
	BridgeWrapper *that = static_cast<BridgeWrapper *>(effect->object);
	if(sampleFrames != 0 && that != nullptr)
	{
		that->BuildProcessBuffer(ProcessMsg::processReplacing, effect->numInputs, effect->numOutputs, inputs, outputs, sampleFrames);
	}
}


void VSTCALLBACK BridgeWrapper::ProcessDoubleReplacing(AEffect *effect, double **inputs, double **outputs, int32 sampleFrames)
{
	BridgeWrapper *that = static_cast<BridgeWrapper *>(effect->object);
	if(sampleFrames != 0 && that != nullptr)
	{
		that->BuildProcessBuffer(ProcessMsg::processDoubleReplacing, effect->numInputs, effect->numOutputs, inputs, outputs, sampleFrames);
	}
}


template <typename buf_t>
void BridgeWrapper::BuildProcessBuffer(ProcessMsg::ProcessType type, int32 numInputs, int32 numOutputs, buf_t **inputs, buf_t **outputs, int32 sampleFrames)
{
	if(!m_processMem.Good())
	{
		MPT_ASSERT_NOTREACHED();
		return;
	}

	ProcessMsg *processMsg = m_processMem.Data<ProcessMsg>();
	new(processMsg) ProcessMsg{type, numInputs, numOutputs, sampleFrames};

	// Anticipate that many plugins will query the play position in a process call and send it along the process call
	// to save some valuable inter-process calls.
	m_sharedMem->timeInfo = *FromIntPtr<VstTimeInfo>(CVstPlugin::MasterCallBack(&m_sharedMem->effect, audioMasterGetTime, 0, kVstNanosValid | kVstPpqPosValid | kVstTempoValid | kVstBarsValid | kVstCyclePosValid | kVstTimeSigValid | kVstSmpteValid | kVstClockValid, nullptr, 0.0f));

	buf_t *ptr = reinterpret_cast<buf_t *>(processMsg + 1);
	for(int32 i = 0; i < numInputs; i++)
	{
		std::memcpy(ptr, inputs[i], sampleFrames * sizeof(buf_t));
		ptr += sampleFrames;
	}
	// Theoretically, we should memcpy() instead of memset() here in process(), but OpenMPT always clears the output buffer before processing so it doesn't matter.
	memset(ptr, 0, numOutputs * sampleFrames * sizeof(buf_t));

	// In case we called Process() from the GUI thread (panic button or song stop => CSoundFile::SuspendPlugins)
	m_isAudioThread = true;

	m_sigProcessAudio.Send();
	const HANDLE objects[] = {m_sigProcessAudio.confirm, m_sigToHostAudio.send, m_otherProcess};
	DWORD result;
	do
	{
		result = WaitForMultipleObjects(mpt::saturate_cast<DWORD>(std::size(objects)), objects, FALSE, INFINITE);
		if(result == WAIT_OBJECT_0 + 1)
		{
			ParseNextMessage(m_sharedMem->audioThreadToHostMsgID);
			m_sigToHostAudio.Confirm();
		}
	} while(result != WAIT_OBJECT_0 && result != WAIT_OBJECT_0 + 2 && result != WAIT_FAILED);

	m_isAudioThread = false;

	for(int32 i = 0; i < numOutputs; i++)
	{
		//std::memcpy(outputs[i], ptr, sampleFrames * sizeof(buf_t));
		outputs[i] = ptr;  // Exactly what you don't want plugins to do usually (bend your output pointers)... muahahaha!
		ptr += sampleFrames;
	}

	// Did we receive any audioMasterProcessEvents data?
	if(auto *events = m_eventMem.Data<int32>(); events != nullptr && *events != 0)
	{
		std::vector<char> eventCache;
		TranslateBridgeToVstEvents(eventCache, events);
		*events = 0;
		CVstPlugin::MasterCallBack(&m_sharedMem->effect, audioMasterProcessEvents, 0, 0, eventCache.data(), 0.0f);
	}
}


LRESULT CALLBACK BridgeWrapper::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	if(hwnd == m_communicationWindow && wParam < m_plugins.size())
	{
		auto *that = static_cast<BridgeWrapper *>(m_plugins[wParam]);
		if(that != nullptr && uMsg == WM_BRIDGE_MESSAGE_TO_HOST)
		{
			that->ParseNextMessage(static_cast<int>(lParam));
			return WM_BRIDGE_SUCCESS;
		}
	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

#endif  // MPT_WITH_VST


OPENMPT_NAMESPACE_END