//------------------------------------------------------------------------
// Project     : VST SDK
//
// Category    : Helpers
// Filename    : public.sdk/source/vst/basewrapper/basewrapper.cpp
// Created by  : Steinberg, 01/2009
// Description : VST 3 -> XXX Wrapper
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
/// \cond ignore

#include "public.sdk/source/vst/basewrapper/basewrapper.h"
#include "public.sdk/source/vst/hosting/connectionproxy.h"
#include "public.sdk/source/vst/hosting/hostclasses.h"

#include "pluginterfaces/base/funknownimpl.h"
#include "pluginterfaces/base/futils.h"
#include "pluginterfaces/base/keycodes.h"
#include "pluginterfaces/gui/iplugview.h"
#include "pluginterfaces/vst/ivstmessage.h"
#include "pluginterfaces/vst/ivstmidicontrollers.h"
#include "pluginterfaces/vst/vstpresetkeys.h"

#include "base/source/fstreamer.h"

#include <cstdio>
#include <cstdlib>
#include <limits>

#if SMTG_OS_MACOS
#include <CoreFoundation/CoreFoundation.h>
#include <dlfcn.h>
#endif

extern "C" {
#if SMTG_OS_MACOS
	// implemented in macmain.cpp
	SMTG_EXPORT_SYMBOL bool bundleEntry (CFBundleRef);
	SMTG_EXPORT_SYMBOL bool bundleExit (void);
#elif SMTG_OS_WINDOWS
	// implemented in dllmain.cpp
	SMTG_EXPORT_SYMBOL bool InitDll ();
	SMTG_EXPORT_SYMBOL bool ExitDll ();
#else
#error platform not supported!
#endif
}

#if SMTG_OS_MACOS
//------------------------------------------------------------------------
static CFBundleRef GetBundleFromExecutable (const char* filepath)
{
	// AutoreleasePool ap;
	char* fname = strdup (filepath);
	size_t pos = strlen (fname);
	int level = 3;
	while (level > 0 && --pos >= 0)
	{
		if (fname[pos] == '/')
			level--;
	}
	if (level > 0)
		return 0;

	fname[pos] = 0;
	CFURLRef url = CFURLCreateFromFileSystemRepresentation (0, (const UInt8*)fname, pos, true);
	CFBundleRef bundle = CFBundleCreate (0, url);
	return bundle;
}

//------------------------------------------------------------------------
static CFBundleRef GetCurrentBundle ()
{
	Dl_info info;
	if (dladdr ((const void*)GetCurrentBundle, &info))
	{
		if (info.dli_fname)
		{
			return GetBundleFromExecutable (info.dli_fname);
		}
	}
	return 0;
}
#endif // SMTG_OS_MACOS

//------------------------------------------------------------------------
bool _InitModule ()
{
#if SMTG_OS_MACOS
	return bundleEntry (GetCurrentBundle ());
#else
	return InitDll ();
#endif
}

//------------------------------------------------------------------------
bool _DeinitModule ()
{
#if SMTG_OS_MACOS
	return bundleExit ();
#else
	return ExitDll ();
#endif
}

//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {

//------------------------------------------------------------------------
// some Globals
//------------------------------------------------------------------------
// In order to speed up hasEditor function gPluginHasEditor can be set in EditController::initialize
enum
{
	kDontKnow = -1,
	kNoEditor = 0,
	kEditor,
};

// default: kDontKnow which uses createView to find out
using EditorAvailability = int32;
EditorAvailability gPluginHasEditor = kDontKnow;

// Set to 'true' in EditController::initialize
// default: VST 3 kIsProgramChange parameter will not be exported
bool gExportProgramChangeParameters = false;

//------------------------------------------------------------------------
BaseEditorWrapper::BaseEditorWrapper (IEditController* controller)
: mController (controller)
{
}

//------------------------------------------------------------------------
BaseEditorWrapper::~BaseEditorWrapper ()
{
	if (mView)
		_close ();

	mController = nullptr;
}

//------------------------------------------------------------------------
tresult PLUGIN_API BaseEditorWrapper::queryInterface (const char* _iid, void** obj)
{
	QUERY_INTERFACE (_iid, obj, FUnknown::iid, IPlugFrame)
	QUERY_INTERFACE (_iid, obj, IPlugFrame::iid, IPlugFrame)

	*obj = nullptr;
	return kNoInterface;
}

//------------------------------------------------------------------------
tresult PLUGIN_API BaseEditorWrapper::resizeView (IPlugView* view, ViewRect* newSize)
{
	tresult result = kResultFalse;
	if (view && newSize)
	{
		result = view->onSize (newSize);
	}

	return result;
}

//------------------------------------------------------------------------
bool BaseEditorWrapper::hasEditor (IEditController* controller)
{
	/* Some plug-ins might have large GUIs. In order to speed up hasEditor function while
	 * initializing the plug-in gPluginHasEditor can be set in EditController::initialize
	 * beforehand. */
	bool result = false;
	if (gPluginHasEditor == kEditor)
	{
		result = true;
	}
	else if (gPluginHasEditor == kNoEditor)
	{
		result = false;
	}
	else
	{
		IPlugView* view = controller ? controller->createView (ViewType::kEditor) : nullptr;
		FReleaser viewRel (view);
		result = (view != nullptr);
	}

	return result;
}

//------------------------------------------------------------------------
void BaseEditorWrapper::createView ()
{
	if (mView == nullptr && mController != nullptr)
	{
		mView = owned (mController->createView (ViewType::kEditor));
		mView->setFrame (this);

#if SMTG_OS_MACOS
#if SMTG_PLATFORM_64
		if (mView && mView->isPlatformTypeSupported (kPlatformTypeNSView) != kResultTrue)
#else
		if (mView && mView->isPlatformTypeSupported (kPlatformTypeHIView) != kResultTrue)
#endif
		{
			mView = nullptr;
			mController = nullptr;
		}
#endif
	}
}

