/*
 * AEffectWrapper.h
 * ----------------
 * Purpose: Helper functions and structs for translating the VST AEffect struct between bit boundaries.
 * Notes  : (currently none)
 * Authors: Johannes Schultz (OpenMPT Devs)
 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
 */


#pragma once

#include "openmpt/all/BuildSettings.hpp"

#include <vector>
#include "../mptrack/plugins/VstDefinitions.h"

OPENMPT_NAMESPACE_BEGIN

#pragma pack(push, 8)

template <typename ptr_t>
struct AEffectProto
{
	int32 magic;
	ptr_t dispatcher;
	ptr_t process;
	ptr_t setParameter;
	ptr_t getParameter;

	int32 numPrograms;
	uint32 numParams;
	int32 numInputs;
	int32 numOutputs;

	int32 flags;

	ptr_t resvd1;
	ptr_t resvd2;

	int32 initialDelay;

	int32 realQualities;
	int32 offQualities;
	float ioRatio;

	ptr_t object;
	ptr_t user;

	int32 uniqueID;
	int32 version;

	ptr_t processReplacing;
	ptr_t processDoubleReplacing;
	char future[56];

	// Convert native representation to bridge representation.
	// Don't overwrite any values managed by the bridge wrapper or host in general.
	void FromNative(const Vst::AEffect &in)
	{
		magic = in.magic;

		numPrograms = in.numPrograms;
		numParams = in.numParams;
		numInputs = in.numInputs;
		numOutputs = in.numOutputs;

		flags = in.flags;

		initialDelay = in.initialDelay;

		realQualities = in.realQualities;
		offQualities = in.offQualities;
		ioRatio = in.ioRatio;

		uniqueID = in.uniqueID;
		version = in.version;

		if(in.processReplacing == nullptr)
			flags &= ~Vst::effFlagsCanReplacing;
		if(in.processDoubleReplacing == nullptr)
			flags &= ~Vst::effFlagsCanDoubleReplacing;
	}
};

using AEffect32 = AEffectProto<int32>;
using AEffect64 = AEffectProto<int64>;

#pragma pack(pop)


// Translate a VSTEvents struct to bridge format (placed in data vector)
static void TranslateVstEventsToBridge(std::vector<char> &outData, const Vst::VstEvents &events, int32 targetPtrSize)
{
	outData.reserve(outData.size() + sizeof(int32) + sizeof(Vst::VstMidiEvent) * events.numEvents);
	// Write number of events
	PushToVector(outData, events.numEvents);
	// Write events
	for(const auto event : events)
	{
		if(event->type == Vst::kVstSysExType)
		{
			// This is going to be messy since the VstMidiSysexEvent event has a different size than other events on 64-bit platforms.
			// We are going to write the event using the target process pointer size.
			auto sysExEvent = *static_cast<const Vst::VstMidiSysexEvent *>(event);
			sysExEvent.byteSize = 4 * sizeof(int32) + 4 * targetPtrSize;  // It's 5 int32s and 3 pointers but that means that on 64-bit platforms, the fifth int32 is padded for alignment.
			PushToVector(outData, sysExEvent, 5 * sizeof(int32));         // Exclude the three pointers at the end for now
			if(targetPtrSize > static_cast<int32>(sizeof(int32)))                             // Padding for 64-bit required?
				outData.insert(outData.end(), targetPtrSize - sizeof(int32), 0);
			outData.insert(outData.end(), 3 * targetPtrSize, 0);  // Make space for pointer + two reserved intptr_ts
			// Embed SysEx dump as well...
			auto sysex = reinterpret_cast<const char *>(sysExEvent.sysexDump);
			outData.insert(outData.end(), sysex, sysex + sysExEvent.dumpBytes);
		} else if(event->type == Vst::kVstMidiType)
		{
			// randomid by Insert Piz Here sends events of type kVstMidiType, but with a claimed size of 24 bytes instead of 32.
			Vst::VstMidiEvent midiEvent;
			std::memcpy(&midiEvent, event, sizeof(midiEvent));
			midiEvent.byteSize = sizeof(midiEvent);
			PushToVector(outData, midiEvent, sizeof(midiEvent));
		} else
		{
			PushToVector(outData, *event, event->byteSize);
		}
	}
}


// Translate bridge format (void *ptr) back to VSTEvents struct (placed in data vector)
static void TranslateBridgeToVstEvents(std::vector<char> &outData, const void *inData)
{
	const int32 numEvents = *static_cast<const int32 *>(inData);

	// First element is really a int32, but in case of 64-bit builds, the next field gets aligned anyway.
	const size_t headerSize = sizeof(intptr_t) + sizeof(intptr_t) + sizeof(Vst::VstEvent *) * numEvents;
	outData.reserve(headerSize + sizeof(Vst::VstMidiEvent) * numEvents);
	outData.resize(headerSize, 0);
	if(numEvents == 0)
		return;

	// Copy over event data (this is required for dumb SynthEdit plugins that don't copy over the event data during effProcessEvents)
	const char *readOffset = static_cast<const char *>(inData) + sizeof(int32);
	for(int32 i = 0; i < numEvents; i++)
	{
		auto *event = reinterpret_cast<const Vst::VstEvent *>(readOffset);
		outData.insert(outData.end(), readOffset, readOffset + event->byteSize);
		readOffset += event->byteSize;

		if(event->type == Vst::kVstSysExType)
		{
			// Copy over sysex dump
			auto *sysExEvent = static_cast<const Vst::VstMidiSysexEvent *>(event);
			outData.insert(outData.end(), readOffset, readOffset + sysExEvent->dumpBytes);
			readOffset += sysExEvent->dumpBytes;
		}
	}

	// Write pointers
	auto events = reinterpret_cast<Vst::VstEvents *>(outData.data());
	events->numEvents = numEvents;
	char *offset = outData.data() + headerSize;
	for(int32 i = 0; i < numEvents; i++)
	{
		events->events[i] = reinterpret_cast<Vst::VstEvent *>(offset);
		offset += events->events[i]->byteSize;
		if(events->events[i]->type == Vst::kVstSysExType)
		{
			auto sysExEvent = static_cast<Vst::VstMidiSysexEvent *>(events->events[i]);
			sysExEvent->sysexDump = reinterpret_cast<const std::byte *>(offset);
			offset += sysExEvent->dumpBytes;
		}
	}
}


