/*=============================================================================
	CAAudioFileFormats.cpp
	
	$Log: CAAudioFileFormats.cpp,v $
	Revision 1.3  2005/02/25 02:02:18  dwyatt
	[4006370 ] fix InferDataFormatFromFileFormat
	
	Revision 1.2  2004/09/03 03:55:45  jcm10
	fix various header issues
	
	Revision 1.1  2004/05/25 23:30:10  dwyatt
	moved from Source/Tests/AudioFileTools/Utility
	
	Revision 1.2  2004/05/15 01:20:10  dwyatt
	track changes to CAAudioFile etc.
	
	Revision 1.1  2004/01/14 00:09:51  dwyatt
	moved from Source/Tests/AudioFileUtility/Utility
	
	Revision 1.2  2003/08/04 23:45:20  dwyatt
	track extensions, add OSTypeToStr
	
	Revision 1.1  2003/06/23 23:07:37  dwyatt
	initial checkin
	
	created Fri Jun 20 2003, Doug Wyatt
	Copyright (c) 2003 Apple Computer, Inc.  All Rights Reserved

	$NoKeywords: $
=============================================================================*/

#include "CAAudioFileFormats.h"
#include <algorithm>
#include <ctype.h>

CAAudioFileFormats *CAAudioFileFormats::sInstance = NULL;

CAAudioFileFormats *CAAudioFileFormats::Instance()
{
	if (sInstance == NULL)
		sInstance = new CAAudioFileFormats;
	return sInstance;
}

/*
class CompareFileFormatNames {
public:
	bool	operator() (const CAAudioFileFormats::FileFormatInfo &a, const CAAudioFileFormats::FileFormatInfo &b)
	{
		return CFStringCompare(a.mFileTypeName, b.mFileTypeName, 
			kCFCompareCaseInsensitive | kCFCompareLocalized) == kCFCompareLessThan;
	}
};*/

static int CompareFileFormatNames(const void *va, const void *vb)
{
	CAAudioFileFormats::FileFormatInfo  *a = (CAAudioFileFormats::FileFormatInfo *)va, 
										*b = (CAAudioFileFormats::FileFormatInfo *)vb;
	return CFStringCompare(a->mFileTypeName, b->mFileTypeName, 
		kCFCompareCaseInsensitive | kCFCompareLocalized);
}

