/**
 * SDL_sound; An abstract sound format decoding API.
 *
 * Please see the file LICENSE.txt in the source's root directory.
 *
 *  This file written by Eric Wing.
 */

#define __SDL_SOUND_INTERNAL__
#include "SDL_sound_internal.h"

#if SOUND_SUPPORTS_COREAUDIO

#include <stddef.h> /* NULL */
#include <AudioToolbox/AudioToolbox.h>

typedef struct CoreAudioFileContainer
{
	AudioFileID* audioFileID;
	ExtAudioFileRef extAudioFileRef;
	AudioStreamBasicDescription* outputFormat;
} CoreAudioFileContainer;


static SDL_bool CoreAudio_init(void)
{
    return SDL_TRUE;  /* always succeeds. */
} /* CoreAudio_init */


static void CoreAudio_quit(void)
{
    /* it's a no-op. */
} /* CoreAudio_quit */

/*
   https://developer.apple.com/library/ios/#documentation/MusicAudio/Reference/AudioFileConvertRef/Reference/reference.html
   kAudioFileAIFFType = 'AIFF',
   kAudioFileAIFCType            = 'AIFC',
   kAudioFileWAVEType            = 'WAVE',
   kAudioFileSoundDesigner2Type  = 'Sd2f',
   kAudioFileNextType            = 'NeXT',
   kAudioFileMP3Type             = 'MPG3',
   kAudioFileMP2Type             = 'MPG2',
   kAudioFileMP1Type             = 'MPG1',
   kAudioFileAC3Type             = 'ac-3',
   kAudioFileAAC_ADTSType        = 'adts',
   kAudioFileMPEG4Type           = 'mp4f',
   kAudioFileM4AType             = 'm4af',
   kAudioFileCAFType             = 'caff',
   kAudioFile3GPType             = '3gpp',
   kAudioFile3GP2Type            = '3gp2',
   kAudioFileAMRType             = 'amrf'
*/
static AudioFileTypeID CoreAudio_GetAudioTypeForExtension(const char* file_extension)
{
	if( (SDL_strcasecmp(file_extension, "aif") == 0)
		|| (SDL_strcasecmp(file_extension, "aiff") == 0)
		|| (SDL_strcasecmp(file_extension, "aifc") == 0)
	)
	{
		return kAudioFileAIFCType;
	}
	else if( (SDL_strcasecmp(file_extension, "wav") == 0)
		|| (SDL_strcasecmp(file_extension, "wave") == 0)
	)
	{
		return kAudioFileWAVEType;
	}
	else if( (SDL_strcasecmp(file_extension, "mp3") == 0)
	)
	{
		return kAudioFileMP3Type;
	}
	else if( (SDL_strcasecmp(file_extension, "mp4") == 0)
	)
	{
		return kAudioFileMPEG4Type;
	}
	else if( (SDL_strcasecmp(file_extension, "m4a") == 0)
	)
	{
		return kAudioFileM4AType;
	}
	else if( (SDL_strcasecmp(file_extension, "aac") == 0)
	)
	{
		return kAudioFileAAC_ADTSType;
	}
	else if( (SDL_strcasecmp(file_extension, "aac") == 0)
	)
	{
		return kAudioFileAAC_ADTSType;
	}
	else if( (SDL_strcasecmp(file_extension, "caf") == 0)
		|| (SDL_strcasecmp(file_extension, "caff") == 0)
	)
	{
		return kAudioFileCAFType;
	}
	else if( (SDL_strcasecmp(file_extension, "Sd2f") == 0)
		|| (SDL_strcasecmp(file_extension, "sd2") == 0)
	)
	{
		return kAudioFileSoundDesigner2Type;
	}
	else if( (SDL_strcasecmp(file_extension, "au") == 0)
		|| (SDL_strcasecmp(file_extension, "next") == 0)
	)
	{
		return kAudioFileNextType;
	}
	else if( (SDL_strcasecmp(file_extension, "mp2") == 0)
	)
	{
		return kAudioFileMP2Type;
	}
	else if( (SDL_strcasecmp(file_extension, "mp1") == 0)
	)
	{
		return kAudioFileMP1Type;
	}
	else if( (SDL_strcasecmp(file_extension, "ac3") == 0)
	)
	{
		return kAudioFileAC3Type;
	}
	else if( (SDL_strcasecmp(file_extension, "3gpp") == 0)
	)
	{
		return kAudioFile3GPType;
	}
	else if( (SDL_strcasecmp(file_extension, "3gp2") == 0)
	)
	{
		return kAudioFile3GP2Type;
	}
	else if( (SDL_strcasecmp(file_extension, "amrf") == 0)
		|| (SDL_strcasecmp(file_extension, "amr") == 0)
	)
	{
		return kAudioFileAMRType;
	}
	else if( (SDL_strcasecmp(file_extension, "ima4") == 0)
		|| (SDL_strcasecmp(file_extension, "ima") == 0)
	)
	{
		/* not sure about this one */
		return kAudioFileCAFType;
	}
	else
	{
		return 0;
	}
}

