/*
 * Copyright 2012, Gerasim Troeglazov (3dEyes**), 3dEyes@gmail.com.
 * All rights reserved.
 * Distributed under the terms of the MIT License.
 */
 
#include <stdio.h>
#include <stdlib.h>
#include <image.h>
 
#include <Application.h>
 
#include "VSTHost.h"
 
static int32 VHostCallback(VSTEffect* effect, int32 opcode, int32 index,
	int32 value, void* ptr, float opt);
 
//Trim string
static void
TrimString(BString *string) {
	char* str = string->LockBuffer(256);
    uint32 k = 0;
    uint32 i = 0;
    for(i=0; str[i]!='\0';) {
        if (isspace(str[i])) {
            k = i;
            for(uint32 j = i; j < strlen(str) - 1; j++)
                str[j] = str[j + 1];
            str[strlen(str) - 1] = '\0';
            i = k;
        } else
            i++;
    }
    string->UnlockBuffer();
}
 
//VST Parameter class
VSTParameter::VSTParameter(VSTPlugin* plugin, int index)
{
	fIndex = index;
	fEffect = plugin->Effect();
	fDropList.MakeEmpty();
 
	char temp[256];
  	//get parameter name
  	temp[0] = 0;
	fEffect->dispatcher(fEffect, VST_GET_PARAM_NAME, index, 0, temp, 0);
	fName.SetTo(temp);
	TrimString(&fName);
	//get parameter label (unit)
	temp[0] = 0;
	fEffect->dispatcher(fEffect, VST_GET_PARAM_UNIT, index, 0, temp, 0);
	fUnit.SetTo(temp);
	ValidateValues(&fUnit);
	//store current value
	float val = fEffect->getParameter(fEffect, index);
	//test for minimum value
	fEffect->setParameter(fEffect, index, 0);
	temp[0] = 0;
	fEffect->dispatcher(fEffect, VST_GET_PARAM_STR, index, 0, temp, 0);
	fMinValue.SetTo(temp);
	ValidateValues(&fMinValue);
	//test for maximum value
	temp[0] = 0;
	fEffect->setParameter(fEffect, index, 1.0);
	fEffect->dispatcher(fEffect, VST_GET_PARAM_STR, index, 0, temp, 0);
	fMaxValue.SetTo(temp);
	ValidateValues(&fMaxValue);
	//test for discrete values
	char test_disp[VST_PARAM_TEST_COUNT][256];
	float test_values[VST_PARAM_TEST_COUNT];
	float delta = 1.0 / (float)VST_PARAM_TEST_COUNT;
	int test_cnt = 0;
	for(int tst_val = 0; tst_val < VST_PARAM_TEST_COUNT; tst_val++) {
		float v = (float)tst_val / (float)VST_PARAM_TEST_COUNT;
 
		if (tst_val >= VST_PARAM_TEST_COUNT - 1)
			v = 1.0;
 
		fEffect->setParameter(fEffect, index, v);
 
		float new_value = fEffect->getParameter(fEffect, index);
		bool valtest = false;
		for(int i = 0; i < test_cnt; i++) {
			if (fabs(test_values[i] - new_value) < delta) {
				valtest = true;
				break;
			}
		}
		if (valtest == false) {
			test_values[test_cnt] = new_value;
			fEffect->dispatcher(fEffect, VST_GET_PARAM_STR, index,
				0, test_disp[test_cnt], 0);
			test_cnt++;
		}
	}
 
	//restore value
	fEffect->setParameter(fEffect, index, val);
 
	//detect param type
	if (test_cnt == 2) {
		fType = VST_PARAM_CHECKBOX;
 
		DropListValue* min_item = new DropListValue();
		min_item->Value = 0.0;
		min_item->Index = 0;
		min_item->Name = fMinValue;
		fDropList.AddItem(min_item);
 
		DropListValue* max_item = new DropListValue();
		max_item->Value = 1.0;
		max_item->Index = 1;
		max_item->Name = fMaxValue;
		fDropList.AddItem(max_item);
	} else if (test_cnt > 2 && test_cnt < VST_PARAM_TEST_COUNT / 2) {
		fType = VST_PARAM_DROPLIST;
 
		for(int i = 0; i < test_cnt; i++) {
			DropListValue* item = new DropListValue();
			item->Value = test_values[i];
			item->Index = i;
			item->Name = test_disp[i];
			fDropList.AddItem(item);
		}
	} else {
		fType = VST_PARAM_SLIDER;
	}
	fChanged = 0LL;
}
 
VSTParameter::~VSTParameter()
{
}
 
BString*
VSTParameter::ValidateValues(BString* string)
{
	if (string->Length() == 0)
		return string;
 
	bool isNum = true;
 
	const char *ptr = string->String();
	for(; *ptr!=0; ptr++) {
		char ch = *ptr;
		if (!((ch >= '0' && ch <= '9') || ch == '.' || ch == '-')) {
			isNum = false;
			break;
		}
	}
 
	if (isNum) {
		float val = atof(string->String());
 
		if (val <= -pow(2, 31)) {
			string->SetTo("-∞");
		} else if (val >= pow(2, 31)) {
			string->SetTo("∞");
		} else {
			char temp[256];
			sprintf(temp, "%g", val);
			string->SetTo(temp);
		}
	} else {
		TrimString(string);
		if (*string == "oo" || *string == "inf")
			string->SetTo("∞");
		if (*string == "-oo" || *string == "-inf")
			string->SetTo("-∞");
 
	}
	return string;
}
 
