//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// AudioFileReaderThread.cpp
//
//	$Log: AudioFileReaderThread.cpp,v $
//	Revision 1.10  2003/07/29 22:36:23  luke
//	[3326532] fixed
//	
//	Revision 1.9  2003/05/09 18:38:08  baron
//	check for IsLooping().
//	
//	Revision 1.8  2003/04/18 17:37:43  baron
//	Make play file code work for aac.
//	
//	Revision 1.7  2003/04/09 22:33:10  baron
//	Pass audio data around by packets instead of bytes.
//	
//	Revision 1.6  2003/03/13 22:11:33  baron
//	Remove V1 code. Replace usage of AudioFile Read/WriteByte APIs with the Read/WritePacket versions.
//	
//	Revision 1.5  2002/07/18 19:02:16  luke
//	Updated to use new mach thread APIs.
//	
//	Revision 1.4  2002/07/14 21:18:06  bills
//	tweaks to setting thread policies
//	
//	Revision 1.3  2002/06/10 00:52:19  bills
//	remove warning
//	
//	Revision 1.2  2002/06/01 23:58:19  bills
//	first pass at v2 units
//	
//	Revision 1.1  2002/05/18 01:19:47  bills
//	new location
//	
//	Revision 1.1  2002/05/17 07:56:45  bills
//	new dir loc
//	
//	Revision 1.6  2002/05/17 07:48:05  bills
//	Add FilePlay "C" API
//	
//	Revision 1.5  2002/05/16 09:22:09  bills
//	add notifications for file underruns
//	
//	Revision 1.4  2002/05/16 09:07:47  bills
//	use a read thread for files (code is safe! for multiple read threads)
//	
//	Revision 1.3  2002/05/15 07:33:02  bills
//	add warnings
//	
//	Revision 1.2  2002/05/15 06:21:59  bills
//	now use AudioConverter to do file reading
//	
//	Revision 1.1  2002/05/14 08:12:23  bills
//	initial checkin
//	
//
//		file reading class that reads a file from disk
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#include "AudioFilePlayer.h"
#include <mach/mach.h> //used for setting policy of thread
#include "CAGuard.h"
#include <pthread.h>

#include <vector>

#if DEBUG
    #define	LOG_DATA_FLOW 0
#endif

class FileReaderThread {
public:
	FileReaderThread ();

	CAGuard&					GetGuard() { return mGuard; }
    
	void						AddReader();
	
	void						RemoveReader (const FileThreadVariables* inItem);
		
		// returns true if succeeded
	bool						TryNextRead (FileThreadVariables* inItem)
	{
		bool didLock = false;
		bool succeeded = false;
		if (mGuard.Try (didLock))
		{
			mFileData.push_back (inItem);
			mGuard.Notify();
			succeeded = true;
		}
		
		if (didLock)
			mGuard.Unlock();
				
			return succeeded;
	}	
	
private:
	typedef	std::vector<FileThreadVariables*> FileData;

	CAGuard				mGuard;
	UInt32				mThreadPriority;
	bool				mThreadShouldDie;
	int					mNumReaders;	
	FileData			mFileData;


	void 						ReadNextChunk ();
	
	void 						StartFixedPriorityThread ();
    static UInt32				GetThreadBasePriority (pthread_t inThread);
    
	static void*				DiskReaderEntry (void *inRefCon);
};

FileReaderThread::FileReaderThread ()
	  : mGuard ("AudioFileReaderThread"),
	    mThreadPriority (62),
		mNumReaders (0)
{
	mFileData.reserve (48);
}

void	FileReaderThread::AddReader()
{
	if (mNumReaders == 0)
	{
		mThreadShouldDie = false;
	
		StartFixedPriorityThread ();
	}
	mNumReaders++;
}

void	FileReaderThread::RemoveReader (const FileThreadVariables* inItem)
{
	if (mNumReaders > 0)
	{
		CAGuard::Locker fileReadLock (mGuard);

		for (FileData::iterator iter = mFileData.begin(); iter != mFileData.end(); ++iter)
		{
			if ((*iter) == inItem) {	
				mFileData.erase (iter);
			}
		}
		
		if (--mNumReaders == 0) {
			mThreadShouldDie = true;
			mGuard.Notify();
		}
	}	
}

