/*
    native_midi_mac:  Native Midi support on MacOS for the SDL_mixer library
    Copyright (C) 2001  Max Horn

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

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    Max Horn
    max@quendi.de
*/
#include "SDL_config.h"
#include "SDL_endian.h"

#if __MACOS__ /*|| __MACOSX__ */

#include "native_midi.h"
#include "native_midi_common.h"

#if __MACOSX__
#include <QuickTime/QuickTimeMusic.h>
#else
#include <QuickTimeMusic.h>
#endif

#include <assert.h>
#include <stdlib.h>
#include <string.h>


/* Native Midi song */
struct _NativeMidiSong
{
	Uint32		*tuneSequence;
	Uint32		*tuneHeader;
};

enum
{
	/* number of (32-bit) long words in a note request event */
	kNoteRequestEventLength = ((sizeof(NoteRequest)/sizeof(long)) + 2),

	/* number of (32-bit) long words in a marker event */
	kMarkerEventLength	= 1,

	/* number of (32-bit) long words in a general event, minus its data */
	kGeneralEventLength	= 2
};

#define ERROR_BUF_SIZE			256
#define	BUFFER_INCREMENT		5000

#define REST_IF_NECESSARY()	do {\
			int timeDiff = eventPos->time - lastEventTime;	\
			if(timeDiff)	\
			{	\
				timeDiff = (int)(timeDiff*tick);	\
				qtma_StuffRestEvent(*tunePos, timeDiff);	\
				tunePos++;	\
				lastEventTime = eventPos->time;	\
			}	\
		} while(0)


static Uint32 *BuildTuneSequence(MIDIEvent *evntlist, int ppqn, int part_poly_max[32], int part_to_inst[32], int *numParts);
static Uint32 *BuildTuneHeader(int part_poly_max[32], int part_to_inst[32], int numParts);

/* The global TunePlayer instance */
static TunePlayer	gTunePlayer = NULL;
static int			gInstaceCount = 0;
static Uint32		*gCurrentTuneSequence = NULL;
static char			gErrorBuffer[ERROR_BUF_SIZE] = "";


/* Check whether QuickTime is available */
int native_midi_detect()
{
	/* TODO */
	return 1;
}

NativeMidiSong *native_midi_loadsong(const char *midifile)
{
	NativeMidiSong	*song = NULL;
	MIDIEvent		*evntlist = NULL;
	int				part_to_inst[32];
	int				part_poly_max[32];
	int				numParts = 0;
	Uint16			ppqn;
	SDL_RWops		*rw;

	/* Init the arrays */
	memset(part_poly_max,0,sizeof(part_poly_max));
	memset(part_to_inst,-1,sizeof(part_to_inst));
	
	/* Attempt to load the midi file */
	rw = SDL_RWFromFile(midifile, "rb");
	if (rw) {
		evntlist = CreateMIDIEventList(rw, &ppqn);
		SDL_RWclose(rw);
		if (!evntlist)
			goto bail;
	}

	/* Allocate memory for the song struct */
	song = malloc(sizeof(NativeMidiSong));
	if (!song)
		goto bail;

	/* Build a tune sequence from the event list */
	song->tuneSequence = BuildTuneSequence(evntlist, ppqn, part_poly_max, part_to_inst, &numParts);
	if(!song->tuneSequence)
		goto bail;

	/* Now build a tune header from the data we collect above, create
	   all parts as needed and assign them the correct instrument.
	*/
	song->tuneHeader = BuildTuneHeader(part_poly_max, part_to_inst, numParts);
	if(!song->tuneHeader)
		goto bail;
	
	/* Increment the instance count */
	gInstaceCount++;
	if (gTunePlayer == NULL)
		gTunePlayer = OpenDefaultComponent(kTunePlayerComponentType, 0);

	/* Finally, free the event list */
	FreeMIDIEventList(evntlist);
	
	return song;
	
bail:
	if (evntlist)
		FreeMIDIEventList(evntlist);
	
	if (song)
	{
		if(song->tuneSequence)
			free(song->tuneSequence);
		
		if(song->tuneHeader)
			DisposePtr((Ptr)song->tuneHeader);

		free(song);
	}
	
	return NULL;
}

