#include "AUPlayer.h"

#include <stdlib.h>

#define SF2PACK

// #define AUPLAYERVIEW

#ifdef AUPLAYERVIEW
#import "AUPlayerView.h"
#endif

#define _countof(arr) (sizeof(arr) / sizeof((arr)[0]))

#define BLOCK_SIZE (512)

AUPlayer::AUPlayer()
: MIDIPlayer() {
	samplerUnit[0] = NULL;
	samplerUnit[1] = NULL;
	samplerUnit[2] = NULL;
#ifdef AUPLAYERVIEW
	samplerUI[0] = NULL;
	samplerUI[1] = NULL;
	samplerUI[2] = NULL;
	samplerUIinitialized[0] = false;
	samplerUIinitialized[1] = false;
	samplerUIinitialized[2] = false;
#endif
	bufferList = NULL;
	audioBuffer = NULL;

	componentSubType = kAudioUnitSubType_DLSSynth;
	componentManufacturer = kAudioUnitManufacturer_Apple;
}

AUPlayer::~AUPlayer() {
	shutdown();
}

void AUPlayer::send_event(uint32_t b) {
	send_event_time(b, 0);
}

void AUPlayer::send_sysex(const uint8_t *data, size_t size, size_t port) {
	send_sysex_time(data, size, port, 0);
}

void AUPlayer::send_event_time(uint32_t b, unsigned int time) {
#ifdef AUPLAYERVIEW
	int _port = -1;
#endif
	unsigned char event[3];
	event[0] = (unsigned char)b;
	event[1] = (unsigned char)(b >> 8);
	event[2] = (unsigned char)(b >> 16);
	unsigned port = (b >> 24) & 0x7F;
	if(port > 2) port = 2;
#ifdef AUPLAYERVIEW
	_port = (int)port;
#endif
	MusicDeviceMIDIEvent(samplerUnit[port], event[0], event[1], event[2], time);
#ifdef AUPLAYERVIEW
	if(_port >= 0 && !samplerUIinitialized[_port]) {
		samplerUIinitialized[_port] = true;
		dispatch_async(dispatch_get_main_queue(), ^{
			samplerUI[_port] = new AUPluginUI(samplerUnit[_port]);
		});
	}
#endif
}

void AUPlayer::send_sysex_time(const uint8_t *data, size_t size, size_t port, unsigned int time) {
	if(port > 2) port = 0;
#ifdef AUPLAYERVIEW
	_port = (int)port;
#endif
	MusicDeviceSysEx(samplerUnit[port], data, (UInt32)size);
	if(port == 0) {
		MusicDeviceSysEx(samplerUnit[1], data, (UInt32)size);
		MusicDeviceSysEx(samplerUnit[2], data, (UInt32)size);
	}
#ifdef AUPLAYERVIEW
	if(_port >= 0 && !samplerUIinitialized[_port]) {
		samplerUIinitialized[_port] = true;
		dispatch_async(dispatch_get_main_queue(), ^{
			samplerUI[_port] = new AUPluginUI(samplerUnit[_port]);
		});
	}
#endif
}

void AUPlayer::render(float *out, unsigned long count) {
	float *ptrL, *ptrR;
	memset(out, 0, count * sizeof(float) * 2);
	while(count) {
		UInt32 numberFrames = count > BLOCK_SIZE ? BLOCK_SIZE : (UInt32)count;

		for(unsigned long i = 0; i < 3; ++i) {
			AudioUnitRenderActionFlags ioActionFlags = 0;

			for(unsigned long j = 0; j < 2; j++) {
				bufferList->mBuffers[j].mNumberChannels = 1;
				bufferList->mBuffers[j].mDataByteSize = (UInt32)(numberFrames * sizeof(float));
				bufferList->mBuffers[j].mData = audioBuffer + j * BLOCK_SIZE;
				memset(bufferList->mBuffers[j].mData, 0, numberFrames * sizeof(float));
			}

			AudioUnitRender(samplerUnit[i], &ioActionFlags, &mTimeStamp, 0, numberFrames, bufferList);

			ptrL = (float *)bufferList->mBuffers[0].mData;
			ptrR = (float *)bufferList->mBuffers[1].mData;
			for(unsigned long j = 0; j < numberFrames; ++j) {
				out[j * 2 + 0] += ptrL[j];
				out[j * 2 + 1] += ptrR[j];
			}
		}

		out += numberFrames * 2;
		count -= numberFrames;

		mTimeStamp.mSampleTime += (double)numberFrames;
	}
}