void 	FileReaderThread::StartFixedPriorityThread ()
{
	pthread_attr_t		theThreadAttrs;
	pthread_t			pThread;
	
	OSStatus result = pthread_attr_init(&theThreadAttrs);
		THROW_RESULT("pthread_attr_init - Thread attributes could not be created.")
	
	result = pthread_attr_setdetachstate(&theThreadAttrs, PTHREAD_CREATE_DETACHED);
		THROW_RESULT("pthread_attr_setdetachstate - Thread attributes could not be detached.")
	
	result = pthread_create (&pThread, &theThreadAttrs, DiskReaderEntry, this);
		THROW_RESULT("pthread_create - Create and start the thread.")
	
	pthread_attr_destroy(&theThreadAttrs);
    
	// we've now created the thread and started it
	// we'll now set the priority of the thread to the nominated priority
	// and we'll also make the thread fixed
    thread_extended_policy_data_t		theFixedPolicy;
    thread_precedence_policy_data_t		thePrecedencePolicy;
    SInt32								relativePriority;
    
    // make thread fixed
    theFixedPolicy.timeshare = false;	// set to true for a non-fixed thread
    result = thread_policy_set (pthread_mach_thread_np(pThread), THREAD_EXTENDED_POLICY, (thread_policy_t)&theFixedPolicy, THREAD_EXTENDED_POLICY_COUNT);
        THROW_RESULT("thread_policy - Couldn't set thread as fixed priority.")
    // set priority
    // precedency policy's "importance" value is relative to spawning thread's priority
    relativePriority = mThreadPriority - FileReaderThread::GetThreadBasePriority (pthread_self());
        
    thePrecedencePolicy.importance = relativePriority;
    result = thread_policy_set (pthread_mach_thread_np(pThread), THREAD_PRECEDENCE_POLICY, (thread_policy_t)&thePrecedencePolicy, THREAD_PRECEDENCE_POLICY_COUNT);
        THROW_RESULT("thread_policy - Couldn't set thread priority.")
}

UInt32	FileReaderThread::GetThreadBasePriority (pthread_t inThread)
{
    thread_basic_info_data_t			threadInfo;
	policy_info_data_t					thePolicyInfo;
	unsigned int						count;
    
    // get basic info
    count = THREAD_BASIC_INFO_COUNT;
    thread_info (pthread_mach_thread_np (inThread), THREAD_BASIC_INFO, (integer_t*)&threadInfo, &count);
    
	switch (threadInfo.policy) {
		case POLICY_TIMESHARE:
			count = POLICY_TIMESHARE_INFO_COUNT;
			thread_info(pthread_mach_thread_np (inThread), THREAD_SCHED_TIMESHARE_INFO, (integer_t*)&(thePolicyInfo.ts), &count);
			return thePolicyInfo.ts.base_priority;
            break;
            
        case POLICY_FIFO:
			count = POLICY_FIFO_INFO_COUNT;
			thread_info(pthread_mach_thread_np (inThread), THREAD_SCHED_FIFO_INFO, (integer_t*)&(thePolicyInfo.fifo), &count);
            if (thePolicyInfo.fifo.depressed) {
                return thePolicyInfo.fifo.depress_priority;
            } else {
                return thePolicyInfo.fifo.base_priority;
            }
            break;
            
		case POLICY_RR:
			count = POLICY_RR_INFO_COUNT;
			thread_info(pthread_mach_thread_np (inThread), THREAD_SCHED_RR_INFO, (integer_t*)&(thePolicyInfo.rr), &count);
			if (thePolicyInfo.rr.depressed) {
                return thePolicyInfo.rr.depress_priority;
            } else {
                return thePolicyInfo.rr.base_priority;
            }
            break;
	}
    
    return 0;
}

void	*FileReaderThread::DiskReaderEntry (void *inRefCon)
{
	FileReaderThread *This = (FileReaderThread *)inRefCon;
	This->ReadNextChunk();
	#if DEBUG
	printf ("finished with reading file\n");
	#endif
	
	return 0;
}

