/*
    This file is part of Equalizer APO, a system-wide equalizer.
    Copyright (C) 2017  Jonas Thedering

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "stdafx.h"
#include <wincrypt.h>
#include <inttypes.h>
#include "StringHelper.h"
#include "version.h"
#include "VSTPluginLibrary.h"
#include "VSTPluginInstance.h"

using namespace std;

#define equalizerApoVSTID CCONST('E', 'A', 'P', 'O');

static intptr_t callback(AEffect* effect, int32_t opcode, int32_t index, intptr_t value, void* ptr, float opt)
{
	VSTPluginInstance* instance = effect != NULL ? (VSTPluginInstance*)effect->user : NULL;
#ifdef _DEBUG
	printf("vst: %p opcode: %d index: %d value: %" PRIdPTR " ptr: %p opt: %f user: %p\n",
		effect, opcode, index, value, ptr, opt, effect != NULL ? effect->user : NULL);
	fflush(stdout);
#endif

	switch (opcode)
	{
	case audioMasterVersion:
		return 2400;

	case audioMasterCurrentId:
		return equalizerApoVSTID;

	case audioMasterGetProductString:
		strcpy_s((char*) ptr, 64, "Equalizer APO");
		return 1;

	case audioMasterGetVendorVersion:
		return (intptr_t) (MAJOR << 24 | MINOR << 16 | REVISION << 8 | 0);

	case audioMasterPinConnected:
		if (instance != NULL)
			return index < instance->getUsedChannelCount() ? 0 : 1;
		else
			return 0;

	case audioMasterNeedIdle:
		return effect != NULL ? effect->dispatcher(effect, effIdle, 0, 0, NULL, 0.0f) : 0;

	case audioMasterUpdateDisplay:
		return effect != NULL ? effect->dispatcher(effect, effEditIdle, 0, 0, NULL, 0.0f) : 0;

	case audioMasterGetTime:
		return 0;

	case audioMasterGetSampleRate:
		if (instance != NULL)
			return (intptr_t)instance->getSampleRate();
		return 0;

	case audioMasterGetCurrentProcessLevel:
		if (instance != NULL)
			return instance->getProcessLevel();
		return 0;

	case audioMasterGetLanguage:
		if (instance != NULL)
			return instance->getLanguage();
		return 0;

	case audioMasterWillReplaceOrAccumulate:
		return 1;

	case audioMasterSizeWindow:
		if (instance != NULL)
			instance->onSizeWindow((int)index, (int)value);
		return 1;

	case audioMasterCanDo:
		{
			char* s = (char*)ptr;
#ifdef _DEBUG
			printf("VST canDo: %s\n", s);
			fflush(stdout);
#endif
			if (strcmp(s, "startStopProcess") == 0 ||
				strcmp(s, "sizeWindow") == 0)
				return 1;
		}
		return 0;

	case audioMasterAutomate:
		if (instance != NULL)
			instance->onAutomate();
		return 0;

	case audioMasterBeginEdit:
	case audioMasterEndEdit:
	case audioMasterIdle:
	case audioMasterWantMidi:
		return 0;
	}

	return 0;
}

VSTPluginInstance::VSTPluginInstance(const std::shared_ptr<VSTPluginLibrary>& library, int processLevel)
	: library(library), processLevel(processLevel)
{
}

VSTPluginInstance::~VSTPluginInstance()
{
	if (effect != NULL)
	{
		effect->dispatcher(effect, effClose, 0, 0, NULL, 0.0f);
		effect = NULL;
	}
}

bool VSTPluginInstance::initialize()
{
	bool result = true;

	__try
	{
		effect = library->VSTPluginMain(callback);
		effect->user = this;
		if (effect->magic == kEffectMagic)
		{
			effect->dispatcher(effect, effOpen, 0, 0, NULL, 0.0f);

			usedChannelCount = max(numInputs(), numOutputs());
		}
		else
		{
			effect = NULL;
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		result = false;
	}

	return result;
}

int VSTPluginInstance::numInputs() const
{
	if (effect == NULL)
		return 0;

	return effect->numInputs;
}

int VSTPluginInstance::numOutputs() const
{
	if (effect == NULL)
		return 0;

	return effect->numOutputs;
}

bool VSTPluginInstance::canReplacing() const
{
	if (effect == NULL)
		return true;

	return (effect->flags & effFlagsCanReplacing) != 0;
}

int VSTPluginInstance::uniqueID() const
{
	if (effect == NULL)
		return 0;

	return effect->uniqueID;
}

std::wstring VSTPluginInstance::getName() const
{
	if (effect == NULL)
		return L"";

	char buf[256];
	memset(buf, 0, sizeof(buf));
	effect->dispatcher(effect, effGetEffectName, 0, 0, buf, 0.0f);
	buf[255] = '\0'; // just to be sure

	return StringHelper::toWString(buf, CP_UTF8);
}

int VSTPluginInstance::getUsedChannelCount() const
{
	return usedChannelCount;
}

void VSTPluginInstance::setUsedChannelCount(int count)
{
	usedChannelCount = count;
}

float VSTPluginInstance::getSampleRate() const
{
	return sampleRate;
}

int VSTPluginInstance::getProcessLevel() const
{
	return processLevel;
}

void VSTPluginInstance::setProcessLevel(int value)
{
	processLevel = value;
}

int VSTPluginInstance::getLanguage() const
{
	return language;
}

void VSTPluginInstance::setLanguage(int value)
{
	language = value;
}

void VSTPluginInstance::prepareForProcessing(float sampleRate, int blockSize)
{
	if (effect == NULL)
		return;

	this->sampleRate = sampleRate;
	effect->dispatcher(effect, effSetSampleRate, 0, 0, NULL, sampleRate);
	effect->dispatcher(effect, effSetBlockSize, 0, blockSize, NULL, 0.0f);
}

void VSTPluginInstance::writeToEffect(const std::wstring& chunkData, const std::unordered_map<std::wstring, float>& paramMap)
{
	if (effect == NULL)
		return;

	if (effect->flags & effFlagsProgramChunks)
	{
		if (chunkData != L"")
		{
			DWORD bufSize = 0;
			CryptStringToBinaryW(chunkData.c_str(), 0, CRYPT_STRING_BASE64, NULL, &bufSize, NULL, NULL);
			BYTE* buf = new BYTE[bufSize];
			if (CryptStringToBinaryW(chunkData.c_str(), 0, CRYPT_STRING_BASE64, buf, &bufSize, NULL, NULL) == TRUE)
				effect->dispatcher(effect, effSetChunk, 1, bufSize, buf, 0.0f);
			delete[] buf;
		}
	}
	else
	{
		for (int i = 0; i < effect->numParams; i++)
		{
			char buf[256];
			effect->dispatcher(effect, effGetParamName, i, 0, buf, 0.0f);
			buf[255] = '\0'; // just to be sure
			wstring name = StringHelper::toWString(buf, CP_UTF8);
			auto it = paramMap.find(name);
			if (it != paramMap.end())
				effect->setParameter(effect, i, it->second);
		}
	}
}

void VSTPluginInstance::readFromEffect(std::wstring& chunkData, std::unordered_map<std::wstring, float>& paramMap) const
{
	if (effect == NULL)
		return;

	chunkData = L"";
	paramMap.clear();

	if (effect->flags & effFlagsProgramChunks)
	{
		BYTE* chunk = NULL;
		int size = (int)effect->dispatcher(effect, effGetChunk, 1, 0, &chunk, 0.0f);
		DWORD stringLength = 0;
		CryptBinaryToStringW(chunk, size, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, NULL, &stringLength);
		wchar_t* string = new wchar_t[stringLength];
		if (CryptBinaryToStringW(chunk, size, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, string, &stringLength) == TRUE)
			chunkData = string;
		delete[] string;
	}
	else
	{
		for (int i = 0; i < effect->numParams; i++)
		{
			char buf[256];
			effect->dispatcher(effect, effGetParamName, i, 0, buf, 0.0f);
			buf[255] = '\0'; // just to be sure
			float value = effect->getParameter(effect, i);
			paramMap[StringHelper::toWString(buf, CP_UTF8)] = value;
		}
	}
}

void VSTPluginInstance::startProcessing()
{
	if (effect == NULL)
		return;

	effect->dispatcher(effect, effMainsChanged, 0, 1, NULL, 0.0f);
	effect->dispatcher(effect, effStartProcess, 0, 0, NULL, 0.0f);
}

void VSTPluginInstance::processReplacing(float** inputArray, float** outputArray, int frameCount)
{
	if (effect == NULL)
		return;

	effect->processReplacing(effect, inputArray, outputArray, frameCount);
}

void VSTPluginInstance::process(float** inputArray, float** outputArray, int frameCount)
{
	if (effect == NULL)
		return;

	effect->process(effect, inputArray, outputArray, frameCount);
}

void VSTPluginInstance::stopProcessing()
{
	if (effect == NULL)
		return;

	effect->dispatcher(effect, effStopProcess, 0, 0, NULL, 0.0f);
	effect->dispatcher(effect, effMainsChanged, 0, 0, NULL, 0.0f);
}

void VSTPluginInstance::startEditing(HWND hWnd, short* width, short* height)
{
	if (effect == NULL)
		return;

	VstRect* rect;
	effect->dispatcher(effect, effEditGetRect, 0, 0, &rect, 0.0f);
	effect->dispatcher(effect, effEditOpen, 0, 0, hWnd, 0.0f);
	effect->dispatcher(effect, effEditGetRect, 0, 0, &rect, 0.0f);

	*width = rect->right - rect->left;
	*height = rect->bottom - rect->top;
}

void VSTPluginInstance::doIdle()
{
	if (effect == NULL)
		return;

	effect->dispatcher(effect, effEditIdle, 0, 0, NULL, 0.0f);
}

void VSTPluginInstance::stopEditing()
{
	if (effect == NULL)
		return;

	effect->dispatcher(effect, effEditClose, 0, 0, NULL, 0.0f);
}

void VSTPluginInstance::setAutomateFunc(std::function<void()> func)
{
	automateFunc = func;
}

void VSTPluginInstance::onAutomate()
{
	if (automateFunc)
		automateFunc();
}

void VSTPluginInstance::setSizeWindowFunc(std::function<void(int, int)> func)
{
	sizeWindowFunc = func;
}

void VSTPluginInstance::onSizeWindow(int w, int h)
{
	if (sizeWindowFunc)
		sizeWindowFunc(w, h);
}