//------------------------------------------------------------------------
bool BaseEditorWrapper::getRect (ViewRect& rect)
{
	createView ();
	if (!mView)
		return false;

	if (mView->getSize (&rect) == kResultTrue)
	{
		if ((rect.bottom - rect.top) > 0 && (rect.right - rect.left) > 0)
		{
			mViewRect = rect;
			return true;
		}
	}

	return false;
}

//------------------------------------------------------------------------
bool BaseEditorWrapper::_open (void* ptr)
{
	createView ();

	if (mView)
	{
#if SMTG_OS_WINDOWS
		FIDString type = kPlatformTypeHWND;
#elif SMTG_OS_MACOS && SMTG_PLATFORM_64
		FIDString type = kPlatformTypeNSView;
#elif SMTG_OS_MACOS
		FIDString type = kPlatformTypeHIView;
#endif
		return mView->attached (ptr, type) == kResultTrue;
	}
	return false;
}

//------------------------------------------------------------------------
void BaseEditorWrapper::_close ()
{
	if (mView)
	{
		mView->setFrame (nullptr);
		mView->removed ();
		mView = nullptr;
	}
}

//------------------------------------------------------------------------
bool BaseEditorWrapper::_setKnobMode (Vst::KnobMode val)
{
	bool result = false;
	auto editController2 = U::cast<IEditController2> (mController);
	if (editController2)
		result = editController2->setKnobMode (val) == kResultTrue;

	return result;
}

//------------------------------------------------------------------------
// MemoryStream with attributes to add information "preset or project"
//------------------------------------------------------------------------
class VstPresetStream : public MemoryStream, Vst::IStreamAttributes
{
public:
	VstPresetStream () {}
	VstPresetStream (void* memory, TSize memorySize) : MemoryStream (memory, memorySize) {}

	//---from Vst::IStreamAttributes-----
	tresult PLUGIN_API getFileName (String128 /*name*/) SMTG_OVERRIDE { return kNotImplemented; }
	IAttributeList* PLUGIN_API getAttributes () SMTG_OVERRIDE { return attrList; }

//------------------------------------------------------------------------
	DELEGATE_REFCOUNT (MemoryStream)
	tresult PLUGIN_API queryInterface (const TUID iid, void** obj) SMTG_OVERRIDE
	{
		QUERY_INTERFACE (iid, obj, IStreamAttributes::iid, IStreamAttributes)
		return MemoryStream::queryInterface (iid, obj);
	}

protected:
	IPtr<IAttributeList> attrList {HostAttributeList::make ()};
};

//------------------------------------------------------------------------
// BaseWrapper
//------------------------------------------------------------------------
BaseWrapper::BaseWrapper (SVST3Config& config)
{
	mPlugInterfaceSupport = owned (NEW PlugInterfaceSupport);

	mProcessor = owned (config.processor);
	mController = owned (config.controller);
	mFactory = config.factory;	// share it
	mVst3EffectClassID = config.vst3ComponentID;

	memset (mName, 0, sizeof (mName));
	memset (mVendor, 0, sizeof (mVendor));
	memset (mSubCategories, 0, sizeof (mSubCategories));

	memset (&mProcessContext, 0, sizeof (ProcessContext));
	mProcessContext.sampleRate = 44100;
	mProcessContext.tempo = 120;

	for (int32 b = 0; b < kMaxMidiMappingBusses; b++)
	{
		for (int32 i = 0; i < 16; i++)
		{
			mMidiCCMapping[b][i] = nullptr;
		}
	}
	for (int32 i = 0; i < kMaxProgramChangeParameters; i++)
	{
		mProgramChangeParameterIDs[i] = kNoParamId;
		mProgramChangeParameterIdxs[i] = -1;
	}
}

//------------------------------------------------------------------------
BaseWrapper::~BaseWrapper ()
{
	term ();
}

//------------------------------------------------------------------------
void BaseWrapper::term ()
{
	mTimer = nullptr;

	mProcessData.unprepare ();

	//---Disconnect components
	if (mComponentsConnected)
	{
		auto cp1 = U::cast<IConnectionPoint> (mProcessor);
		auto cp2 = U::cast<IConnectionPoint> (mController);
		if (cp1 && cp2)
		{
			if (mProcessorConnection)
				mProcessorConnection->disconnect (cp2);
			if (mControllerConnection)
				mControllerConnection->disconnect (cp1);
		}
		mComponentsConnected = false;
	}

	//---Terminate Controller Component
	if (mController)
	{
		mController->setComponentHandler (nullptr);
		if (mControllerInitialized)
			mController->terminate ();
		mControllerInitialized = false;
	}

	//---Terminate Processor Component
	if (mComponent && mComponentInitialized)
	{
		mComponent->terminate ();
		mComponentInitialized = false;
	}

	mInputEvents = nullptr;
	mOutputEvents = nullptr;
	mUnitInfo = nullptr;
	mMidiMapping = nullptr;

	if (mMidiCCMapping[0][0])
		for (int32 b = 0; b < kMaxMidiMappingBusses; b++)
			for (int32 i = 0; i < 16; i++)
			{
				delete mMidiCCMapping[b][i];
				mMidiCCMapping[b][i] = nullptr;
			}

	mEditor = nullptr;
	mController = nullptr;
	mProcessor = nullptr;
	mComponent = nullptr;
	mFactory = nullptr;
	mPlugInterfaceSupport = nullptr;
	mProcessorConnection = nullptr;
	mControllerConnection = nullptr;
}