#ifdef DEBUG_CHATTER /* used with SNDDBG() */
static const char* CoreAudio_FourCCToString(Sint32 error_code)
{
	static char return_string[16];
	Uint32 big_endian_code = SDL_SwapBE32((Uint32)error_code);
	char* big_endian_str = (char*)&big_endian_code;
	// see if it appears to be a 4-char-code
	if(isprint(big_endian_str[0])
	   && isprint(big_endian_str[1])
	   && isprint(big_endian_str[2])
	   && isprint (big_endian_str[3]))
	{
		return_string[0] = '\'';
		return_string[1] = big_endian_str[0];
		return_string[2] = big_endian_str[1];
		return_string[3] = big_endian_str[2];
		return_string[4] = big_endian_str[3];
		return_string[5] = '\'';
		return_string[6] = '\0';
	}
	else if(error_code > -200000 && error_code < 200000)
	{
		// no, format it as an integer
		snprintf(return_string, 16, "%d", error_code);
	}
	else
	{
		// no, format it as an integer but in hex
		snprintf(return_string, 16, "0x%x", error_code);
	}
	return return_string;
}
#endif


SInt64 CoreAudio_SizeCallback(void* inClientData)
{
	SDL_RWops* rw_ops = (SDL_RWops*)inClientData;
	const Sint64 current_position = SDL_RWtell(rw_ops);
	const Sint64 end_position = SDL_RWseek(rw_ops, 0, RW_SEEK_END);
	SDL_RWseek(rw_ops, current_position, RW_SEEK_SET);
//	fprintf(stderr, "CoreAudio_SizeCallback:%d\n", end_position);
	return end_position;
}

OSStatus CoreAudio_ReadCallback(
	void* inClientData,
	SInt64 inPosition,
	UInt32 requestCount,
	void* data_buffer,
	UInt32* actualCount
)
{
	SDL_RWops* rw_ops = (SDL_RWops*)inClientData;
	SDL_RWseek(rw_ops, inPosition, RW_SEEK_SET);
	size_t bytes_actually_read = SDL_RWread(rw_ops, data_buffer, 1, requestCount);
	// Not sure how to test for a read error with SDL_RWops
//	fprintf(stderr, "CoreAudio_ReadCallback:%d, %d\n", requestCount, bytes_actually_read);

	*actualCount = bytes_actually_read;
	return noErr;
}