void 	FileReaderThread::ReadNextChunk ()
{
	OSStatus 						result;
	UInt32							dataChunkSize;
	UInt32							dataChunkSizeInPackets;
    AudioStreamPacketDescription	*packetDescriptions = NULL;
	FileThreadVariables* 			theItem = 0;

	for (;;) 
	{
		{ // this is a scoped based lock
			CAGuard::Locker fileReadLock (mGuard);
			
			if (mThreadShouldDie) return;
			
			while (mFileData.empty()) {
				fileReadLock.Wait();
			}
			
			// kill thread
			if (mThreadShouldDie) return;

			theItem = mFileData[0];
			mFileData.erase (mFileData.begin());
		}
	

		packetDescriptions = theItem->mPacketDescriptions;
        if (!theItem->mWriteToFirstBuffer)
            packetDescriptions += theItem->mChunkSizeInPackets;
        
        if ((theItem->mPacketCount - theItem->mReadPacketPosition) < theItem->mChunkSizeInPackets)
		{
        	dataChunkSizeInPackets = theItem->mPacketCount - theItem->mReadPacketPosition;
            if (!theItem->IsLooping()) {
                theItem->mFinishedReadingData = true;
            }
		}
        else
			dataChunkSizeInPackets = theItem->mChunkSizeInPackets;

        // this is the exit condition for the thread
		if (dataChunkSizeInPackets == 0 && !theItem->IsLooping()) {
			theItem->mFinishedReadingData = true;
			continue;
		}

        // construct pointer
        char* writePtr = const_cast<char*>(theItem->GetFileBuffer() + 
                            (theItem->mWriteToFirstBuffer ? 0 : theItem->mChunkSizeInPackets * theItem->mMaxPacketSize));
	
#if LOG_DATA_FLOW
		fprintf(stdout, "***** ReadNextChunk(1) - AFReadPackets (pkts/offset) = %ld/%qd\n", dataChunkSizeInPackets, theItem->mReadPacketPosition);
#endif
        result = AudioFileReadPackets (theItem->GetFileID(), 
                                        false,
                                        &dataChunkSize,
                                        packetDescriptions,
                                        theItem->mReadPacketPosition, 
                                        &dataChunkSizeInPackets, 
                                        writePtr);
		if (result) {
			theItem->GetParent().DoNotification(result);
			continue;
		}

		theItem->mCurrentPacketCountInBuffer = dataChunkSizeInPackets;
		theItem->mCurrentByteCountInBuffer = dataChunkSize;

		if (dataChunkSizeInPackets != theItem->mChunkSizeInPackets)
		{
			writePtr += dataChunkSize;
            packetDescriptions += dataChunkSizeInPackets;

			if (theItem->IsLooping())
			{

                packetDescriptions = theItem->mPacketDescriptions + dataChunkSizeInPackets;
				dataChunkSizeInPackets = theItem->mChunkSizeInPackets - dataChunkSizeInPackets;
				theItem->mReadPacketPosition = 0;
            
#if LOG_DATA_FLOW
                fprintf(stdout, "***** ReadNextChunk(2) - AFReadPackets (pkts/offset) = %ld/%qd\n", dataChunkSizeInPackets, theItem->mReadPacketPosition);
#endif
                result = AudioFileReadPackets (theItem->GetFileID(), 
                                                false,
                                                &dataChunkSize,
                                                packetDescriptions,
                                                theItem->mReadPacketPosition, 
                                                &dataChunkSizeInPackets, 
                                                writePtr);
                if (result) {
                    theItem->GetParent().DoNotification(result);
                    continue;
                }
                theItem->mCurrentPacketCountInBuffer += dataChunkSizeInPackets;
                theItem->mCurrentByteCountInBuffer += dataChunkSize;
			} 
            else 
            {
                // can't exit yet.. we still have to pass the partial buffer back
                memset (writePtr, 0, ((theItem->mChunkSizeInPackets - dataChunkSizeInPackets) * theItem->mMaxPacketSize));
			}
		}
		
        theItem->mWriteToFirstBuffer = !theItem->mWriteToFirstBuffer;	// switch buffers
		
		theItem->mReadPacketPosition += dataChunkSizeInPackets;		// increment count
	}
}


static FileReaderThread sReaderThread;