int
VSTParameter::ListCount(void)
{
	return fDropList.CountItems();
}
 
DropListValue*
VSTParameter::ListItemAt(int index)
{
	DropListValue* item = NULL;
	if (index >= 0 && index < fDropList.CountItems())
		item = (DropListValue*)fDropList.ItemAt(index);
	return item;
}
 
 
float
VSTParameter::Value()
{
	float value = fEffect->getParameter(fEffect, fIndex);
	if (fType == VST_PARAM_DROPLIST) {
		//scan for near value
		int	min_index = 0;
		float min_delta = 1.0;
		for(int i = 0; i < fDropList.CountItems(); i++) {
			DropListValue* item = (DropListValue*)fDropList.ItemAt(i);
			float delta	= fabs(item->Value - value);
			if (delta <= min_delta) {
				min_delta = delta;
				min_index = i;
			}
		}
		value = min_index;
	}
	fLastValue = value;
	return value;
}
 
void
VSTParameter::SetValue(float value)
{
	if (value == fLastValue)
		return;
 
	if (fType == VST_PARAM_DROPLIST) {
		//take value by index
		int index = (int)vstround(value);
		if (index >= 0 && index < fDropList.CountItems()) {
			DropListValue *item = (DropListValue*)fDropList.ItemAt(index);
			value = item->Value;
			fLastValue = index;
		} else {
			return;
		}
	} else {
		fLastValue = value;
	}
	fChanged = system_time();
	fEffect->setParameter(fEffect, fIndex, value);
}
 
bigtime_t
VSTParameter::LastChangeTime(void)
{
	return fChanged;
}
 
const char*
VSTParameter::MinimumValue(void)
{
	return fMinValue.String();
}
 
const char*
VSTParameter::MaximumValue(void)
{
	return fMaxValue.String();
}
 
const char*
VSTParameter::Unit(void)
{
	return fUnit.String();
}
 
int
VSTParameter::Index(void)
{
	return fIndex;
}
 
int
VSTParameter::Type(void)
{
	return fType;
}
 
const char*
VSTParameter::Name(void)
{
	return fName.String();
}
 
//VST Plugin class
VSTPlugin::VSTPlugin()
{
	fActive = false;
	fEffect = NULL;
	VSTMainProc = NULL;
	fInputChannels = 0;
	fOutputChannels = 0;
	fSampleRate = 44100.f;
	fBlockSize = 0;
	inputs = NULL;
	outputs = NULL;
	fParameters.MakeEmpty();
}
 
VSTPlugin::~VSTPlugin()
{
	fParameters.MakeEmpty();
	UnLoadModule();
}
 
int
VSTPlugin::LoadModule(const char *path)
{
	char effectName[256] = {0};
	char vendorString[256] = {0};
	char productString[256] = {0};
 
	if (fActive)
		return VST_ERR_ALREADY_LOADED;
 
	fPath = BPath(path);
 
	fModule = load_add_on(path);
	if (fModule <= 0)
		return VST_ERR_NOT_LOADED;
 
	if (get_image_symbol(fModule, "main_plugin", B_SYMBOL_TYPE_TEXT,
			(void**)&VSTMainProc) != B_OK) {
		unload_add_on(fModule);
		return VST_ERR_NO_MAINPROC;
	}
 
	fEffect = VSTMainProc(VHostCallback);
	if (fEffect==NULL) {
		unload_add_on(fModule);
		return VST_ERR_NOT_LOADED;
	}
 
	fEffect->dispatcher(fEffect, VST_OPEN, 0, 0, 0, 0);
 
	fEffect->dispatcher(fEffect, VST_GET_EFFECT_NAME, 0, 0, effectName, 0);
	fEffectName.SetTo(effectName);
	TrimString(&fEffectName);
 
	fModuleName.SetTo("VST:");
	fModuleName.Append(fPath.Leaf());
 
	fEffect->dispatcher(fEffect, VST_GET_VENDOR_STR, 0, 0, vendorString, 0);
	fVendorString.SetTo(vendorString);
	TrimString(&fVendorString);
 
	fEffect->dispatcher(fEffect, VST_GET_PRODUCT_STR, 0, 0, productString, 0);
	fProductString.SetTo(productString);
	TrimString(&fProductString);
 
	fInputChannels = fEffect->numInputs;
	fOutputChannels = fEffect->numOutputs;
 
	for(int i=0; i < fEffect->numParams; i++) {
		VSTParameter *param = new VSTParameter(this, i);
		fParameters.AddItem(param);
	}
 
	fEffect->dispatcher(fEffect, VST_STATE_CHANGED, 0, 1, 0, 0);
 
	ReAllocBuffers();
 
	fActive = true;
	return B_OK;
}
 