NativeMidiSong *native_midi_loadsong_RW(SDL_RWops *rw)
{
	NativeMidiSong	*song = NULL;
	MIDIEvent		*evntlist = NULL;
	int				part_to_inst[32];
	int				part_poly_max[32];
	int				numParts = 0;
	Uint16			ppqn;

	/* Init the arrays */
	memset(part_poly_max,0,sizeof(part_poly_max));
	memset(part_to_inst,-1,sizeof(part_to_inst));
	
	/* Attempt to load the midi file */
	evntlist = CreateMIDIEventList(rw, &ppqn);
	if (!evntlist)
		goto bail;

	/* Allocate memory for the song struct */
	song = malloc(sizeof(NativeMidiSong));
	if (!song)
		goto bail;

	/* Build a tune sequence from the event list */
	song->tuneSequence = BuildTuneSequence(evntlist, ppqn, part_poly_max, part_to_inst, &numParts);
	if(!song->tuneSequence)
		goto bail;

	/* Now build a tune header from the data we collect above, create
	   all parts as needed and assign them the correct instrument.
	*/
	song->tuneHeader = BuildTuneHeader(part_poly_max, part_to_inst, numParts);
	if(!song->tuneHeader)
		goto bail;
	
	/* Increment the instance count */
	gInstaceCount++;
	if (gTunePlayer == NULL)
		gTunePlayer = OpenDefaultComponent(kTunePlayerComponentType, 0);

	/* Finally, free the event list */
	FreeMIDIEventList(evntlist);
	
	return song;
	
bail:
	if (evntlist)
		FreeMIDIEventList(evntlist);
	
	if (song)
	{
		if(song->tuneSequence)
			free(song->tuneSequence);
		
		if(song->tuneHeader)
			DisposePtr((Ptr)song->tuneHeader);

		free(song);
	}
	
	return NULL;
}

void native_midi_freesong(NativeMidiSong *song)
{
	if(!song || !song->tuneSequence)
		return;

	/* If this is the currently playing song, stop it now */	
	if (song->tuneSequence == gCurrentTuneSequence)
		native_midi_stop();
	
	/* Finally, free the data storage */
	free(song->tuneSequence);
	DisposePtr((Ptr)song->tuneHeader);
	free(song);

	/* Increment the instance count */
	gInstaceCount--;
	if ((gTunePlayer != NULL) && (gInstaceCount == 0))
	{
		CloseComponent(gTunePlayer);
		gTunePlayer = NULL;
	}
}

void native_midi_start(NativeMidiSong *song)
{
	UInt32		queueFlags = 0;
	ComponentResult tpError;
	
	assert (gTunePlayer != NULL);
	
	SDL_PauseAudio(1);
	SDL_UnlockAudio();
    
	/* First, stop the currently playing music */
	native_midi_stop();
	
	/* Set up the queue flags */
	queueFlags = kTuneStartNow;

	/* Set the time scale (units per second), we want milliseconds */
	tpError = TuneSetTimeScale(gTunePlayer, 1000);
	if (tpError != noErr)
	{
		strncpy (gErrorBuffer, "MIDI error during TuneSetTimeScale", ERROR_BUF_SIZE);
		goto done;
	}

	/* Set the header, to tell what instruments are used */
	tpError = TuneSetHeader(gTunePlayer, (UInt32 *)song->tuneHeader);
	if (tpError != noErr)
	{
		strncpy (gErrorBuffer, "MIDI error during TuneSetHeader", ERROR_BUF_SIZE);
		goto done;
	}
	
	/* Have it allocate whatever resources are needed */
	tpError = TunePreroll(gTunePlayer);
	if (tpError != noErr)
	{
		strncpy (gErrorBuffer, "MIDI error during TunePreroll", ERROR_BUF_SIZE);
		goto done;
	}

	/* We want to play at normal volume */
	tpError = TuneSetVolume(gTunePlayer, 0x00010000);
	if (tpError != noErr)
	{
		strncpy (gErrorBuffer, "MIDI error during TuneSetVolume", ERROR_BUF_SIZE);
		goto done;
	}
	
	/* Finally, start playing the full song */
	gCurrentTuneSequence = song->tuneSequence;
	tpError = TuneQueue(gTunePlayer, (UInt32 *)song->tuneSequence, 0x00010000, 0, 0xFFFFFFFF, queueFlags, NULL, 0);
	if (tpError != noErr)
	{
		strncpy (gErrorBuffer, "MIDI error during TuneQueue", ERROR_BUF_SIZE);
		goto done;
	}
    
done:
	SDL_LockAudio();
	SDL_PauseAudio(0);
}