AudioFileReaderThread::AudioFileReaderThread (AudioFilePlayer	&inParent, 
										AudioFileID 			&inFile, 
										SInt64 					inFileLength,
                                        SInt64					inPacketCount,
                                        UInt32					inMaxPacketSize,
                                        UInt32					inChunkSizeInPackets)
	: FileThreadVariables (inChunkSizeInPackets, inFileLength, inPacketCount, inMaxPacketSize, inParent, inFile),
	  mLockUnsuccessful (false),
	  mIsEngaged (false)
{
	mFileBuffer = (char*) malloc (mChunkSizeInPackets * mMaxPacketSize * 2);
    mPacketDescriptions = (AudioStreamPacketDescription *) calloc (1, (mChunkSizeInPackets * sizeof(AudioStreamPacketDescription)) * 2);
}

void	AudioFileReaderThread::DoConnect ()
{
	if (!mIsEngaged)
	{
		mFinishedReadingData = false;

		mNumTimesAskedSinceFinished = -1;
		mLockUnsuccessful = false;
		
		UInt32 dataChunkSize = 0;

		mCurrentPacketCountInBuffer = mChunkSizeInPackets;
#if LOG_DATA_FLOW
		fprintf(stdout, "***** DoConnect - AFReadPackets from  (pkts/offset) %ld/%qd\n", mCurrentPacketCountInBuffer, mReadPacketPosition);
#endif
		OSStatus result = AudioFileReadPackets ( mAudioFileID, 
												false, 
												&dataChunkSize,
                                                mPacketDescriptions,
                                                mReadPacketPosition, 
												&mCurrentPacketCountInBuffer, 
												mFileBuffer);
			THROW_RESULT("AudioFileReadPackets")
        
        mCurrentByteCountInBuffer = dataChunkSize;
		mReadPacketPosition = mCurrentPacketCountInBuffer;

		mWriteToFirstBuffer = false;
		mReadFromFirstBuffer = true;

		sReaderThread.AddReader();
		
		mIsEngaged = true;
	}
	else
		throw static_cast<OSStatus>(-1); //thread has already been started
}

void	AudioFileReaderThread::Disconnect ()
{
	if (mIsEngaged) 
	{
		sReaderThread.RemoveReader (this);
		mIsEngaged = false;
	}
}

OSStatus AudioFileReaderThread::GetFileData (void** inOutData, UInt32 *inOutDataSize, UInt32 *outPacketCount, AudioStreamPacketDescription	**outPacketDescriptions)
{
	if (mFinishedReadingData) 
	{
		++mNumTimesAskedSinceFinished;
		*inOutDataSize = 0;
		*inOutData = 0;
		return noErr;
	}
	
	if (mReadFromFirstBuffer == mWriteToFirstBuffer) {
		#if DEBUG
		printf ("* * * * * * * Can't keep up with reading file:%ld\n", mParent.GetBusNumber());
		#endif
		
		mParent.DoNotification (kAudioFilePlayErr_FilePlayUnderrun);
		*inOutDataSize = 0;
		*inOutData = 0;
	} else {
		*inOutDataSize = mCurrentByteCountInBuffer;
		*inOutData = mReadFromFirstBuffer ? mFileBuffer : (mFileBuffer + (mChunkSizeInPackets * mMaxPacketSize));
        *outPacketCount = mCurrentPacketCountInBuffer;
		*outPacketDescriptions = mReadFromFirstBuffer ? mPacketDescriptions : (mPacketDescriptions + mChunkSizeInPackets);
	}

	mLockUnsuccessful = !sReaderThread.TryNextRead (this);
	
	mReadFromFirstBuffer = !mReadFromFirstBuffer;

	return noErr;
}

void 	AudioFileReaderThread::AfterRender ()
{
	if (mNumTimesAskedSinceFinished > 0)
	{
		bool didLock = false;
		if (sReaderThread.GetGuard().Try (didLock)) {
			mParent.DoNotification (kAudioFilePlay_FileIsFinished);
			if (didLock)
				sReaderThread.GetGuard().Unlock();
		}
	}

	if (mLockUnsuccessful)
		mLockUnsuccessful = !sReaderThread.TryNextRead (this);
}