//------------------------------------------------------------------------
bool BaseWrapper::init ()
{
	// VST 3 stuff -----------------------------------------------
	if (mProcessor)
		mProcessor->queryInterface (IComponent::iid, (void**)&mComponent);
	if (mController)
	{
		mController->queryInterface (IUnitInfo::iid, (void**)&mUnitInfo);
		mController->queryInterface (IMidiMapping::iid, (void**)&mMidiMapping);
	}

	//---init the processor component
	mComponentInitialized = true;
	if (mComponent->initialize ((IHostApplication*)this) != kResultTrue)
		return false;

	//---init the controller component
	if (mController)
	{
		// do not initialize 2 times the component if it is singleComponent
		if (FUnknownPtr<IEditController> (mComponent).getInterface () != mController)
		{
			mControllerInitialized = true;
			if (mController->initialize ((IHostApplication*)this) != kResultTrue)
				return false;
		}

		//---set this class as Component Handler
		mController->setComponentHandler (this);

		//---connect the 2 components
		auto cp1 = U::cast<IConnectionPoint> (mProcessor);
		auto cp2 = U::cast<IConnectionPoint> (mController);
		if (cp1 && cp2)
		{
			mProcessorConnection = owned (NEW ConnectionProxy (cp1));
			mProcessorConnection->connect (cp2);

			mControllerConnection = owned (NEW ConnectionProxy (cp2));
			mControllerConnection->connect (cp1);

			mComponentsConnected = true;
		}

		//---inform the component "controller" with the component "processor" state
		MemoryStream stream;
		if (mComponent->getState (&stream) == kResultTrue)
		{
			stream.seek (0, IBStream::kIBSeekSet, nullptr);
			mController->setComponentState (&stream);
		}
	}

	// Wrapper -----------------------------------------------
	if (mProcessor)
	{
		if (mProcessor->canProcessSampleSize (kSample64) == kResultTrue)
		{
			_canDoubleReplacing (true); // supports double precision processing

			// we use the 64 as default only if 32 bit not supported
			if (mProcessor->canProcessSampleSize (kSample32) != kResultTrue)
				mVst3SampleSize = kSample64;
			else
				mVst3SampleSize = kSample32;
		}

		// latency -------------------------------
		_setInitialDelay (mProcessor->getLatencySamples ());

		if (mProcessor->getTailSamples () == kNoTail)
			_noTail (true);

		setupProcessing (); // initialize vst3 component processing parameters
	}

	// parameters
	setupParameters ();

	// setup inputs and outputs
	setupBuses ();

	// find out programs of root unit --------------------------
	mNumPrograms = 0;
	if (mUnitInfo)
	{
		int32 programListCount = mUnitInfo->getProgramListCount ();
		if (programListCount > 0)
		{
			ProgramListID rootUnitProgramListId = kNoProgramListId;
			for (int32 i = 0; i < mUnitInfo->getUnitCount (); i++)
			{
				UnitInfo unit = {};
				if (mUnitInfo->getUnitInfo (i, unit) == kResultTrue)
				{
					if (unit.id == kRootUnitId)
					{
						rootUnitProgramListId = unit.programListId;
						break;
					}
				}
			}

			if (rootUnitProgramListId != kNoProgramListId)
			{
				for (int32 i = 0; i < programListCount; i++)
				{
					ProgramListInfo progList = {};
					if (mUnitInfo->getProgramListInfo (i, progList) == kResultTrue)
					{
						if (progList.id == rootUnitProgramListId)
						{
							mNumPrograms = progList.programCount;
							break;
						}
					}
				}
			}
		}
	}

	if (mTimer == nullptr)
		mTimer = owned (Timer::create (this, 50));

	initMidiCtrlerAssignment ();

	return true;
}

//------------------------------------------------------------------------
void BaseWrapper::_suspend ()
{
	_stopProcess ();

	if (mComponent && mActive == true)
		mComponent->setActive (false);
	mActive = false;
}

//------------------------------------------------------------------------
void BaseWrapper::_resume ()
{
	if (mComponent && mActive == false)
		mComponent->setActive (true);
	mActive = true;
}

//------------------------------------------------------------------------
void BaseWrapper::_startProcess ()
{
	if (mProcessor && mProcessing == false)
	{
		mProcessing = true;
		mProcessor->setProcessing (true);
	}
}

//------------------------------------------------------------------------
void BaseWrapper::_stopProcess ()
{
	if (mProcessor && mProcessing == true)
	{
		mProcessor->setProcessing (false);
		mProcessing = false;
	}
}

//------------------------------------------------------------------------
void BaseWrapper::_setEditor (BaseEditorWrapper* editor)
{
	mEditor = owned (editor);
}

//------------------------------------------------------------------------
bool BaseWrapper::_setBlockSize (int32 newBlockSize)
{
	if (mProcessing)
		return false;

	if (mBlockSize != newBlockSize)
	{
		mBlockSize = newBlockSize;
		setupProcessing ();
		return true;
	}
	return false;
}

//------------------------------------------------------------------------
bool BaseWrapper::setupProcessing (int32 processModeOverwrite)
{
	if (!mProcessor)
		return false;

	ProcessSetup setup;
	if (processModeOverwrite >= 0)
		setup.processMode = processModeOverwrite;
	else
		setup.processMode = mVst3processMode;
	setup.maxSamplesPerBlock = mBlockSize;
	setup.sampleRate = mSampleRate;
	setup.symbolicSampleSize = mVst3SampleSize;

	return mProcessor->setupProcessing (setup) == kResultTrue;
}