static int CoreAudio_open(Sound_Sample *sample, const char *ext)
{
	CoreAudioFileContainer* core_audio_file_container;
	AudioFileID* audio_file_id;
	OSStatus error_result;
	Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
	AudioStreamBasicDescription	actual_format;
	AudioStreamBasicDescription	output_format;
	Float64 estimated_duration;
	UInt32 format_size;

	core_audio_file_container = (CoreAudioFileContainer*)SDL_malloc(sizeof(CoreAudioFileContainer));
	BAIL_IF_MACRO(core_audio_file_container == NULL, ERR_OUT_OF_MEMORY, 0);

	audio_file_id = (AudioFileID*)SDL_malloc(sizeof(AudioFileID));
	BAIL_IF_MACRO(audio_file_id == NULL, ERR_OUT_OF_MEMORY, 0);

	error_result = AudioFileOpenWithCallbacks(
		internal->rw,
		CoreAudio_ReadCallback,
		NULL,
		CoreAudio_SizeCallback,
		NULL,
		CoreAudio_GetAudioTypeForExtension(ext),
		audio_file_id
	);
	if (error_result != noErr)
	{
		AudioFileClose(*audio_file_id);
		SDL_free(audio_file_id);
		SDL_free(core_audio_file_container);
		SNDDBG(("Core Audio: can't grok data. reason: [%s].\n", CoreAudio_FourCCToString(error_result)));
		BAIL_MACRO("Core Audio: Not valid audio data.", 0);
	} /* if */

	format_size = sizeof(actual_format);
	error_result = AudioFileGetProperty(
		*audio_file_id,
		kAudioFilePropertyDataFormat,
		&format_size,
		&actual_format
	);
	if (error_result != noErr)
	{
		AudioFileClose(*audio_file_id);
		SDL_free(audio_file_id);
		SDL_free(core_audio_file_container);
		SNDDBG(("Core Audio: AudioFileGetProperty failed. reason: [%s]", CoreAudio_FourCCToString(error_result)));
		BAIL_MACRO("Core Audio: Not valid audio data.", 0);
	} /* if */

	format_size = sizeof(estimated_duration);
	error_result = AudioFileGetProperty(
		*audio_file_id,
		kAudioFilePropertyEstimatedDuration,
		&format_size,
		&estimated_duration
	);
	if (error_result != noErr)
	{
		AudioFileClose(*audio_file_id);
		SDL_free(audio_file_id);
		SDL_free(core_audio_file_container);
		SNDDBG(("Core Audio: AudioFileGetProperty failed. reason: [%s].\n", CoreAudio_FourCCToString(error_result)));
		BAIL_MACRO("Core Audio: Not valid audio data.", 0);
	} /* if */

	core_audio_file_container->audioFileID = audio_file_id;

	internal->decoder_private = core_audio_file_container;

	sample->flags = SOUND_SAMPLEFLAG_CANSEEK;
	sample->actual.rate = (UInt32) actual_format.mSampleRate;
	sample->actual.channels = (UInt8)actual_format.mChannelsPerFrame;
	internal->total_time = (SInt32)(estimated_duration * 1000.0 + 0.5);

	if(0 == sample->desired.format)
	{
		if(32 == actual_format.mBitsPerChannel)
		{
			if(kAudioFormatFlagIsFloat & actual_format.mFormatFlags)
			{
				if(actual_format.mFormatFlags & kAudioFormatFlagIsBigEndian)
				{
					sample->actual.format = AUDIO_F32MSB;
				}
				else
				{
					sample->actual.format = AUDIO_F32LSB;
				}
			}
			else
			{
				if(actual_format.mFormatFlags & kAudioFormatFlagIsBigEndian)
				{
					sample->actual.format = AUDIO_S32MSB;
				}
				else
				{
					sample->actual.format = AUDIO_S32LSB;
				}
			}
		}
		else if(16 == actual_format.mBitsPerChannel)
		{
			if(kAudioFormatFlagIsSignedInteger & actual_format.mFormatFlags)
			{
				if(actual_format.mFormatFlags & kAudioFormatFlagIsBigEndian)
				{
					sample->actual.format = AUDIO_S16MSB;
				}
				else
				{
					sample->actual.format = AUDIO_S16LSB;
				}
			}
			else
			{
				if(actual_format.mFormatFlags & kAudioFormatFlagIsBigEndian)
				{
					sample->actual.format = AUDIO_U16MSB;
				}
				else
				{
					sample->actual.format = AUDIO_U16LSB;
				}
			}
		}
		else if(8 == actual_format.mBitsPerChannel)
		{
			if(kAudioFormatFlagIsSignedInteger & actual_format.mFormatFlags)
			{
				sample->actual.format = AUDIO_S8;
			}
			else
			{
				sample->actual.format = AUDIO_U8;
			}
		}
		else // might be 0 for undefined?
		{
			// This case seems to come up a lot for me. Maybe for file types like .m4a?
			sample->actual.format = AUDIO_S16SYS;
			SNDDBG(("Core Audio: Unsupported actual_format.mBitsPerChannel: [%d].\n", actual_format.mBitsPerChannel));
		}
	}
	else
	{
		/*
		 * I want to use Core Audio to do conversion and decoding for performance reasons.
		 * This is particularly important on mobile devices like iOS.
		 * Taking from the Ogg Vorbis decode, I pretend the "actual" format is the same
		 * as the desired format.
		 */
		sample->actual.format = sample->desired.format;
	}

	/* AUDIO_U16LSB and AUDIO_U16MSB don't seem to be supported by CoreAudio. */
	if (sample->actual.format == AUDIO_U16LSB)
		sample->actual.format = AUDIO_S16LSB;
	if (sample->actual.format == AUDIO_U16MSB)
		sample->actual.format = AUDIO_S16MSB;

	SNDDBG(("CoreAudio: channels == (%d).\n", sample->actual.channels));
	SNDDBG(("CoreAudio: sampling rate == (%d).\n",sample->actual.rate));
	SNDDBG(("CoreAudio: total seconds of sample == (%d).\n", internal->total_time));
	SNDDBG(("CoreAudio: sample->actual.format == (%d).\n", sample->actual.format));

	error_result = ExtAudioFileWrapAudioFileID(*audio_file_id,
		false, // set to false for read-only
		&core_audio_file_container->extAudioFileRef
	);
	if(error_result != noErr)
	{
		AudioFileClose(*audio_file_id);
		SDL_free(audio_file_id);
		SDL_free(core_audio_file_container);
		SNDDBG(("Core Audio: can't wrap data. reason: [%s].\n", CoreAudio_FourCCToString(error_result)));
		BAIL_MACRO("Core Audio: Failed to wrap data.", 0);
	} /* if */

	/* The output format must be linear PCM because that's the only type OpenAL knows how to deal with.
	 * Set the client format to 16 bit signed integer (native-endian) data because that is the most
	 * optimal format on iPhone/iPod Touch hardware.
	 * Maintain the channel count and sample rate of the original source format.
	 */
	output_format.mSampleRate = actual_format.mSampleRate; // preserve the original sample rate
	output_format.mChannelsPerFrame = actual_format.mChannelsPerFrame; // preserve the number of channels
	output_format.mFormatID = kAudioFormatLinearPCM; // We want linear PCM data
	output_format.mFormatFlags = kAudioFormatFlagIsPacked; // I seem to read failures problems without kAudioFormatFlagIsPacked. From a mailing list post, this seems to be a Core Audio bug.
	output_format.mFramesPerPacket = 1; // We know for linear PCM, the definition is 1 frame per packet
	output_format.mBitsPerChannel = SDL_AUDIO_BITSIZE(sample->actual.format);

	if(SDL_AUDIO_ISFLOAT(sample->actual.format))
	{
		output_format.mFormatFlags |= kAudioFormatFlagIsFloat;
	}

	if(SDL_AUDIO_ISSIGNED(sample->actual.format))
	{
		output_format.mFormatFlags |= kAudioFormatFlagIsSignedInteger;
	}

	if(SDL_AUDIO_ISBIGENDIAN(sample->actual.format))
	{
		output_format.mFormatFlags |= kAudioFormatFlagIsBigEndian;
	}

	output_format.mBytesPerPacket = output_format.mBitsPerChannel/8 * output_format.mChannelsPerFrame; // e.g. 16-bits/8 * channels => so 2-bytes per channel per frame
	output_format.mBytesPerFrame = output_format.mBitsPerChannel/8 * output_format.mChannelsPerFrame; // For PCM, since 1 frame is 1 packet, it is the same as mBytesPerPacket

	SNDDBG(("output_format: mSampleRate: %lf\n", output_format.mSampleRate));
	SNDDBG(("output_format: mChannelsPerFrame: %d\n", output_format.mChannelsPerFrame));
	SNDDBG(("output_format: mFormatID: %d\n", output_format.mFormatID));
	SNDDBG(("output_format: mFormatFlags: %d\n", output_format.mFormatFlags));
	SNDDBG(("output_format: mFramesPerPacket: %d\n", output_format.mFramesPerPacket));
	SNDDBG(("output_format: mBitsPerChannel: %d\n", output_format.mBitsPerChannel));
	SNDDBG(("output_format: mBytesPerPacket: %d\n", output_format.mBytesPerPacket));
	SNDDBG(("output_format: mBytesPerFrame: %d\n", output_format.mBytesPerFrame));

	/* Set the desired client (output) data format */
	error_result = ExtAudioFileSetProperty(core_audio_file_container->extAudioFileRef, kExtAudioFileProperty_ClientDataFormat, sizeof(output_format), &output_format);
	if(noErr != error_result)
	{
		ExtAudioFileDispose(core_audio_file_container->extAudioFileRef);
		AudioFileClose(*audio_file_id);
		SDL_free(audio_file_id);
		SDL_free(core_audio_file_container);
		SNDDBG(("Core Audio: ExtAudioFileSetProperty(kExtAudioFileProperty_ClientDataFormat) failed, reason: [%s].\n", CoreAudio_FourCCToString(error_result)));
		BAIL_MACRO("Core Audio: Not valid audio data.", 0);
	}

	core_audio_file_container->outputFormat = (AudioStreamBasicDescription*)SDL_malloc(sizeof(AudioStreamBasicDescription));
	BAIL_IF_MACRO(core_audio_file_container->outputFormat == NULL, ERR_OUT_OF_MEMORY, 0);

	/* Copy the output format to the audio_description that was passed in so the 
	 * info will be returned to the user.
	 */
	SDL_memcpy(core_audio_file_container->outputFormat, &output_format, sizeof(AudioStreamBasicDescription));

	return 1;
} /* CoreAudio_open */