CAAudioFileFormats::CAAudioFileFormats() : 
	mNumFileFormats(0), mFileFormats(NULL)
{
	OSStatus err;
	UInt32 size;
	UInt32 *fileTypes = NULL, *writableFormats = NULL, *readableFormats = NULL;
	int nWritableFormats, nReadableFormats;
	
	// get all file types
	err = AudioFileGetGlobalInfoSize(kAudioFileGlobalInfo_WritableTypes, 0, NULL, &size);
	if (err != noErr) goto bail;
	mNumFileFormats = size / sizeof(UInt32);
	mFileFormats = new FileFormatInfo[mNumFileFormats];
	fileTypes = new UInt32[mNumFileFormats];
	err = AudioFileGetGlobalInfo(kAudioFileGlobalInfo_WritableTypes, 0, NULL, &size, fileTypes);
	if (err != noErr) goto bail;
	
	// get all writable formats
	err = AudioFormatGetPropertyInfo(kAudioFormatProperty_EncodeFormatIDs, 0, NULL, &size);
	if (err != noErr) goto bail;
	nWritableFormats = size / sizeof(UInt32);
	writableFormats = new UInt32[nWritableFormats];
	err = AudioFormatGetProperty(kAudioFormatProperty_EncodeFormatIDs, 0, NULL, &size, writableFormats);
	if (err != noErr) goto bail;
	
	// get all readable formats
	err = AudioFormatGetPropertyInfo(kAudioFormatProperty_DecodeFormatIDs, 0, NULL, &size);
	if (err != noErr) goto bail;
	nReadableFormats = size / sizeof(UInt32);
	readableFormats = new UInt32[nReadableFormats];
	err = AudioFormatGetProperty(kAudioFormatProperty_DecodeFormatIDs, 0, NULL, &size, readableFormats);
	if (err != noErr) goto bail;
	
	// get info for each file type
	for (int i = 0; i < mNumFileFormats; ++i) {
		FileFormatInfo *ffi = &mFileFormats[i];
		OSType filetype = fileTypes[i];

		ffi->mFileTypeID = filetype;
		
		// file type name
		ffi->mFileTypeName = NULL;
		size = sizeof(CFStringRef);
		err = AudioFileGetGlobalInfo(kAudioFileGlobalInfo_FileTypeName, sizeof(UInt32), &filetype, &size, &ffi->mFileTypeName);
		if (ffi->mFileTypeName)
			CFRetain(ffi->mFileTypeName);
		
		// file extensions
		size = sizeof(CFArrayRef);
		err = AudioFileGetGlobalInfo(kAudioFileGlobalInfo_ExtensionsForType,
			sizeof(OSType), &filetype, &size, &ffi->mExtensions);
		if (err)
			ffi->mExtensions = NULL;
		
		// file data formats
		ffi->mNumDataFormats = 0;
		ffi->mDataFormats = NULL;

		err = AudioFileGetGlobalInfoSize(kAudioFileGlobalInfo_AvailableFormatIDs, 
				sizeof(UInt32), &filetype, &size);
		if (err == noErr) {
			ffi->mNumDataFormats = size / sizeof(OSType);
			OSType *formatIDs = new OSType[ffi->mNumDataFormats];
			err = AudioFileGetGlobalInfo(kAudioFileGlobalInfo_AvailableFormatIDs,
				sizeof(UInt32), &filetype, &size, formatIDs);
			if (err == noErr) {
				ffi->mDataFormats = new DataFormatInfo[ffi->mNumDataFormats];
				for (int j = 0; j < ffi->mNumDataFormats; ++j) {
					int k;
					bool anyBigEndian = false, anyLittleEndian = false;
					DataFormatInfo *dfi = &ffi->mDataFormats[j];
					dfi->mFormatID = formatIDs[j];
					dfi->mReadable = (dfi->mFormatID == kAudioFormatLinearPCM);
					dfi->mWritable = (dfi->mFormatID == kAudioFormatLinearPCM);
					for (k = 0; k < nReadableFormats; ++k)
						if (readableFormats[k] == dfi->mFormatID) {
							dfi->mReadable = true;
							break;
						}
					for (k = 0; k < nWritableFormats; ++k)
						if (writableFormats[k] == dfi->mFormatID) {
							dfi->mWritable = true;
							break;
						}
					
					dfi->mNumVariants = 0;
					AudioFileTypeAndFormatID tf = { filetype, dfi->mFormatID };
					err = AudioFileGetGlobalInfoSize(kAudioFileGlobalInfo_AvailableStreamDescriptionsForFormat,
						sizeof(AudioFileTypeAndFormatID), &tf, &size);
					if (err == noErr) {
						dfi->mNumVariants = size / sizeof(AudioStreamBasicDescription);
						dfi->mVariants = new AudioStreamBasicDescription[dfi->mNumVariants];
						err = AudioFileGetGlobalInfo(kAudioFileGlobalInfo_AvailableStreamDescriptionsForFormat,
							sizeof(AudioFileTypeAndFormatID), &tf, &size, dfi->mVariants);
						if (err) {
							dfi->mNumVariants = 0;
							delete[] dfi->mVariants;
							dfi->mVariants = NULL;
						} else {
							for (k = 0; k < dfi->mNumVariants; ++k) {
								AudioStreamBasicDescription *desc = &dfi->mVariants[k];
								if (desc->mBitsPerChannel > 8) {
									if (desc->mFormatFlags & kAudioFormatFlagIsBigEndian)
										anyBigEndian = true;
									else
										anyLittleEndian = true;
								}
							}
						}
					}
					
					dfi->mEitherEndianPCM = (anyBigEndian && anyLittleEndian);
				}
			}
			delete[] formatIDs;
		}
	}

	// sort file formats by name
	qsort(mFileFormats, mNumFileFormats, sizeof(FileFormatInfo), CompareFileFormatNames);
bail:
	delete[] fileTypes;
	delete[] readableFormats;
	delete[] writableFormats;
}

// note that the outgoing format will have zero for the sample rate, channels per frame, bytesPerPacket, bytesPerFrame
bool	CAAudioFileFormats::InferDataFormatFromFileFormat(AudioFileTypeID filetype, CAStreamBasicDescription &fmt)
{
	// if the file format only supports one data format
	for (int i = 0; i < mNumFileFormats; ++i) {
		FileFormatInfo *ffi = &mFileFormats[i];
		if (ffi->mFileTypeID == filetype && ffi->mNumDataFormats == 1) {
			DataFormatInfo *dfi = &ffi->mDataFormats[0];
			memset(&fmt, 0, sizeof(fmt));
			fmt.mFormatID = dfi->mFormatID;
			if (dfi->mNumVariants > 0) {
				// take the first variant as a default
				fmt = dfi->mVariants[0];
				if (dfi->mNumVariants > 1 && dfi->mFormatID == kAudioFormatLinearPCM) {
					// look for a 16-bit variant as a better default
					for (int j = 0; j < dfi->mNumVariants; ++j) {
						AudioStreamBasicDescription *desc = &dfi->mVariants[j];
						if (desc->mBitsPerChannel == 16) {
							fmt = *desc;
							break;
						}
					}
				}
			}
			return true;
		}
	}
	return false;
}