//------------------------------------------------------------------------
bool BaseWrapper::getEditorSize (int32& width, int32& height) const
{
	if (auto* editor = getEditor ())
	{
		ViewRect rect;
		if (editor->getRect (rect))
		{
			width = rect.right - rect.left;
			height = rect.bottom - rect.top;
			return true;
		}
	}
	return false;
}

//------------------------------------------------------------------------
float BaseWrapper::_getParameter (int32 index) const
{
	if (!mController)
		return 0.f;

	if (index < static_cast<int32> (mParameterMap.size ()))
	{
		ParamID id = mParameterMap.at (index).vst3ID;
		return (float)mController->getParamNormalized (id);
	}

	return 0.f;
}

//------------------------------------------------------------------------
void BaseWrapper::addParameterChange (ParamID id, ParamValue value, int32 sampleOffset)
{
	mGuiTransfer.addChange (id, value, sampleOffset);
	mInputTransfer.addChange (id, value, sampleOffset);
}

//------------------------------------------------------------------------
/*!	Usually VST 2 hosts call setParameter (...) and getParameterDisplay (...) synchronously.
In setParameter (...) param changes get queued (guiTransfer) and transfered in idle (::onTimer).
The ::onTimer call almost always comes AFTER getParameterDisplay (...) and therefore returns an
old
value. To avoid sending back old values, getLastParamChange (...) returns the latest value
from the guiTransfer queue. */
//------------------------------------------------------------------------
bool BaseWrapper::getLastParamChange (ParamID id, ParamValue& value)
{
	ParameterChanges changes;
	mGuiTransfer.transferChangesTo (changes);
	for (int32 i = 0; i < changes.getParameterCount (); ++i)
	{
		IParamValueQueue* queue = changes.getParameterData (i);
		if (queue)
		{
			if (queue->getParameterId () == id)
			{
				int32 points = queue->getPointCount ();
				if (points > 0)
				{
					mGuiTransfer.transferChangesFrom (changes);
					int32 sampleOffset = 0;
					return queue->getPoint (points - 1, sampleOffset, value) == kResultTrue;
				}
			}
		}
	}

	mGuiTransfer.transferChangesFrom (changes);
	return false;
}

//------------------------------------------------------------------------
void BaseWrapper::getUnitPath (UnitID unitID, String& path) const
{
	if (!mUnitInfo)
		return;

	//! Build the unit path up to the root unit (e.g. "Modulators.LFO 1.". Separator is a ".")
	for (int32 unitIndex = 0; unitIndex < mUnitInfo->getUnitCount (); ++unitIndex)
	{
		UnitInfo info = {};
		mUnitInfo->getUnitInfo (unitIndex, info);
		if (info.id == unitID)
		{
			String unitName (info.name);
			unitName.append (".");
			path.insertAt (0, unitName);
			if (info.parentUnitId != kRootUnitId)
				getUnitPath (info.parentUnitId, path);

			break;
		}
	}
}
//------------------------------------------------------------------------
int32 BaseWrapper::_getChunk (void** data, bool /*isPreset*/)
{
	// Host stores plug-in state. Returns the size in bytes of the chunk (Plug-in allocates the data
	// array)
	MemoryStream componentStream;
	if (mComponent && mComponent->getState (&componentStream) != kResultTrue)
		componentStream.setSize (0);

	MemoryStream controllerStream;
	if (mController && mController->getState (&controllerStream) != kResultTrue)
		controllerStream.setSize (0);

	if (componentStream.getSize () + controllerStream.getSize () == 0)
		return 0;

	mChunk.setSize (0);
	IBStreamer acc (&mChunk, kLittleEndian);

	acc.writeInt64 (componentStream.getSize ());
	acc.writeInt64 (controllerStream.getSize ());

	acc.writeRaw (componentStream.getData (), static_cast<int32> (componentStream.getSize ()));
	acc.writeRaw (controllerStream.getData (), static_cast<int32> (controllerStream.getSize ()));

	auto chunkSize = static_cast<int32> (mChunk.getSize ());
	*data = mChunk.getData ();
	return chunkSize;
}

//------------------------------------------------------------------------
int32 BaseWrapper::_setChunk (void* data, int32 byteSize, bool isPreset)
{
	if (!mComponent)
		return 0;

	// throw away all previously queued parameter changes, they are obsolete
	mGuiTransfer.removeChanges ();
	mInputTransfer.removeChanges ();

	MemoryStream chunk (data, byteSize);
	IBStreamer acc (&chunk, kLittleEndian);

	int64 componentDataSize = 0;
	int64 controllerDataSize = 0;

	acc.readInt64 (componentDataSize);
	acc.readInt64 (controllerDataSize);

	VstPresetStream componentStream (((char*)data) + acc.tell (), componentDataSize);
	VstPresetStream controllerStream (((char*)data) + acc.tell () + componentDataSize,
	                                  controllerDataSize);

	if (!isPreset)
	{
		if (Vst::IAttributeList* attr = componentStream.getAttributes ())
			attr->setString (Vst::PresetAttributes::kStateType,
				String (Vst::StateType::kProject));
		if (Vst::IAttributeList* attr = controllerStream.getAttributes ())
			attr->setString (Vst::PresetAttributes::kStateType,
				String (Vst::StateType::kProject));
	}

	mComponent->setState (&componentStream);
	componentStream.seek (0, IBStream::kIBSeekSet, nullptr);

	if (mController)
	{
		mController->setComponentState (&componentStream);
		mController->setState (&controllerStream);
	}

	return 0;
}