void AUPlayer::shutdown() {
	if(samplerUnit[2]) {
#ifdef AUPLAYERVIEW
		if(samplerUI[2]) {
			delete samplerUI[2];
			samplerUI[2] = 0;
			samplerUIinitialized[2] = false;
		}
#endif
		AudioUnitUninitialize(samplerUnit[2]);
		AudioComponentInstanceDispose(samplerUnit[2]);
		samplerUnit[2] = NULL;
	}
	if(samplerUnit[1]) {
#ifdef AUPLAYERVIEW
		if(samplerUI[1]) {
			delete samplerUI[1];
			samplerUI[1] = 0;
			samplerUIinitialized[1] = false;
		}
#endif
		AudioUnitUninitialize(samplerUnit[1]);
		AudioComponentInstanceDispose(samplerUnit[1]);
		samplerUnit[1] = NULL;
	}
	if(samplerUnit[0]) {
#ifdef AUPLAYERVIEW
		if(samplerUI[0]) {
			delete samplerUI[0];
			samplerUI[0] = 0;
			samplerUIinitialized[0] = false;
		}
#endif
		AudioUnitUninitialize(samplerUnit[0]);
		AudioComponentInstanceDispose(samplerUnit[0]);
		samplerUnit[0] = NULL;
	}
	if(audioBuffer) {
		free(audioBuffer);
		audioBuffer = NULL;
	}
	if(bufferList) {
		free(bufferList);
		bufferList = NULL;
	}
	initialized = false;
}

void AUPlayer::enumComponents(callback cbEnum) {
	AudioComponentDescription cd = { 0 };
	cd.componentType = kAudioUnitType_MusicDevice;

	AudioComponent comp = NULL;

	const char *bytes;
	char bytesBuffer[512];

	comp = AudioComponentFindNext(comp, &cd);

	while(comp != NULL) {
		CFStringRef cfName;
		AudioComponentCopyName(comp, &cfName);
		bytes = CFStringGetCStringPtr(cfName, kCFStringEncodingUTF8);
		if(!bytes) {
			CFStringGetCString(cfName, bytesBuffer, sizeof(bytesBuffer) - 1, kCFStringEncodingUTF8);
			bytes = bytesBuffer;
		}
		AudioComponentGetDescription(comp, &cd);
		cbEnum(cd.componentSubType, cd.componentManufacturer, bytes);
		CFRelease(cfName);
		comp = AudioComponentFindNext(comp, &cd);
	}
}

void AUPlayer::setComponent(OSType uSubType, OSType uManufacturer) {
	componentSubType = uSubType;
	componentManufacturer = uManufacturer;
	shutdown();
}

void AUPlayer::setSoundFont(const char *in) {
	const char *ext = strrchr(in, '.');
	if(*ext && ((strncasecmp(ext + 1, "sf2", 3) == 0) || (strncasecmp(ext + 1, "dls", 3) == 0))) {
		sSoundFontName = in;
		shutdown();
	}
}

/*void AUPlayer::setFileSoundFont( const char * in )
{
    sFileSoundFontName = in;
    shutdown();
}*/

static OSStatus renderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) {
	if(inNumberFrames && ioData) {
		for(int i = 0, j = ioData->mNumberBuffers; i < j; ++i) {
			int k = inNumberFrames * sizeof(float);
			if(k > ioData->mBuffers[i].mDataByteSize)
				k = ioData->mBuffers[i].mDataByteSize;
			memset(ioData->mBuffers[i].mData, 0, k);
		}
	}

	return noErr;
}

