#include <stddef.h>
#include <string.h>
#include <stdio.h>
#include <math.h>

#include "AudioEffect.hpp"
#include "AEffEditor.hpp"

//-----------------------------------------------------------------------------
long dispatchEffectClass (AEffect *e, long opCode, 
	long index, long value, void *ptr, float opt)
{
	AudioEffect *ae = (AudioEffect *)(e->object);

	if(opCode == effClose)
	{
		ae->dispatcher(opCode, index, value, ptr, opt);
		delete ae;
		return 1;
	}

	return ae->dispatcher(opCode, index, value, ptr, opt);
}

//-----------------------------------------------------------------------------
float getParameterClass(AEffect *e, long index)
{
	AudioEffect *ae = (AudioEffect *)(e->object);
	return ae->getParameter(index);
}

//-----------------------------------------------------------------------------
void setParameterClass(AEffect *e, long index, float value)
{
	AudioEffect *ae = (AudioEffect *)(e->object);
	ae->setParameter(index, value);
}

//-----------------------------------------------------------------------------
void processClass(AEffect *e, float **inputs, float **outputs, long sampleFrames)
{
	AudioEffect *ae = (AudioEffect *)(e->object);
	ae->process(inputs, outputs, sampleFrames);
}

//-----------------------------------------------------------------------------
void processClassReplacing(AEffect *e, float **inputs, float **outputs, long sampleFrames)
{
	AudioEffect *ae = (AudioEffect *)(e->object);
	ae->processReplacing(inputs, outputs, sampleFrames);
}

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

AudioEffect::AudioEffect(audioMasterCallback audioMaster, long numPrograms, long numParams)
{
	this->audioMaster = audioMaster;
	editor = 0;
	this->numPrograms = numPrograms;
	this->numParams = numParams;
	curProgram = 0;

	memset(&cEffect, 0, sizeof(cEffect));
	cEffect.magic = kEffectMagic;
	cEffect.dispatcher = dispatchEffectClass;
	cEffect.process = processClass;
	cEffect.setParameter = setParameterClass;
	cEffect.getParameter = getParameterClass;
	cEffect.numPrograms = numPrograms;
	cEffect.numParams = numParams;
	cEffect.numInputs = 1;
	cEffect.numOutputs = 2;
	cEffect.flags = 0;
	cEffect.resvd1 = 0;
	cEffect.resvd2 = 0;
	cEffect.initialDelay = 0;
	cEffect.realQualities = 0;
	cEffect.offQualities = 0;
	cEffect.ioRatio = 1.f;
	cEffect.object = this;
	cEffect.user = 0;
	cEffect.uniqueID = 'NoEf';		// you must set this!
	cEffect.version = 1;
	cEffect.processReplacing = processClassReplacing;

	sampleRate = 44100.f;
	blockSize = 1024L;
}

//-----------------------------------------------------------------------------
AudioEffect::~AudioEffect()
{
	if(editor)
		delete editor;
}

//-----------------------------------------------------------------------------
long AudioEffect::dispatcher(long opCode, long index, long value, void *ptr, float opt)
{
	long v = 0;

	switch(opCode)
	{
		case effOpen:				open();												break;
		case effClose:				close();											break;
		case effSetProgram:			if(value < numPrograms)	setProgram(value);			break;
		case effGetProgram:			v = getProgram();									break;
		case effSetProgramName: 	setProgramName((char *)ptr);						break;
		case effGetProgramName: 	getProgramName((char *)ptr);						break;
		case effGetParamLabel:		getParameterLabel(index, (char *)ptr);				break;
		case effGetParamDisplay:	getParameterDisplay(index, (char *)ptr);			break;
		case effGetParamName:		getParameterName(index, (char *)ptr);				break;

		case effSetSampleRate:		setSampleRate(opt);									break;
		case effSetBlockSize:		setBlockSize(value);								break;
		case effMainsChanged:		if(!value) suspend(); else resume();				break;
		case effGetVu:				v = (long)(getVu() * 32767.);						break;

	// editor
	
		case effEditGetRect:		if(editor) v = editor->getRect((ERect **)ptr);		break;
		case effEditOpen:			if(editor) v = editor->open(ptr);					break;
		case effEditClose:			if(editor) editor->close();							break;		
		case effEditIdle:			if(editor) editor->idle();							break;
		
		#if MAC
		case effEditDraw:			if(editor) editor->draw((ERect *)ptr);				break;
		case effEditMouse:			if(editor) v = editor->mouse(index, value);			break;
		case effEditKey:			if(editor) v = editor->key(value);					break;
		case effEditTop:			if(editor) editor->top();							break;
		case effEditSleep:			if(editor) editor->sleep();							break;
		#endif
		
	// new

		case effIdentify:			v = 'NvEf';											break;
		case effGetChunk:			v = getChunk((void**)ptr, index ? true : false); break;
		case effSetChunk:			v = setChunk(ptr, value, index ? true : false); break;
	}
	return v;
}

//-----------------------------------------------------------------------------
long AudioEffect::getMasterVersion()
{
	long version = 1;
	if(audioMaster)
	{
		version = audioMaster(&cEffect, audioMasterVersion, 0, 0, 0, 0);
		if(!version)	// old
			version = 1;
	}
	return version;
}

//-----------------------------------------------------------------------------
long AudioEffect::getCurrentUniqueId()
{
	long id = 0;
	if(audioMaster)
		id = audioMaster(&cEffect, audioMasterCurrentId, 0, 0, 0, 0);
	return id;
}