static void CoreAudio_close(Sound_Sample *sample)
{
	Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
	CoreAudioFileContainer* core_audio_file_container = (CoreAudioFileContainer *) internal->decoder_private;

	SDL_free(core_audio_file_container->outputFormat);
	ExtAudioFileDispose(core_audio_file_container->extAudioFileRef);
	AudioFileClose(*core_audio_file_container->audioFileID);
	SDL_free(core_audio_file_container->audioFileID);
	SDL_free(core_audio_file_container);
} /* CoreAudio_close */


static Uint32 CoreAudio_read(Sound_Sample *sample)
{
	OSStatus error_result = noErr;	
	/* Documentation/example shows SInt64, but is problematic for big endian
	 * on 32-bit cast for ExtAudioFileRead() because it takes the upper
	 * bits which turn to 0.
	 */
	UInt32 buffer_size_in_frames = 0;
	UInt32 buffer_size_in_frames_remaining = 0;
	UInt32 total_frames_read = 0;
	UInt32 data_buffer_size = 0;
	UInt32 bytes_remaining = 0;
	size_t total_bytes_read = 0;
	Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
	CoreAudioFileContainer* core_audio_file_container = (CoreAudioFileContainer *) internal->decoder_private;
	UInt32 max_buffer_size = internal->buffer_size;

//	printf("internal->buffer_size=%d, internal->buffer=0x%x, sample->buffer_size=%d\n", internal->buffer_size, internal->buffer, sample->buffer_size); 
//	printf("internal->max_buffer_size=%d\n", max_buffer_size); 

	/* Compute how many frames will fit into our max buffer size */
	/* Warning: If this is not evenly divisible, the buffer will not be completely filled which violates the SDL_sound assumption. */
	buffer_size_in_frames = max_buffer_size / core_audio_file_container->outputFormat->mBytesPerFrame;
//	printf("buffer_size_in_frames=%ld, internal->buffer_size=%d, internal->buffer=0x%x outputFormat->mBytesPerFrame=%d, sample->buffer_size=%d\n", buffer_size_in_frames, internal->buffer_size, internal->buffer, core_audio_file_container->outputFormat->mBytesPerFrame, sample->buffer_size); 


//	void* temp_buffer = SDL_malloc(max_buffer_size);

	AudioBufferList audio_buffer_list;
	audio_buffer_list.mNumberBuffers = 1;
	audio_buffer_list.mBuffers[0].mDataByteSize = max_buffer_size;
	audio_buffer_list.mBuffers[0].mNumberChannels = core_audio_file_container->outputFormat->mChannelsPerFrame;
	audio_buffer_list.mBuffers[0].mData = internal->buffer;

	bytes_remaining = max_buffer_size;
	buffer_size_in_frames_remaining = buffer_size_in_frames;

	// oops. Due to the kAudioFormatFlagIsPacked bug,
	// I was misled to believe that Core Audio
	// was not always filling my entire requested buffer.
	// So this while-loop might be unnecessary.
	// However, I have not exhaustively tested all formats,
	// so maybe it is possible this loop is useful.
	// It might also handle the not-evenly disvisible case above.
	while(buffer_size_in_frames_remaining > 0 && !(sample->flags & SOUND_SAMPLEFLAG_EOF))
	{
		data_buffer_size = (UInt32)(buffer_size_in_frames * core_audio_file_container->outputFormat->mBytesPerFrame);
//		printf("data_buffer_size=%d\n", data_buffer_size);

		buffer_size_in_frames = buffer_size_in_frames_remaining;

//		printf("reading buffer_size_in_frames=%"PRId64"\n", buffer_size_in_frames); 

		audio_buffer_list.mBuffers[0].mDataByteSize = bytes_remaining;
		audio_buffer_list.mBuffers[0].mData = &(((UInt8*)internal->buffer)[total_bytes_read]);

		/* Read the data into an AudioBufferList */
		error_result = ExtAudioFileRead(core_audio_file_container->extAudioFileRef, &buffer_size_in_frames, &audio_buffer_list);
		if(error_result == noErr)
		{
			/* Success */
			total_frames_read += buffer_size_in_frames;
			buffer_size_in_frames_remaining = buffer_size_in_frames_remaining - buffer_size_in_frames;

//			printf("read buffer_size_in_frames=%"PRId64", buffer_size_in_frames_remaining=%"PRId64"\n", buffer_size_in_frames, buffer_size_in_frames_remaining); 

			/* ExtAudioFileRead returns the number of frames actually read. Need to convert back to bytes. */
			data_buffer_size = (UInt32)(buffer_size_in_frames * core_audio_file_container->outputFormat->mBytesPerFrame);
//			printf("data_buffer_size=%d\n", data_buffer_size);

			total_bytes_read += data_buffer_size;
			bytes_remaining = bytes_remaining - data_buffer_size;

			/* Note: 0 == buffer_size_in_frames is a legitimate value meaning we are EOF. */
			if(0 == buffer_size_in_frames)
			{
				sample->flags |= SOUND_SAMPLEFLAG_EOF;
			}
		}
		else
		{
			SNDDBG(("Core Audio: ExtAudioFileReadfailed, reason: [%s].\n", CoreAudio_FourCCToString(error_result)));

			sample->flags |= SOUND_SAMPLEFLAG_ERROR;
			break;
		}
	}

	if( (!(sample->flags & SOUND_SAMPLEFLAG_EOF)) && (total_bytes_read < max_buffer_size))
	{
		SNDDBG(("Core Audio: ExtAudioFileReadfailed SOUND_SAMPLEFLAG_EAGAIN, reason: [total_bytes_read < max_buffer_size], %d, %d.\n", total_bytes_read , max_buffer_size));
		
		/* Don't know what to do here. */
		sample->flags |= SOUND_SAMPLEFLAG_EAGAIN;
	}
	return total_bytes_read;
} /* CoreAudio_read */