//------------------------------------------------------------------------
bool BaseWrapper::_setBypass (bool onOff)
{
	if (mBypassParameterID != kNoParamId)
	{
		addParameterChange (mBypassParameterID, onOff ? 1.0 : 0.0, 0);
		return true;
	}
	return false;
}

//-----------------------------------------------------------------------------
bool BaseWrapper::getProgramListAndUnit (int32 midiChannel, UnitID& unitId,
                                         ProgramListID& programListId)
{
	programListId = kNoProgramListId;
	unitId = -1;
	if (!mUnitInfo)
		return false;

	// use the first input event bus (VST 2 has only 1 bus for event)
	if (mUnitInfo->getUnitByBus (kEvent, kInput, 0, midiChannel, unitId) == kResultTrue)
	{
		for (int32 i = 0, unitCount = mUnitInfo->getUnitCount (); i < unitCount; i++)
		{
			UnitInfo unitInfoStruct = {};
			if (mUnitInfo->getUnitInfo (i, unitInfoStruct) == kResultTrue)
			{
				if (unitId == unitInfoStruct.id)
				{
					programListId = unitInfoStruct.programListId;
					return programListId != kNoProgramListId;
				}
			}
		}
	}

	return false;
}

//-----------------------------------------------------------------------------
bool BaseWrapper::getProgramListInfoByProgramListID (ProgramListID programListId,
                                                     ProgramListInfo& info)
{
	if (mUnitInfo)
	{
		int32 programListCount = mUnitInfo->getProgramListCount ();
		for (int32 i = 0; i < programListCount; i++)
		{
			memset (&info, 0, sizeof (ProgramListInfo));
			if (mUnitInfo->getProgramListInfo (i, info) == kResultTrue)
			{
				if (info.id == programListId)
				{
					return true;
				}
			}
		}
	}
	return false;
}

//-----------------------------------------------------------------------------
void BaseWrapper::setVendorName (char* name)
{
	memcpy (mVendor, name, sizeof (mVendor));
}

//-----------------------------------------------------------------------------
void BaseWrapper::setEffectName (char* effectName)
{
	memcpy (mName, effectName, sizeof (mName));
}

//-----------------------------------------------------------------------------
void BaseWrapper::setEffectVersion (char* version)
{
	if (!version)
		mVersion = 0;
	else
	{
		long major = 1;
		long minor = 0;
		long subminor = 0;
		long subsubminor = 0;
		int32 ret = sscanf (version, "%ld.%ld.%ld.%ld", &major, &minor, &subminor, &subsubminor);
		mVersion = static_cast<int32> ((major & 0xff) << 24);
		if (ret > 3)
			mVersion += static_cast<int32> (subsubminor & 0xff);
		if (ret > 2)
			mVersion += static_cast<int32> ((subminor & 0xff) << 8);
		if (ret > 1)
			mVersion += static_cast<int32> ((minor & 0xff) << 16);
	}
}

//-----------------------------------------------------------------------------
void BaseWrapper::setSubCategories (char* string)
{
	memcpy (mSubCategories, string, sizeof (mSubCategories));
}

//-----------------------------------------------------------------------------
void BaseWrapper::setupBuses ()
{
	if (!mComponent)
		return;

	mProcessData.prepare (*mComponent, 0, mVst3SampleSize);

	_setNumInputs (countMainBusChannels (kInput, mMainAudioInputBuses));
	_setNumOutputs (countMainBusChannels (kOutput, mMainAudioOutputBuses));

	mHasEventInputBuses = mComponent->getBusCount (kEvent, kInput) > 0;
	mHasEventOutputBuses = mComponent->getBusCount (kEvent, kOutput) > 0;

	if (mHasEventInputBuses)
	{
		if (mInputEvents == nullptr)
			mInputEvents = owned (new EventList (kMaxEvents));
	}
	else
		mInputEvents = nullptr;

	if (mHasEventOutputBuses)
	{
		if (mOutputEvents == nullptr)
			mOutputEvents = owned (new EventList (kMaxEvents));
	}
	else
		mOutputEvents = nullptr;
}