bool AUPlayer::startup() {
	if(bufferList) return true;

	AudioComponentDescription cd = { 0 };
	cd.componentType = kAudioUnitType_MusicDevice;
	cd.componentSubType = componentSubType;
	cd.componentManufacturer = componentManufacturer;

	AudioComponent comp = NULL;

	comp = AudioComponentFindNext(comp, &cd);

	if(!comp)
		return false;

	OSStatus error;

	for(int i = 0; i < 3; i++) {
		UInt32 value = 1;
		UInt32 size = sizeof(value);

		error = AudioComponentInstanceNew(comp, &samplerUnit[i]);

		if(error != noErr)
			return false;

		{
			AudioStreamBasicDescription stream = { 0 };
			stream.mSampleRate = uSampleRate;
			stream.mFormatID = kAudioFormatLinearPCM;
			stream.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved | kAudioFormatFlagsNativeEndian;
			stream.mFramesPerPacket = 1;
			stream.mBytesPerPacket = 4;
			stream.mBytesPerFrame = 4;
			stream.mBitsPerChannel = 32;
			stream.mChannelsPerFrame = 2;

			AudioUnitSetProperty(samplerUnit[i], kAudioUnitProperty_StreamFormat,
			                     kAudioUnitScope_Input, 0, &stream, sizeof(stream));

			AudioUnitSetProperty(samplerUnit[i], kAudioUnitProperty_StreamFormat,
			                     kAudioUnitScope_Output, 0, &stream, sizeof(stream));
		}

		value = BLOCK_SIZE;
		AudioUnitSetProperty(samplerUnit[i], kAudioUnitProperty_MaximumFramesPerSlice,
		                     kAudioUnitScope_Global, 0, &value, size);

		value = 127;
		AudioUnitSetProperty(samplerUnit[i], kAudioUnitProperty_RenderQuality,
		                     kAudioUnitScope_Global, 0, &value, size);

		AURenderCallbackStruct callbackStruct;
		callbackStruct.inputProc = renderCallback;
		callbackStruct.inputProcRefCon = 0;
		AudioUnitSetProperty(samplerUnit[i], kAudioUnitProperty_SetRenderCallback,
		                     kAudioUnitScope_Input, 0, &callbackStruct, sizeof(callbackStruct));

		/*Float64 sampleRateIn = 0, sampleRateOut = 0;
		UInt32 sampleRateSize = sizeof (sampleRateIn);
		const Float64 sr = uSampleRate;

		AudioUnitGetProperty(samplerUnit[i], kAudioUnitProperty_SampleRate, kAudioUnitScope_Input, 0, &sampleRateIn, &sampleRateSize);

		if (sampleRateIn != sr)
		    AudioUnitSetProperty(samplerUnit[i], kAudioUnitProperty_SampleRate, kAudioUnitScope_Input, 0, &sr, sizeof (sr));

		AudioUnitGetProperty (samplerUnit[i], kAudioUnitProperty_SampleRate, kAudioUnitScope_Output, 0, &sampleRateOut, &sampleRateSize);

		if (sampleRateOut != sr)
		    AudioUnitSetProperty (samplerUnit[i], kAudioUnitProperty_SampleRate, kAudioUnitScope_Output, i, &sr, sizeof (sr));*/

		AudioUnitReset(samplerUnit[i], kAudioUnitScope_Input, 0);
		AudioUnitReset(samplerUnit[i], kAudioUnitScope_Output, 0);

		AudioUnitReset(samplerUnit[i], kAudioUnitScope_Global, 0);

		value = 1;
		AudioUnitSetProperty(samplerUnit[i], kMusicDeviceProperty_StreamFromDisk, kAudioUnitScope_Global, 0, &value, size);

		error = AudioUnitInitialize(samplerUnit[i]);

		if(error != noErr)
			return false;
	}

	// Now load instruments
	if(sSoundFontName.length()) {
		loadSoundFont(sSoundFontName.c_str());
	}

	/*if ( sFileSoundFontName.length() )
	{
	    loadSoundFont( sFileSoundFontName.c_str() );
	}*/

	bufferList = (AudioBufferList *)calloc(1, sizeof(AudioBufferList) + sizeof(AudioBuffer));
	if(!bufferList)
		return false;

	audioBuffer = (float *)malloc(BLOCK_SIZE * 2 * sizeof(float));
	if(!audioBuffer)
		return false;

	bufferList->mNumberBuffers = 2;

	memset(&mTimeStamp, 0, sizeof(mTimeStamp));
	mTimeStamp.mFlags = kAudioTimeStampSampleTimeValid;

	initialized = true;

	setFilterMode(mode, reverb_chorus_disabled);

	return true;
}

void AUPlayer::loadSoundFont(const char *name) {
	// kMusicDeviceProperty_SoundBankURL was added in 10.5 as a replacement
	// In addition, the File Manager API became deprecated starting in 10.8
	CFURLRef url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)name, strlen(name), false);

	if(url) {
		for(int i = 0; i < 3; i++)
			AudioUnitSetProperty(samplerUnit[i],
			                     kMusicDeviceProperty_SoundBankURL, kAudioUnitScope_Global,
			                     0,
			                     &url, sizeof(url));

		CFRelease(url);
	}
}

unsigned int AUPlayer::send_event_needs_time() {
	return BLOCK_SIZE;
}