// Calculate the size total of the VSTEvents (without header) in bridge format
static size_t BridgeVstEventsSize(const void *ptr)
{
	const int32 numEvents = *static_cast<const int32 *>(ptr);
	size_t size = 0;
	for(int32 i = 0; i < numEvents; i++)
	{
		const auto event = reinterpret_cast<const Vst::VstEvent *>(static_cast<const char *>(ptr) + sizeof(int32) + size);
		size += event->byteSize;
		if(event->type == Vst::kVstSysExType)
		{
			size += static_cast<const Vst::VstMidiSysexEvent *>(event)->dumpBytes;
		}
	}
	return size;
}


static void TranslateVstFileSelectToBridge(std::vector<char> &outData, const Vst::VstFileSelect &fileSelect, int32 targetPtrSize)
{
	outData.reserve(outData.size() + sizeof(Vst::VstFileSelect) + fileSelect.numFileTypes * sizeof(Vst::VstFileType));
	PushToVector(outData, fileSelect.command);
	PushToVector(outData, fileSelect.type);
	PushToVector(outData, fileSelect.macCreator);
	PushToVector(outData, fileSelect.numFileTypes);
	outData.insert(outData.end(), targetPtrSize, 0);  // fileTypes
	PushToVector(outData, fileSelect.title);
	outData.insert(outData.end(), 2 * targetPtrSize, 0);  // initialPath, returnPath
	PushToVector(outData, fileSelect.sizeReturnPath);
	if(targetPtrSize > static_cast<int32>(sizeof(int32)))
		outData.insert(outData.end(), targetPtrSize - sizeof(int32), 0);  // padding
	outData.insert(outData.end(), targetPtrSize, 0);                      // returnMultiplePaths
	PushToVector(outData, fileSelect.numReturnPaths);
	outData.insert(outData.end(), targetPtrSize, 0);  // reserved
	PushToVector(outData, fileSelect.reserved2);

	if(fileSelect.command != Vst::kVstDirectorySelect)
	{
		for(int32 i = 0; i < fileSelect.numFileTypes; i++)
		{
			PushToVector(outData, fileSelect.fileTypes[i]);
		}
	}

	if(fileSelect.command == Vst::kVstMultipleFilesLoad)
	{
		outData.insert(outData.end(), fileSelect.numReturnPaths * targetPtrSize, 0);
		for(int32 i = 0; i < fileSelect.numReturnPaths; i++)
		{
			PushZStringToVector(outData, fileSelect.returnMultiplePaths[i]);
		}
	}

	PushZStringToVector(outData, fileSelect.initialPath);
	PushZStringToVector(outData, fileSelect.returnPath);
}


static void TranslateBridgeToVstFileSelect(std::vector<char> &outData, const void *inData, size_t srcSize)
{
	outData.assign(static_cast<const char *>(inData), static_cast<const char *>(inData) + srcSize);

	// Fixup pointers
	Vst::VstFileSelect &fileSelect = *reinterpret_cast<Vst::VstFileSelect *>(outData.data());
	auto ptrOffset = outData.data() + sizeof(Vst::VstFileSelect);

	if(fileSelect.command != Vst::kVstDirectorySelect)
	{
		fileSelect.fileTypes = reinterpret_cast<Vst::VstFileType *>(ptrOffset);
		ptrOffset += fileSelect.numFileTypes * sizeof(Vst::VstFileType);
	} else
	{
		fileSelect.fileTypes = nullptr;
	}

	if(fileSelect.command == Vst::kVstMultipleFilesLoad)
	{
		fileSelect.returnMultiplePaths = reinterpret_cast<char **>(ptrOffset);
		ptrOffset += fileSelect.numReturnPaths * sizeof(char *);

		for(int32 i = 0; i < fileSelect.numReturnPaths; i++)
		{
			fileSelect.returnMultiplePaths[i] = ptrOffset;
			ptrOffset += strlen(fileSelect.returnMultiplePaths[i]) + 1;
		}
	} else
	{
		fileSelect.returnMultiplePaths = nullptr;
	}

	fileSelect.initialPath = ptrOffset;
	ptrOffset += strlen(fileSelect.initialPath) + 1;
	fileSelect.returnPath = ptrOffset;
	fileSelect.sizeReturnPath = static_cast<int32>(srcSize - std::distance(outData.data(), ptrOffset));
	ptrOffset += strlen(fileSelect.returnPath) + 1;
}


OPENMPT_NAMESPACE_END