int
VSTPlugin::UnLoadModule(void)
{
	if (!fActive || fModule <= 0)
		return VST_ERR_NOT_LOADED;
 
	fEffect->dispatcher(fEffect, VST_STATE_CHANGED, 0, 0, 0, 0);
	fEffect->dispatcher(fEffect, VST_CLOSE, 0, 0, 0, 0);
 
	unload_add_on(fModule);
 
	return B_OK;
}
 
int
VSTPlugin::Channels(int mode)
{
	switch(mode) {
		case VST_INPUT_CHANNELS:
			return fInputChannels;
		case VST_OUTPUT_CHANNELS:
			return fOutputChannels;
		default:
			return 0;
	}
}
 
int
VSTPlugin::SetSampleRate(float rate)
{
	fSampleRate = rate;
	fEffect->dispatcher(fEffect, VST_SET_SAMPLE_RATE, 0, 0, 0, rate);
	return B_OK;
}
 
float
VSTPlugin::SampleRate(void)
{
	return fSampleRate;
}
 
int
VSTPlugin::SetBlockSize(size_t size)
{
	fBlockSize = size;
	fEffect->dispatcher(fEffect, VST_SET_BLOCK_SIZE, 0, size, 0, 0);
	ReAllocBuffers();
	return B_OK;
}
 
const char*
VSTPlugin::Path(void)
{
	return fPath.Path();
}
 
int
VSTPlugin::ReAllocBuffers(void)
{
	if (inputs != NULL) {
		for(int32 i = 0; i < fInputChannels; i++)
			delete inputs[i];
	}
 
	if (outputs != NULL) {
		for(int32 i = 0; i < fOutputChannels; i++)
			delete outputs[i];
	}
 
	if (fInputChannels > 0) {
		inputs = new float*[fInputChannels];
		for(int32 i = 0; i < fInputChannels; i++)	{
			inputs[i] = new float[fBlockSize];
			memset(inputs[i], 0, fBlockSize * sizeof(float));
		}
	}
 
	if (fOutputChannels > 0) {
		outputs = new float*[fOutputChannels];
		for(int32_t i = 0; i < fOutputChannels; i++) {
			outputs[i] = new float[fBlockSize];
			memset (outputs[i], 0, fBlockSize * sizeof(float));
		}
	}
	return B_OK;
}
 
size_t
VSTPlugin::BlockSize(void)
{
	return fBlockSize;
}
 
int
VSTPlugin::ParametersCount(void)
{
	return fParameters.CountItems();
}
 
VSTParameter*
VSTPlugin::Parameter(int index)
{
	VSTParameter* param = NULL;
 
	if (index >= 0 && index < fParameters.CountItems())
		param = (VSTParameter*)fParameters.ItemAt(index);
 
	return param;
}
 
VSTEffect*
VSTPlugin::Effect(void)
{
	return fEffect;
}
 
const char*
VSTPlugin::EffectName(void)
{
	return fEffectName.String();
}
 
const char*
VSTPlugin::ModuleName(void)
{
	return fModuleName.String();
}
 
const char*
VSTPlugin::Vendor(void)
{
	return fVendorString.String();
}
 
const char*
VSTPlugin::Product(void)
{
	return fProductString.String();
}
 
 
void
VSTPlugin::Process(float *buffer, int samples, int channels)
{
	//todo: full channels remapping needed
	float* src = buffer;
 
	if (channels == fInputChannels) { //channel to channel
		for(int j = 0; j < samples; j++) {
			for(int c = 0; c < fInputChannels; c++)
				inputs[c][j] = *src++;
		}
	} else if ( channels == 1) {	//from mone to multichannel
		for(int j = 0; j < samples; j++, src++) {
			for(int c = 0; c < fInputChannels; c++)
				inputs[c][j] = *src;
		}
	}
 
	fEffect->processReplacing(fEffect, inputs, outputs, fBlockSize);
 
	float* dst = buffer;
 
	if (channels == fOutputChannels) { //channel to channel
		for(int j = 0; j < samples; j++) {
			for(int c = 0; c < fOutputChannels; c++)
				*dst++ = outputs[c][j];
		}
	} else if (channels == 1) {  //from multichannel to mono
		for(int j = 0; j < samples; j++, dst++) {
			float mix = 0;
			for(int c = 0; c < fOutputChannels; c++)
				mix += outputs[c][j];
			*dst = mix / (float)fOutputChannels;
		}
	}
}
 
static int32
VHostCallback(VSTEffect* effect, int32 opcode, int32 index, int32 value,
	void* ptr, float opt)
{
	intptr_t result = 0;
 
	switch(opcode)
	{
		case VST_MASTER_PRODUCT:
			if (ptr) {
				strcpy((char*)ptr, "VSTHost Media AddOn");
				result = 1;
			}
			break;
		case VST_MASTER_VERSION :
			result = 2300;
			break;
	}
 
	return result;
}

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fModule.