void native_midi_stop()
{
	if (gTunePlayer == NULL)
		return;

	/* Stop music */
	TuneStop(gTunePlayer, 0);
	
	/* Deallocate all instruments */
	TuneUnroll(gTunePlayer);
}

int native_midi_active()
{
	if (gTunePlayer != NULL)
	{
		TuneStatus	ts;

		TuneGetStatus(gTunePlayer,&ts);
		return ts.queueTime != 0;
	}
	else
		return 0;
}

void native_midi_setvolume(int volume)
{
	if (gTunePlayer == NULL)
		return;

	/* QTMA olume may range from 0.0 to 1.0 (in 16.16 fixed point encoding) */
	TuneSetVolume(gTunePlayer, (0x00010000 * volume)/SDL_MIX_MAXVOLUME);
}

const char *native_midi_error(void)
{
	return gErrorBuffer;
}

Uint32 *BuildTuneSequence(MIDIEvent *evntlist, int ppqn, int part_poly_max[32], int part_to_inst[32], int *numParts)
{
	int			part_poly[32];
	int			channel_to_part[16];
	
	int			channel_pan[16];
	int			channel_vol[16];
	int			channel_pitch_bend[16];
	
	int			lastEventTime = 0;
	int			tempo = 500000;
	double		Ippqn = 1.0 / (1000*ppqn);
	double		tick = tempo * Ippqn;
	MIDIEvent	*eventPos = evntlist;
	MIDIEvent	*noteOffPos;
	Uint32 		*tunePos, *endPos;
	Uint32		*tuneSequence;
	size_t		tuneSize;
	
	/* allocate space for the tune header */
	tuneSize = 5000;
	tuneSequence = (Uint32 *)malloc(tuneSize * sizeof(Uint32));
	if (tuneSequence == NULL)
		return NULL;
	
	/* Set starting position in our tune memory */
	tunePos = tuneSequence;
	endPos = tuneSequence + tuneSize;

	/* Initialise the arrays */
	memset(part_poly,0,sizeof(part_poly));
	
	memset(channel_to_part,-1,sizeof(channel_to_part));
	memset(channel_pan,-1,sizeof(channel_pan));
	memset(channel_vol,-1,sizeof(channel_vol));
	memset(channel_pitch_bend,-1,sizeof(channel_pitch_bend));
	
	*numParts = 0;
	
	/*
	 * Now the major work - iterate over all GM events,
	 * and turn them into QuickTime Music format.
	 * At the same time, calculate the max. polyphony for each part,
	 * and also the part->instrument mapping.
	 */
	while(eventPos)
	{
		int status = (eventPos->status&0xF0)>>4;
		int channel = eventPos->status&0x0F;
		int part = channel_to_part[channel];
        int velocity, pitch;
        int value, controller;
        int bend;
        int newInst;
		
		/* Check if we are running low on space... */
		if((tunePos+16) > endPos)
		{
			/* Resize our data storage. */
			Uint32 		*oldTuneSequence = tuneSequence;

			tuneSize += BUFFER_INCREMENT;
			tuneSequence = (Uint32 *)realloc(tuneSequence, tuneSize * sizeof(Uint32));
			if(oldTuneSequence != tuneSequence)
				tunePos += tuneSequence - oldTuneSequence;
			endPos = tuneSequence + tuneSize;
		}
		
		switch (status)
		{
		case MIDI_STATUS_NOTE_OFF:
			assert(part>=0 && part<=31);

			/* Keep track of the polyphony of the current part */
			part_poly[part]--;
			break;
		case MIDI_STATUS_NOTE_ON:
			if (part < 0)
			{
				/* If no part is specified yet, we default to the first instrument, which
				   is piano (or the first drum kit if we are on the drum channel)
				*/
				int newInst;
				
				if (channel == 9)
					newInst = kFirstDrumkit + 1;		/* the first drum kit is the "no drum" kit! */
				else
					newInst = kFirstGMInstrument;
				part = channel_to_part[channel] = *numParts;
				part_to_inst[(*numParts)++] = newInst;
			}
			/* TODO - add support for more than 32 parts using eXtended QTMA events */
			assert(part<=31);
			
			/* Decode pitch & velocity */
			pitch = eventPos->data[0];
			velocity = eventPos->data[1];
			
			if (velocity == 0)
			{
				/* was a NOTE OFF in disguise, so we decrement the polyphony */
				part_poly[part]--;
			}
			else
			{
				/* Keep track of the polyphony of the current part */
				int foo = ++part_poly[part];
				if (part_poly_max[part] < foo)
					part_poly_max[part] = foo;

				/* Now scan forward to find the matching NOTE OFF event */
				for(noteOffPos = eventPos; noteOffPos; noteOffPos = noteOffPos->next)
				{
					if ((noteOffPos->status&0xF0)>>4 == MIDI_STATUS_NOTE_OFF
						&& channel == (eventPos->status&0x0F)
						&& pitch == noteOffPos->data[0])
						break;
					/* NOTE ON with velocity == 0 is the same as a NOTE OFF */
					if ((noteOffPos->status&0xF0)>>4 == MIDI_STATUS_NOTE_ON
						&& channel == (eventPos->status&0x0F)
						&& pitch == noteOffPos->data[0]
						&& 0 == noteOffPos->data[1])
						break;
				}
				
				/* Did we find a note off? Should always be the case, but who knows... */
				if (noteOffPos)
				{
					/* We found a NOTE OFF, now calculate the note duration */
					int duration = (int)((noteOffPos->time - eventPos->time)*tick);
					
					REST_IF_NECESSARY();
					/* Now we need to check if we get along with a normal Note Event, or if we need an extended one... */
					if (duration < 2048 && pitch>=32 && pitch<=95 && velocity>=0 && velocity<=127)
					{
						qtma_StuffNoteEvent(*tunePos, part, pitch, velocity, duration);
						tunePos++;
					}
					else
					{
						qtma_StuffXNoteEvent(*tunePos, *(tunePos+1), part, pitch, velocity, duration);
						tunePos+=2;
					}
				}
			}
			break;
		case MIDI_STATUS_AFTERTOUCH:
			/* NYI - use kControllerAfterTouch. But how are the parameters to be mapped? */
			break;
		case MIDI_STATUS_CONTROLLER:
			controller = eventPos->data[0];
			value = eventPos->data[1];

			switch(controller)
			{
			case 0:	/* bank change - igore for now */
				break;
			case kControllerVolume:
				if(channel_vol[channel] != value<<8)
				{
					channel_vol[channel] = value<<8;
					if(part>=0 && part<=31)
					{
						REST_IF_NECESSARY();
						qtma_StuffControlEvent(*tunePos, part, kControllerVolume, channel_vol[channel]);
						tunePos++;
					}
				}
				break;
			case kControllerPan:
				if(channel_pan[channel] != (value << 1) + 256)
				{
					channel_pan[channel] = (value << 1) + 256;
					if(part>=0 && part<=31)
					{
						REST_IF_NECESSARY();
						qtma_StuffControlEvent(*tunePos, part, kControllerPan, channel_pan[channel]);
						tunePos++;
					}
				}
				break;
			default:
				/* No other controllers implemented yet */;
				break;
			}
			
			break;
		case MIDI_STATUS_PROG_CHANGE:
			/* Instrument changed */
			newInst = eventPos->data[0];
			
			/* Channel 9 (the 10th channel) is different, it indicates a drum kit */
			if (channel == 9)
				newInst += kFirstDrumkit;
			else
				newInst += kFirstGMInstrument;
			/* Only if the instrument for this channel *really* changed, add a new part. */
			if(newInst != part_to_inst[part])
			{
				/* TODO maybe make use of kGeneralEventPartChange here,
				   to help QT reuse note channels?
				*/
				part = channel_to_part[channel] = *numParts;
				part_to_inst[(*numParts)++] = newInst;

				if(channel_vol[channel] >= 0)
				{
					REST_IF_NECESSARY();
					qtma_StuffControlEvent(*tunePos, part, kControllerVolume, channel_vol[channel]);
					tunePos++;
				}
				if(channel_pan[channel] >= 0)
				{
					REST_IF_NECESSARY();
					qtma_StuffControlEvent(*tunePos, part, kControllerPan, channel_pan[channel]);
					tunePos++;
				}
				if(channel_pitch_bend[channel] >= 0)
				{
					REST_IF_NECESSARY();
					qtma_StuffControlEvent(*tunePos, part, kControllerPitchBend, channel_pitch_bend[channel]);
					tunePos++;
				}			
			}
			break;
		case MIDI_STATUS_PRESSURE:
			/* NYI */
			break;
		case MIDI_STATUS_PITCH_WHEEL:
			/* In the midi spec, 0x2000 = center, 0x0000 = - 2 semitones, 0x3FFF = +2 semitones
			   but for QTMA, we specify it as a 8.8 fixed point of semitones
			   TODO: detect "pitch bend range changes" & honor them!
			*/
			bend = (eventPos->data[0] & 0x7f) | ((eventPos->data[1] & 0x7f) << 7);
			
			/* "Center" the bend */
			bend -= 0x2000;
			
			/* Move it to our format: */
			bend <<= 4;
			
			/* If it turns out the pitch bend didn't change, stop here */
			if(channel_pitch_bend[channel] == bend)
				break;
			
			channel_pitch_bend[channel] = bend;
			if(part>=0 && part<=31)
			{
				/* Stuff a control event */
				REST_IF_NECESSARY();
				qtma_StuffControlEvent(*tunePos, part, kControllerPitchBend, bend);
				tunePos++;
			}			
			break;
		case MIDI_STATUS_SYSEX:
			if (eventPos->status == 0xFF && eventPos->data[0] == 0x51) /* Tempo change */
			{
				tempo = (eventPos->extraData[0] << 16) +
					(eventPos->extraData[1] << 8) +
					eventPos->extraData[2];
				
				tick = tempo * Ippqn;
			}
			break;
		}
		
		/* on to the next event */
		eventPos = eventPos->next;
	} 
	
	/* Finally, place an end marker */
	*tunePos = kEndMarkerValue;
	
	return tuneSequence;
}