static int CoreAudio_rewind(Sound_Sample *sample)
{
	OSStatus error_result = noErr;
	Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
	CoreAudioFileContainer* core_audio_file_container = (CoreAudioFileContainer *) internal->decoder_private;

	error_result = ExtAudioFileSeek(core_audio_file_container->extAudioFileRef, 0);
	if(error_result != noErr) {
		sample->flags |= SOUND_SAMPLEFLAG_ERROR;
	}
	return 1;
} /* CoreAudio_rewind */

/* Note: I found this tech note:
 https://developer.apple.com/library/mac/#qa/qa2009/qa1678.html
 I don't know if this applies to us. So far, I haven't noticed the problem,
 so I haven't applied any of the techniques.
 */
static int CoreAudio_seek(Sound_Sample *sample, Uint32 ms)
{
	OSStatus error_result = noErr;	
	Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
	CoreAudioFileContainer* core_audio_file_container = (CoreAudioFileContainer *) internal->decoder_private;
	SInt64 frame_offset = 0;

#if 0 /* not working */
	/* I'm confused. The Apple documentation says this:
	"Seek position is specified in the sample rate and frame count of the file’s audio data format
	— not your application’s audio data format."
	My interpretation is that I want to get the "actual format of the file and compute the frame offset.
	But when I try that, seeking goes to the wrong place.
	When I use outputFormat, things seem to work correctly.
	I must be misinterpreting the documentation or doing something wrong.
	*/
	AudioStreamBasicDescription	actual_format;
	UInt32 format_size = sizeof(AudioStreamBasicDescription);
	error_result = AudioFileGetProperty(
		*core_audio_file_container->audioFileID,
		kAudioFilePropertyDataFormat,
		&format_size,
		&actual_format
	);
	if(error_result != noErr) {
		sample->flags |= SOUND_SAMPLEFLAG_ERROR;
		BAIL_MACRO("Core Audio: Could not GetProperty for kAudioFilePropertyDataFormat.", 0);
	} /* if */

	// packetIndex = (pos * sampleRate) / framesPerPacket
	//	frame_offset = (SInt64)((ms/1000.0 * actual_format.mSampleRate) / actual_format.mFramesPerPacket);
#else /* seems to work, but I'm confused */
	// packetIndex = (pos * sampleRate) / framesPerPacket
	frame_offset = (SInt64)((ms/1000.0 * core_audio_file_container->outputFormat->mSampleRate) / core_audio_file_container->outputFormat->mFramesPerPacket);	
#endif

	// computed against actual format and not the client format
	error_result = ExtAudioFileSeek(core_audio_file_container->extAudioFileRef, frame_offset);
	if(error_result != noErr) {
		sample->flags |= SOUND_SAMPLEFLAG_ERROR;
	}

	return 1;
} /* CoreAudio_seek */


static const char *extensions_coreaudio[] =
{
	"aif",
	"aiff",
	"aifc",
	"wav",
	"wave",
	"mp3",
	"mp4",
	"m4a",
	"aac",
	"caf",
	"Sd2f",
	"Sd2",
	"au",
	"next",
	"mp2",
	"mp1",
	"ac3",
	"3gpp",
	"3gp2",
	"amrf",
	"amr",
	"ima4",
	"ima",
	NULL 
};
const Sound_DecoderFunctions __Sound_DecoderFunctions_CoreAudio =
{
    {
        extensions_coreaudio,
        "Decode audio through Core Audio through",
        "Eric Wing <ewing . public @ playcontrol.net>",
        "https://playcontrol.net"
    },

    CoreAudio_init,       /*   init() method */
    CoreAudio_quit,       /*   quit() method */
    CoreAudio_open,       /*   open() method */
    CoreAudio_close,      /*  close() method */
    CoreAudio_read,       /*   read() method */
    CoreAudio_rewind,     /* rewind() method */
    CoreAudio_seek        /*   seek() method */
};

#endif /* SOUND_SUPPORTS_COREAUDIO */

/* end of SDL_sound_coreaudio.c ... */