//-----------------------------------------------------------------------------
void BaseWrapper::setupParameters ()
{
	mParameterMap.clear ();
	mParamIndexMap.clear ();
	mBypassParameterID = mProgramParameterID = kNoParamId;
	mProgramParameterIdx = -1;

	std::vector<ParameterInfo> programParameterInfos;
	std::vector<int32> programParameterIdxs;

	int32 paramCount = mController ? mController->getParameterCount () : 0;
	int32 numParamID = 0;
	for (int32 i = 0; i < paramCount; i++)
	{
		ParameterInfo paramInfo = {};
		if (mController->getParameterInfo (i, paramInfo) == kResultTrue)
		{
			//--- ------------------------------------------
			if ((paramInfo.flags & ParameterInfo::kIsBypass) != 0)
			{
				if (mBypassParameterID == kNoParamId)
					mBypassParameterID = paramInfo.id;
				if (mUseExportedBypass)
				{
					ParamMapEntry entry = {paramInfo.id, i};
					mParameterMap.push_back (entry);
					mParamIndexMap[paramInfo.id] = mUseIncIndex ? numParamID : i;
					numParamID++;
				}
			}
			//--- ------------------------------------------
			else if ((paramInfo.flags & ParameterInfo::kIsProgramChange) != 0)
			{
				programParameterInfos.push_back (paramInfo);
				programParameterIdxs.push_back (i);
				if (paramInfo.unitId == kRootUnitId)
				{
					if (mProgramParameterID == kNoParamId)
					{
						mProgramParameterID = paramInfo.id;
						mProgramParameterIdx = i;
					}
				}

				if (gExportProgramChangeParameters == true)
				{
					ParamMapEntry entry = {paramInfo.id, i};
					mParameterMap.push_back (entry);
					mParamIndexMap[paramInfo.id] = mUseIncIndex ? numParamID : i;
					numParamID++;
				}
			}
			//--- ------------------------------------------
			// do not export read only parameters
			else if ((paramInfo.flags & ParameterInfo::kIsReadOnly) == 0)
			{
				ParamMapEntry entry = {paramInfo.id, i};
				mParameterMap.push_back (entry);
				mParamIndexMap[paramInfo.id] = mUseIncIndex ? numParamID : i;
				numParamID++;
			}
		}
	}

	mNumParams = static_cast<int32> (mParameterMap.size ());

	mInputTransfer.setMaxParameters (paramCount);
	mOutputTransfer.setMaxParameters (paramCount);
	mGuiTransfer.setMaxParameters (paramCount);
	mInputChanges.setMaxParameters (paramCount);
	mOutputChanges.setMaxParameters (paramCount);

	for (int32 midiChannel = 0; midiChannel < kMaxProgramChangeParameters; midiChannel++)
	{
		mProgramChangeParameterIDs[midiChannel] = kNoParamId;
		mProgramChangeParameterIdxs[midiChannel] = -1;

		UnitID unitId;
		ProgramListID programListId;
		if (getProgramListAndUnit (midiChannel, unitId, programListId))
		{
			for (uint32 i = 0; i < programParameterInfos.size (); i++)
			{
				const ParameterInfo& paramInfo = programParameterInfos.at (i);
				if (paramInfo.unitId == unitId)
				{
					mProgramChangeParameterIDs[midiChannel] = paramInfo.id;
					mProgramChangeParameterIdxs[midiChannel] = programParameterIdxs.at (i);
					break;
				}
			}
		}
	}
}

//-----------------------------------------------------------------------------
void BaseWrapper::initMidiCtrlerAssignment ()
{
	if (!mMidiMapping || !mComponent)
		return;

	int32 busses = Min<int32> (mComponent->getBusCount (kEvent, kInput), kMaxMidiMappingBusses);

	if (!mMidiCCMapping[0][0])
	{
		for (int32 b = 0; b < busses; b++)
			for (int32 i = 0; i < 16; i++)
				mMidiCCMapping[b][i] = NEW ParamID[Vst::kCountCtrlNumber];
	}

	ParamID paramID;
	for (int32 b = 0; b < busses; b++)
	{
		for (int16 ch = 0; ch < 16; ch++)
		{
			for (int32 i = 0; i < Vst::kCountCtrlNumber; i++)
			{
				paramID = kNoParamId;
				if (mMidiMapping->getMidiControllerAssignment (b, ch, (CtrlNumber)i, paramID) ==
				    kResultTrue)
				{
					// TODO check if tag is associated to a parameter
					mMidiCCMapping[b][ch][i] = paramID;
				}
				else
					mMidiCCMapping[b][ch][i] = kNoParamId;
			}
		}
	}
}

//-----------------------------------------------------------------------------
void BaseWrapper::_setSampleRate (float newSamplerate)
{
	if (mProcessing)
		return;

	if (newSamplerate != mSampleRate)
	{
		mSampleRate = newSamplerate;
		setupProcessing ();
	}
}

//-----------------------------------------------------------------------------
uint32 BaseWrapper::countMainBusChannels (BusDirection dir, uint64& mainBusBitset)
{
	uint32 result = 0;
	mainBusBitset = 0;

	int32 busCount = mComponent->getBusCount (kAudio, dir);
	for (int32 i = 0; i < busCount; i++)
	{
		BusInfo busInfo = {};
		if (mComponent->getBusInfo (kAudio, dir, i, busInfo) == kResultTrue)
		{
			if (busInfo.busType == kMain)
			{
				result += busInfo.channelCount;
				mainBusBitset |= (uint64 (1) << i);

				mComponent->activateBus (kAudio, dir, i, true);
			}
			else if (busInfo.flags & BusInfo::kDefaultActive)
			{
				mComponent->activateBus (kAudio, dir, i, false);
			}
		}
	}
	return result;
}