bool	CAAudioFileFormats::InferFileFormatFromDataFormat(const CAStreamBasicDescription &fmt, 
			AudioFileTypeID &filetype)
{
	// if there's exactly one file format that supports this data format
	FileFormatInfo *theFileFormat = NULL;
	for (int i = 0; i < mNumFileFormats; ++i) {
		FileFormatInfo *ffi = &mFileFormats[i];
		DataFormatInfo *dfi = ffi->mDataFormats, *dfiend = dfi + ffi->mNumDataFormats;
		for ( ; dfi < dfiend; ++dfi)
			if (dfi->mFormatID == fmt.mFormatID) {
				if (theFileFormat != NULL)
					return false;	// ambiguous
				theFileFormat = ffi;	// got a candidate
			}
	}
	if (theFileFormat == NULL)
		return false;
	filetype = theFileFormat->mFileTypeID;
	return true;
}

bool	CAAudioFileFormats::IsKnownDataFormat(OSType dataFormat)
{
	for (int i = 0; i < mNumFileFormats; ++i) {
		FileFormatInfo *ffi = &mFileFormats[i];
		DataFormatInfo *dfi = ffi->mDataFormats, *dfiend = dfi + ffi->mNumDataFormats;
		for ( ; dfi < dfiend; ++dfi)
			if (dfi->mFormatID == dataFormat)
				return true;
	}
	return false;
}

CAAudioFileFormats::FileFormatInfo *	CAAudioFileFormats::FindFileFormat(UInt32 formatID)
{
	for (int i = 0; i < mNumFileFormats; ++i) {
		FileFormatInfo *ffi = &mFileFormats[i];
		if (ffi->mFileTypeID == formatID)
			return ffi;
	}
	return NULL;
}

bool	CAAudioFileFormats::FileFormatInfo::AnyWritableFormats()
{
	DataFormatInfo *dfi = mDataFormats, *dfiend = dfi + mNumDataFormats;
	for ( ; dfi < dfiend; ++dfi)
		if (dfi->mWritable)
			return true;
	return false;
}

char *OSTypeToStr(char *buf, OSType t)
{
	char *p = buf;
	char str[4], *q = str;
	*(UInt32 *)str = EndianU32_NtoB(t);
	for (int i = 0; i < 4; ++i) {
		if (isprint(*q) && *q != '\\')
			*p++ = *q++;
		else {
			sprintf(p, "\\x%02x", *q++);
			p += 4;
		}
	}
	*p = '\0';
	return buf;
}

int		StrToOSType(const char *str, OSType &t)
{
	char buf[4];
	const char *p = str;
	int x;
	for (int i = 0; i < 4; ++i) {
		if (*p != '\\') {
			if ((buf[i] = *p++) == '\0')
				goto fail;
		} else {
			if (*++p != 'x') goto fail;
			if (sscanf(++p, "%02X", &x) != 1) goto fail;
			buf[i] = x;
			p += 2;
		}
	}
	t = EndianU32_BtoN(*(UInt32 *)buf);
	return p - str;
fail:
	return 0;
}

#if DEBUG

void	CAAudioFileFormats::DebugPrint()
{
	for (int i = 0; i < mNumFileFormats; ++i)
		mFileFormats[i].DebugPrint();
}

void	CAAudioFileFormats::FileFormatInfo::DebugPrint()
{
	char ftype[20];
	char ftypename[64];
	CFStringGetCString(mFileTypeName, ftypename, sizeof(ftypename), kCFStringEncodingUTF8);
	printf("File type: '%s' = %s\n  Extensions:", OSTypeToStr(ftype, mFileTypeID), ftypename);
	int i, n = NumberOfExtensions();
	for (i = 0; i < n; ++i) {
		GetExtension(i, ftype, sizeof(ftype));
		printf(" .%s", ftype);
	}
	printf("\n  Formats:\n");
	for (i = 0; i < mNumDataFormats; ++i)
		mDataFormats[i].DebugPrint();
}

void	CAAudioFileFormats::DataFormatInfo::DebugPrint()
{
	char buf[20];
	static char *ny[] = { "not ", "" };
	printf("    '%s': %sreadable %swritable\n", OSTypeToStr(buf, mFormatID), ny[mReadable], ny[mWritable]);
	for (int i = 0; i < mNumVariants; ++i) {
		CAStreamBasicDescription desc(mVariants[i]);
		desc.PrintFormat(stdout, "      ", "");
		//printf("        %d bytes/frame\n", desc.mBytesPerFrame);
	}
}
#endif