//-----------------------------------------------------------------------------
void AudioEffect::masterIdle()
{
	if(audioMaster)
		audioMaster(&cEffect, audioMasterIdle, 0, 0, 0, 0);
}

//-----------------------------------------------------------------------------
bool AudioEffect::isInputConnected(long input)
{
	long ret = 0;
	if(audioMaster)
		ret = audioMaster(&cEffect, audioMasterPinConnected, input, 0, 0, 0);
	return ret ? false : true;		// return value is 0 for true
}

//-----------------------------------------------------------------------------
bool AudioEffect::isOutputConnected(long output)
{
	long ret = 0;
	if(audioMaster)
		ret = audioMaster(&cEffect, audioMasterPinConnected, output, 1, 0, 0);
	return ret ? false : true;		// return value is 0 for true
}

//-----------------------------------------------------------------------------
// flags

//-----------------------------------------------------------------------------
void AudioEffect::hasVu(bool state)
{
	if(state)
		cEffect.flags |= effFlagsHasVu;
	else
		cEffect.flags &= ~effFlagsHasVu;
}

//-----------------------------------------------------------------------------
void AudioEffect::hasClip(bool state)
{
	if(state)
		cEffect.flags |= effFlagsHasClip;
	else
		cEffect.flags &= ~effFlagsHasClip;
}

//-----------------------------------------------------------------------------
void AudioEffect::canMono(bool state)
{
	if(state)
		cEffect.flags |= effFlagsCanMono;
	else
		cEffect.flags &= ~effFlagsCanMono;
}

//-----------------------------------------------------------------------------
void AudioEffect::canProcessReplacing(bool state)
{
	if(state)
		cEffect.flags |= effFlagsCanReplacing;
	else
		cEffect.flags &= ~effFlagsCanReplacing;
}

//-----------------------------------------------------------------------------
void AudioEffect::programsAreChunks(bool state)
{
	if(state)
		cEffect.flags |= effFlagsProgramChunks;
	else
		cEffect.flags &= ~effFlagsProgramChunks;
}

//-----------------------------------------------------------------------------
void AudioEffect::setRealtimeQualities(long qualities)
{
	cEffect.realQualities = qualities;
}

//-----------------------------------------------------------------------------
void AudioEffect::setOfflineQualities(long qualities)
{
	cEffect.offQualities = qualities;
}

//-----------------------------------------------------------------------------
void AudioEffect::setInitialDelay(long delay)
{
	cEffect.initialDelay = delay;
}

//-----------------------------------------------------------------------------
// string

//-----------------------------------------------------------------------------
void AudioEffect::dB2string(float value, char *text)
{
	if(value <= 0)
#if MAC
		strcpy(text, "   -°   ");
#else
		strcpy(text, "  -oo   ");
#endif
	else
		float2string((float)(20. * log10(value)), text);
}

//-----------------------------------------------------------------------------
void AudioEffect::Hz2string(float samples, char *text)
{
	float sampleRate = getSampleRate();
	if(!samples)
		float2string(0, text);
	else
		float2string(sampleRate / samples, text);
}

//-----------------------------------------------------------------------------
void AudioEffect::ms2string(float samples, char *text)
{
	float2string((float)(samples * 1000. / getSampleRate()), text);
}

//-----------------------------------------------------------------------------
void AudioEffect::float2string(float value, char *text)
{
	long c = 0, neg = 0;
	char string[32];
	char *s;
	double v, integ, i10, mantissa, m10, ten = 10.;
	
	v = (double)value;
	if(v < 0)
	{
		neg = 1;
		value = -value;
		v = -v;
		c++;
		if(v > 9999999.)
		{
			strcpy(string, " Huge!  ");
			return;
		}
	}
	else if(v > 99999999.)
	{
		strcpy(string, " Huge!  ");
		return;
	}

	s = string + 31;
	*s-- = 0;
	*s-- = '.';
	c++;
	
	integ = floor(v);
	i10 = fmod(integ, ten);
	*s-- = (long)i10 + '0';
	integ /= ten;
	c++;
	while(integ >= 1. && c < 8)
	{
		i10 = fmod(integ, ten);
		*s-- = (long)i10 + '0';
		integ /= ten;
		c++;
	}
	if(neg)
		*s-- = '-';
	strcpy(text, s + 1);
	if(c >= 8)
		return;

	s = string + 31;
	*s-- = 0;
	mantissa = fmod(v, 1.);
	mantissa *= pow(ten, (double)(8 - c));
	while(c < 8)
	{
		if(mantissa <= 0)
			*s-- = '0';
		else
		{
			m10 = fmod(mantissa, ten);
			*s-- = (long)m10 + '0';
			mantissa /= 10.;
		}
		c++;
	}
	strcat(text, s + 1);
}

//-----------------------------------------------------------------------------
void AudioEffect::long2string(long value, char *text)
{
	char string[32];
	
	if(value >= 100000000)
	{
		strcpy(text, " Huge!  ");
		return;
	}
	sprintf(string, "%7d", value);
	string[8] = 0;
	strcpy(text, (char *)string);
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void AudioEffect::setParameterAutomated(long index, float value)
{
	setParameter(index, value);
	if(audioMaster)
		audioMaster(&cEffect, audioMasterAutomate, index, 0, 0, value);		// value is in opt
}