//-----------------------------------------------------------------------------
void BaseWrapper::processMidiEvent (Event& toAdd, char* midiData, bool isLive, int32 noteLength,
                                    float noteOffVelocity, float detune)
{
	uint8 status = static_cast<uint8> (midiData[0] & kStatusMask);
	uint8 channel = static_cast<uint8> (midiData[0] & kChannelMask);

	// not allowed
	if (channel >= 16)
		return;

	if (isLive)
		toAdd.flags |= Event::kIsLive;

	//--- -----------------------------
	switch (status)
	{
		case kNoteOn:
		case kNoteOff:
		{
			if (status == kNoteOff || midiData[2] == 0) // note off
			{
				toAdd.type = Event::kNoteOffEvent;
				toAdd.noteOff.channel = channel;
				toAdd.noteOff.pitch = midiData[1];
				toAdd.noteOff.velocity = noteOffVelocity;
				toAdd.noteOff.noteId = -1; // TODO ?
			}
			else if (status == kNoteOn) // note on
			{
				toAdd.type = Event::kNoteOnEvent;
				toAdd.noteOn.channel = channel;
				toAdd.noteOn.pitch = midiData[1];
				toAdd.noteOn.tuning = detune;
				toAdd.noteOn.velocity = (float)midiData[2] * kMidiScaler;
				toAdd.noteOn.length = noteLength;
				toAdd.noteOn.noteId = -1; // TODO ?
			}
			mInputEvents->addEvent (toAdd);
		}
		break;
		//--- -----------------------------
		case kPolyPressure:
		{
			toAdd.type = Vst::Event::kPolyPressureEvent;
			toAdd.polyPressure.channel = channel;
			toAdd.polyPressure.pitch = static_cast<int16>(midiData[1] & kDataMask);
			toAdd.polyPressure.pressure = (float)(midiData[2] & kDataMask) * kMidiScaler;
			toAdd.polyPressure.noteId = -1; // TODO ?

			mInputEvents->addEvent (toAdd);
		}
		break;
		//--- -----------------------------
		case kController:
		{
			if (toAdd.busIndex < kMaxMidiMappingBusses && mMidiCCMapping[toAdd.busIndex][channel])
			{
				ParamID paramID =
				    mMidiCCMapping[toAdd.busIndex][channel][static_cast<size_t> (midiData[1])];
				if (paramID != kNoParamId)
				{
					ParamValue value = (double)midiData[2] * kMidiScaler;

					int32 index = 0;
					if (IParamValueQueue* queue = mInputChanges.addParameterData (paramID, index))
						queue->addPoint (toAdd.sampleOffset, value, index);

					mGuiTransfer.addChange (paramID, value, toAdd.sampleOffset);
				}
			}
		}
		break;
		//--- -----------------------------
		case kPitchBendStatus:
		{
			if (toAdd.busIndex < kMaxMidiMappingBusses && mMidiCCMapping[toAdd.busIndex][channel])
			{
				ParamID paramID = mMidiCCMapping[toAdd.busIndex][channel][Vst::kPitchBend];
				if (paramID != kNoParamId)
				{
					const double kPitchWheelScaler = 1. / (double)0x3FFF;

					const int32 ctrl = static_cast<int32> ((midiData[1] & kDataMask) | ((midiData[2] & kDataMask) << 7));
					ParamValue value = kPitchWheelScaler * (double)ctrl;

					int32 index = 0;
					if (IParamValueQueue* queue = mInputChanges.addParameterData (paramID, index))					
						queue->addPoint (toAdd.sampleOffset, value, index);
					
					mGuiTransfer.addChange (paramID, value, toAdd.sampleOffset);
				}
			}
		}
		break;
		//--- -----------------------------
		case kAfterTouchStatus:
		{
			if (toAdd.busIndex < kMaxMidiMappingBusses && mMidiCCMapping[toAdd.busIndex][channel])
			{
				ParamID paramID = mMidiCCMapping[toAdd.busIndex][channel][Vst::kAfterTouch];
				if (paramID != kNoParamId)
				{
					ParamValue value = (ParamValue) (midiData[1] & kDataMask) * kMidiScaler;

					int32 index = 0;
					if (IParamValueQueue* queue = mInputChanges.addParameterData (paramID, index))
						queue->addPoint (toAdd.sampleOffset, value, index);

					mGuiTransfer.addChange (paramID, value, toAdd.sampleOffset);
				}
			}
		}
		break;
		//--- -----------------------------
		case kProgramChangeStatus:
		{
			if (mProgramChangeParameterIDs[channel] != kNoParamId &&
			    mProgramChangeParameterIdxs[channel] != -1)
			{
				ParameterInfo paramInfo = {};
				if (mController->getParameterInfo (mProgramChangeParameterIdxs[channel],
				                                   paramInfo) == kResultTrue)
				{
					int32 program = midiData[1];
					if (paramInfo.stepCount > 0 && program <= paramInfo.stepCount)
					{
						ParamValue normalized =
						    (ParamValue)program / (ParamValue)paramInfo.stepCount;
						addParameterChange (mProgramChangeParameterIDs[channel], normalized,
						                    toAdd.sampleOffset);
					}
				}
			}
		}
		break;
	}
}

//-----------------------------------------------------------------------------
template <class T>
inline void BaseWrapper::setProcessingBuffers (T** inputs, T** outputs)
{
	// set processing buffers
	int32 sourceIndex = 0;
	for (int32 i = 0; i < mProcessData.numInputs; i++)
	{
		AudioBusBuffers& buffers = mProcessData.inputs[i];
		if (mMainAudioInputBuses & (uint64 (1) << i))
		{
			for (int32 j = 0; j < buffers.numChannels; j++)
			{
				buffers.channelBuffers32[j] = (Sample32*)inputs[sourceIndex++];
			}
		}
		else
			buffers.silenceFlags = HostProcessData::kAllChannelsSilent;
	}

	sourceIndex = 0;
	for (int32 i = 0; i < mProcessData.numOutputs; i++)
	{
		AudioBusBuffers& buffers = mProcessData.outputs[i];
		buffers.silenceFlags = 0;
		if (mMainAudioOutputBuses & (uint64 (1) << i))
		{
			for (int32 j = 0; j < buffers.numChannels; j++)
			{
				buffers.channelBuffers32[j] = (Sample32*)outputs[sourceIndex++];
			}
		}
	}
}

//-----------------------------------------------------------------------------
inline void BaseWrapper::setEventPPQPositions ()
{
	if (!mInputEvents)
		return;

	int32 eventCount = mInputEvents->getEventCount ();
	if (eventCount > 0 && (mProcessContext.state & ProcessContext::kTempoValid) &&
	    (mProcessContext.state & ProcessContext::kProjectTimeMusicValid))
	{
		TQuarterNotes projectTimeMusic = mProcessContext.projectTimeMusic;
		double secondsToQuarterNoteScaler = mProcessContext.tempo / 60.0;
		double multiplicator = secondsToQuarterNoteScaler / mSampleRate;

		for (int32 i = 0; i < eventCount; i++)
		{
			Event* e = mInputEvents->getEventByIndex (i);
			if (e)
			{
				TQuarterNotes localTimeMusic = e->sampleOffset * multiplicator;
				e->ppqPosition = projectTimeMusic + localTimeMusic;
			}
		}
	}
}