Uint32 *BuildTuneHeader(int part_poly_max[32], int part_to_inst[32], int numParts)
{
	Uint32			*myHeader;
	Uint32			*myPos1, *myPos2;		/* pointers to the head and tail long words of a music event */
	NoteRequest		*myNoteRequest;
	NoteAllocator	myNoteAllocator;		/* for the NAStuffToneDescription call */
	ComponentResult	myErr = noErr;
	int				part;

	myHeader = NULL;
	myNoteAllocator = NULL;

	/*
	 * Open up the Note Allocator
	 */
	myNoteAllocator = OpenDefaultComponent(kNoteAllocatorComponentType,0);
	if (myNoteAllocator == NULL)
		goto bail;
	
	/*
	 * Allocate space for the tune header
	 */
	myHeader = (Uint32 *)
			NewPtrClear((numParts * kNoteRequestEventLength + kMarkerEventLength) * sizeof(Uint32));
	if (myHeader == NULL)
		goto bail;
	
	myPos1 = myHeader;
	
	/*
	 * Loop over all parts
	 */
	for(part = 0; part < numParts; ++part)
	{
		/*
		 * Stuff request for the instrument with the given polyphony
		 */
		myPos2 = myPos1 + (kNoteRequestEventLength - 1); /* last longword of general event */
		qtma_StuffGeneralEvent(*myPos1, *myPos2, part, kGeneralEventNoteRequest, kNoteRequestEventLength);
		myNoteRequest = (NoteRequest *)(myPos1 + 1);
		myNoteRequest->info.flags = 0;
		/* I'm told by the Apple people that the Quicktime types were poorly designed and it was 
		 * too late to change them. On little endian, the BigEndian(Short|Fixed) types are structs
		 * while on big endian they are primitive types. Furthermore, Quicktime failed to 
		 * provide setter and getter functions. To get this to work, we need to case the 
		 * code for the two possible situations.
		 * My assumption is that the right-side value was always expected to be BigEndian
		 * as it was written way before the Universal Binary transition. So in the little endian
		 * case, OSSwap is used.
		 */
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
		myNoteRequest->info.polyphony.bigEndianValue = OSSwapHostToBigInt16(part_poly_max[part]);
		myNoteRequest->info.typicalPolyphony.bigEndianValue = OSSwapHostToBigInt32(0x00010000);
#else
		myNoteRequest->info.polyphony = part_poly_max[part];
		myNoteRequest->info.typicalPolyphony = 0x00010000;
#endif
		myErr = NAStuffToneDescription(myNoteAllocator,part_to_inst[part],&myNoteRequest->tone);
		if (myErr != noErr)
			goto bail;
		
		/* move pointer to beginning of next event */
		myPos1 += kNoteRequestEventLength;
	}

	*myPos1 = kEndMarkerValue;		/* end of sequence marker */


bail:
	if(myNoteAllocator)
		CloseComponent(myNoteAllocator);

	/* if we encountered an error, dispose of the storage we allocated and return NULL */
	if (myErr != noErr) {
		DisposePtr((Ptr)myHeader);
		myHeader = NULL;
	}

	return myHeader;
}

#endif /* MacOS native MIDI support */