//-----------------------------------------------------------------------------
inline void BaseWrapper::doProcess (int32 sampleFrames)
{
	if (!mProcessor)
		return;

	mProcessData.numSamples = sampleFrames;

	if (mProcessing == false)
		_startProcess ();

	mProcessData.inputEvents = mInputEvents;
	mProcessData.outputEvents = mOutputEvents;

	setupProcessTimeInfo ();
	setEventPPQPositions ();

	mInputTransfer.transferChangesTo (mInputChanges);

	mProcessData.inputParameterChanges = &mInputChanges;
	mProcessData.outputParameterChanges = &mOutputChanges;
	mOutputChanges.clearQueue ();

	// VST 3 process call
	mProcessor->process (mProcessData);

	processOutputParametersChanges ();

	mOutputTransfer.transferChangesFrom (mOutputChanges);
	processOutputEvents ();

	// clear input parameters and events
	mInputChanges.clearQueue ();
	if (mInputEvents)
		mInputEvents->clear ();
}

//-----------------------------------------------------------------------------
void BaseWrapper::_processReplacing (float** inputs, float** outputs, int32 sampleFrames)
{
	if (mProcessData.symbolicSampleSize != kSample32)
		return;

	setProcessingBuffers<float> (inputs, outputs);
	doProcess (sampleFrames);
}

//-----------------------------------------------------------------------------
void BaseWrapper::_processDoubleReplacing (double** inputs, double** outputs, int32 sampleFrames)
{
	if (mProcessData.symbolicSampleSize != kSample64)
		return;

	setProcessingBuffers<double> (inputs, outputs);
	doProcess (sampleFrames);
}

//-----------------------------------------------------------------------------
void BaseWrapper::onTimer (Timer*)
{
	if (!mController)
		return;

	ParamID id;
	ParamValue value;
	int32 sampleOffset;

	while (mOutputTransfer.getNextChange (id, value, sampleOffset))
	{
		mController->setParamNormalized (id, value);
	}
	while (mGuiTransfer.getNextChange (id, value, sampleOffset))
	{
		mController->setParamNormalized (id, value);
	}
}

// FUnknown
//-----------------------------------------------------------------------------
tresult PLUGIN_API BaseWrapper::queryInterface (const char* iid, void** obj)
{
	QUERY_INTERFACE (iid, obj, FUnknown::iid, Vst::IHostApplication)
	QUERY_INTERFACE (iid, obj, Vst::IHostApplication::iid, Vst::IHostApplication)
	QUERY_INTERFACE (iid, obj, Vst::IComponentHandler::iid, Vst::IComponentHandler)
	QUERY_INTERFACE (iid, obj, Vst::IUnitHandler::iid, Vst::IUnitHandler)

	if (mPlugInterfaceSupport && mPlugInterfaceSupport->queryInterface (iid, obj) == kResultTrue)
		return ::Steinberg::kResultOk;

	*obj = nullptr;
	return kNoInterface;
}

//-----------------------------------------------------------------------------
tresult PLUGIN_API BaseWrapper::restartComponent (int32 flags)
{
	tresult result = kResultFalse;

	//--- ----------------------
	if (flags & kIoChanged)
	{
		setupBuses ();
		_ioChanged ();
		result = kResultTrue;
	}

	//--- ----------------------
	if ((flags & kParamValuesChanged) || (flags & kParamTitlesChanged))
	{
		_updateDisplay ();
		result = kResultTrue;
	}

	//--- ----------------------
	if (flags & kLatencyChanged)
	{
		if (mProcessor)
			_setInitialDelay (mProcessor->getLatencySamples ());

		_ioChanged ();
		result = kResultTrue;
	}

	//--- ----------------------
	if (flags & kMidiCCAssignmentChanged)
	{
		initMidiCtrlerAssignment ();
		result = kResultTrue;
	}

	// kReloadComponent is Not supported

	return result;
}

//-----------------------------------------------------------------------------
// IHostApplication
//-----------------------------------------------------------------------------
tresult PLUGIN_API BaseWrapper::createInstance (TUID cid, TUID iid, void** obj)
{
	FUID classID (FUID::fromTUID (cid));
	FUID interfaceID (FUID::fromTUID (iid));
	if (classID == IMessage::iid && interfaceID == IMessage::iid)
	{
		*obj = new HostMessage;
		return kResultTrue;
	}
	else if (classID == IAttributeList::iid && interfaceID == IAttributeList::iid)
	{
		if (auto al = HostAttributeList::make ())
		{
			*obj = al.take ();
			return kResultTrue;
		}
		return kOutOfMemory;
	}
	*obj = nullptr;
	return kResultFalse;
}

//-----------------------------------------------------------------------------
// IUnitHandler
//-----------------------------------------------------------------------------
tresult PLUGIN_API BaseWrapper::notifyUnitSelection (UnitID /*unitId*/)
{
	return kResultTrue;
}

//-----------------------------------------------------------------------------
tresult PLUGIN_API BaseWrapper::notifyProgramListChange (ProgramListID /*listId*/, int32 /*programIndex*/)
{
	// TODO -> redirect to hasMidiProgramsChanged somehow...
	return kResultTrue;
}

//-----------------------------------------------------------------------------
} // namespace Vst
} // namespace Steinberg

//-----------------------------------------------------------------------------

/// \endcond
