/*
 Copyright (C) 2002 Andreas Thiede ( a.thiede@berlin.de )

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

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

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

#include <Carbon/Carbon.h>
#include <CoreAudio/AudioHardware.h>
#include <IO/osx/AudioHardware.hpp>
#include <IO/Manager/IOOutput.hpp>
#include <IO/Manager/IOInput.hpp>
#include <IO/Manager/IOBuffer.hpp>
#include <IO/Manager/IOBufferList.hpp>
#include <assert.h>

/* forward */ void BlitThisData(const AudioBufferList  *inBufferList, AudioBufferList *outBufferList, UInt32 inStartingOutputChannel);

static AudioHardware * theAudioHardware = 0;

#define MAX_BUFFERS 10

static IOBuffer *  ringBuffer[MAX_BUFFERS];
static int         currentBuffer = 0;

static void initRingBuffer()
{
    for( int i = 0; i < MAX_BUFFERS; i++ ) {

        char * buf = ( char *) malloc( 8192  );

        IOBuffer * buffer = IOBuffer::initWithConstPointer( buf, 8192, i );

        ringBuffer[i] = buffer;
    }
}

static IOBuffer * nextBuffer()
{
    IOBuffer * b = ringBuffer[ currentBuffer ];
    
    if( currentBuffer + 1 < MAX_BUFFERS ) {
        currentBuffer++;
    }
    else {
        currentBuffer = 0;
    }
    return b;
}

static int GetAudioDevices( Ptr * devices, short * devicesAvailable )
{
    OSStatus	err = noErr;
    UInt32 		outSize;
    Boolean		outWritable;
    
    // find out how many audio devices there are, if any
    err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &outSize, &outWritable);	
    
    if ( err != noErr ) {
	return 0;
    }
   
    // calculate the number of device available
    *devicesAvailable = outSize / sizeof(AudioDeviceID);						
    if ( *devicesAvailable < 1 )
      {
	fprintf( stderr, "No devices\n" );
	return 0;
      }
    
    // make space for the devices we are about to get
    *devices = (Ptr) malloc(outSize);		
    	
    memset( *devices, 0, outSize );			
    // get an array of AudioDeviceIDs
        
    err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &outSize, (void *) *devices);	

    if (err != noErr )
      return 0;

    return 1;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This is a simple playThru ioProc. It simply places the data in the input buffer back into the output buffer.
// Watch out for feedback from Speakers to Microphone
OSStatus softPlayThruIOProc (AudioDeviceID  inDevice, const AudioTimeStamp*  inNow, const AudioBufferList*   inInputData, 
                             const AudioTimeStamp*  inInputTime, AudioBufferList*  outOutputData, const AudioTimeStamp* inOutputTime, 
                             void* device)
{        
    puts( "softPlayThruIOProc" );
    
    // move data at the input buffer into the output data buffer
    if((outOutputData != NULL) && (outOutputData->mNumberBuffers > 0))
    {
        BlitThisData(inInputData, outOutputData, 0);
    }
    
    return (noErr);     
}

static OSStatus outputIOProc (	AudioDeviceID inDevice, const AudioTimeStamp *inNow, 
                                const AudioBufferList *inInputData,   const AudioTimeStamp *inInputTime, 
                                      AudioBufferList *outOutputData, const AudioTimeStamp *inOutputTime, 
                                void *_out )
{
    static unsigned numBuffers = 0;
    
    //printf( "outputIOProc inInputData(%p)\n", inInputData );
    
    if(  _out && outOutputData && outOutputData->mNumberBuffers && outOutputData->mBuffers[0].mData != 0 ) {

        IOOutput * out = (IOOutput*) _out;

        IOBuffer * buffer = out->getBuffer( 0 );
    
        if( buffer ) {

            //printf( "outputIOProc: %s useCount(%d)\n", buffer->getName().c_str(), buffer->useCount() ); fflush( stdout );

            memcpy( outOutputData->mBuffers[0].mData, buffer->getData(), buffer->getSize() );

            buffer->release();

            //printf( "outputIOProc: buffer released\n" );
            numBuffers++;
            //printf( "outputIOProc: numBuffers(%d)\r", numBuffers );
            //fflush(stdout);
        }
        else {

        #if 0
            memset(outOutputData->mBuffers[0].mData, 0, out->frameSize() );
        #endif
        
        }
    }
    return noErr;
}

static OSStatus inputIOProc (	AudioDeviceID inDevice, const AudioTimeStamp *inNow, 
                                const AudioBufferList *inInputData,   const AudioTimeStamp *inInputTime, 
                                      AudioBufferList *outOutputData, const AudioTimeStamp *inOutputTime, 
                                void *_in )
{
    IOInput * in = (IOInput*) _in;

    for( unsigned i = 0; i <  inInputData->mNumberBuffers; i++ ) {
        
        IOBuffer * buffer = nextBuffer();
    
        memcpy( buffer->getData(), inInputData->mBuffers[i].mData, inInputData->mBuffers[i].mDataByteSize );
        
        if( buffer ) {
        
            in->push( buffer );

            //printf( "inputIOProc: %s useCount(%d) size(%d)\n", buffer->getName().c_str(), buffer->useCount(), buffer->getSize() );
                        
            //buffer->release();

        }
    }
    return noErr;
}

/* static */ AudioHardware& AudioHardware::instance()
{
    if( theAudioHardware == 0 )
        theAudioHardware = new AudioHardware();
        
    assert( theAudioHardware != 0 );
    
    return *theAudioHardware;
}

   
AudioHardware::AudioHardware()
 : IOHardware( "AudioHardware")
{
}
        
AudioHardware::~AudioHardware()
{
    
}

void AudioHardware::updateDeviceList()
{
    AudioDeviceID * devices;
    short           devicesAvailable;
   
    if( GetAudioDevices( (Ptr*)&devices, &devicesAvailable ) ) {
      
        for( int i = 0; i < devicesAvailable; i++ ) {

            IODescription dd("AudioDevice");

            UInt32	                theSize;
            OSStatus	                theStatus;
            AudioStreamBasicDescription	theFormat;
            char	                theString[1024];
            AudioBufferList *           theBufferList;
            UInt32                      theNumberInputChannels  = 0;
            UInt32                      theNumberOutputChannels = 0;
            UInt32                      theIndex;

            theSize = 255;
            
            theStatus = AudioDeviceGetProperty( devices[i], 0, 0, kAudioDevicePropertyDeviceName, &theSize, theString);

            if( theStatus != 0 ) {
                dd.setName( "unkown" );
            }
            else {
                dd.setName( string( theString ) );
            }
            
            dd.setDeviceID( devices[i] );

            theSize = sizeof(AudioStreamBasicDescription);
            memset(&theFormat, 0, sizeof(AudioStreamBasicDescription));

            theSize = 0;
            
            theStatus = AudioDeviceGetPropertyInfo( devices[i], 0, 1, kAudioDevicePropertyStreamConfiguration, &theSize, NULL);

            if((theStatus == 0) && (theSize != 0)) {

                theBufferList = (AudioBufferList*)malloc(theSize);
		
                if(theBufferList != NULL) {
                    
                    //get the input stream configuration
                    theStatus = AudioDeviceGetProperty( devices[i], 0, 1, kAudioDevicePropertyStreamConfiguration, &theSize, 
                                                        theBufferList);
                    
                    if(theStatus == 0) {
					
                        // get the total number of channels for later
			for(theIndex = 0; theIndex < theBufferList->mNumberBuffers; ++theIndex)	{
                            theNumberInputChannels += theBufferList->mBuffers[theIndex].mNumberChannels;
			}
                    }
	
                    // free the list
                    free(theBufferList);
                }
	
                if( theNumberInputChannels > 0 ) {
                 
                    dd.setDirection( IODescription::ioIn );

                    //printf("AudioHardware::updateDeviceList adding input %s %ld\n", theString, devices[i] );
                
                    mDeviceList.append( new IODescription(dd) );
                }
            }
            
            theSize = 0;
            
            theStatus = AudioDeviceGetPropertyInfo( devices[i], 0, 0, kAudioDevicePropertyStreamConfiguration, &theSize, NULL);

            if((theStatus == 0) && (theSize != 0)) {
	
                theBufferList = (AudioBufferList*)malloc(theSize);

		if(theBufferList != NULL) {
                    
                    // get the output stream configuration
		
                    theStatus = AudioDeviceGetProperty( devices[i], 0, 0, kAudioDevicePropertyStreamConfiguration, &theSize, 
                                                        theBufferList);

                    if(theStatus == 0) {

			// get the total number of channels for later
			
                        for(theIndex = 0; theIndex < theBufferList->mNumberBuffers; ++theIndex) 	{
                            theNumberOutputChannels += theBufferList->mBuffers[theIndex].mNumberChannels;
			}
                    }
	
                    // free the list
                    free(theBufferList);
		}

                if( theNumberOutputChannels > 0 ) {
        
                    dd.setDirection( IODescription::ioOut );

                    //printf("AudioHardware::updateDeviceList adding output %s %ld\n", theString, devices[i] );
                
                    mDeviceList.append( new IODescription(dd) );
                }
            }
        }
    }

    free( devices );
}

/* static */ bool AudioHardware::init( IODescription::IODirection direction, IODeviceID id, 
                                        size_t bufferSize, int nChannels, void * _device )
{
    initRingBuffer();
    
    AudioStreamBasicDescription outDeviceFormat;

    OSErr  err;
    UInt32 outSize;
    UInt32 isAlive;

    outSize = sizeof( outDeviceFormat );

    err = AudioDeviceGetProperty( id,
                                  0, 
                                  false, 
			          kAudioDevicePropertyStreamFormat, 
			          &outSize, 
                                  &outDeviceFormat);

    isAlive = 0;

    outSize = sizeof(UInt32);

    AudioDeviceGetProperty( id,
                            0,
                            false, 
                            kAudioDevicePropertyDeviceIsAlive,
                            &outSize,
                            &isAlive );

  
    outSize = sizeof( outDeviceFormat );

    err = AudioDeviceGetProperty( id, 0, false,  kAudioDevicePropertyStreamFormatMatch,
                                  &outSize, &outDeviceFormat);
    
    err = AudioDeviceSetProperty( id, 
                                  0, 
			          0,
                                  false,
                                  kAudioDevicePropertyBufferSize,
                                  sizeof(UInt32),
			          &bufferSize );

    outSize = sizeof( bufferSize );
  
    err = AudioDeviceGetProperty( id, 0, false, kAudioDevicePropertyBufferSize, &outSize, &bufferSize );
    
    fprintf(stderr, "deviceBufferSize = %ld\n", bufferSize);

    if ( err != noErr )
    {
      fprintf(stderr, "Can't set bufferSize");
      return false;
    }

    if( direction == IODescription::ioOut ) {
    
        err = AudioDeviceAddIOProc( id, outputIOProc,  _device );

    }
    else {

        err = AudioDeviceAddIOProc( id, inputIOProc,  _device );
        // use the softPlayThru IOProc on this device
        //err = AudioDeviceAddIOProc( id, softPlayThruIOProc, _device );

    }
    
    return ( err == noErr );
}

/* static */ bool AudioHardware::finit( IODescription::IODirection direction, IODeviceID anID )
{
    OSStatus result;
    
    if( direction == IODescription::ioOut ) {

        result = AudioDeviceRemoveIOProc( anID, outputIOProc );
    }
    else {

        result = AudioDeviceRemoveIOProc( anID, inputIOProc );
        //result = AudioDeviceRemoveIOProc( anID, softPlayThruIOProc );
    }

    return result == noErr;
}

/* static */ bool AudioHardware::start( IODescription::IODirection direction, IODeviceID anID, IONode * _device )
{
    OSStatus result;

    if( direction == IODescription::ioOut ) {
    
        result = AudioDeviceStart( anID, outputIOProc );

    }
    else {

        result = AudioDeviceStart( anID, inputIOProc );
        //result = AudioDeviceStart( anID, softPlayThruIOProc );
    }

    return result == noErr;
}

/* static */ bool AudioHardware::stop ( IODescription::IODirection direction, IODeviceID anID, IONode * _device )
{
    if( direction == IODescription::ioOut ) {
    
        return ( AudioDeviceStop( anID, outputIOProc ) == noErr );
        
    }
    else {

        return ( AudioDeviceStop( anID, inputIOProc ) == noErr );
        //return ( AudioDeviceStop( anID, softPlayThruIOProc ) == noErr );
    }
}

/* static */ bool AudioHardware::getVolume( IODeviceID anID, int & l, int & r , bool isInput )
{
    Float32 left, right;
    UInt32 size;
    OSErr err1, err2;

    size = sizeof(left);
    err1 = AudioDeviceGetProperty( anID, 
                                   1, 
			           isInput, 
			           kAudioDevicePropertyVolumeScalar, 
			           &size,
			           &left);

    err2 = AudioDeviceGetProperty( anID, 
	 		           2, 
			           isInput, 
			           kAudioDevicePropertyVolumeScalar, 
			           &size,
			           &right);

    l = (int) (left  * 100.0);
    r = (int) (right * 100.0);
    
    return ( err1 ==  noErr && err2 == noErr );
}

/* static */ bool AudioHardware::setVolume( IODeviceID anID, int l, int r, bool isInput )
{
    printf("AudioHardware::setVolume devcieID(%d) lr(%d,%d) input(%d)\n", anID, l, r, isInput );
    
    Float32 left, right;
    UInt32 size;
    OSErr err1,err2;

    left = l / 100.0;
    right = r / 100.0;

    size = sizeof(left);
  
    err1 = AudioDeviceSetProperty( anID, 
			           0,
			           1, 
			           isInput, 
			           kAudioDevicePropertyVolumeScalar, 
			           size,
			           &left);

    err2 = AudioDeviceSetProperty( anID, 
			           0,
			           2, 
			           isInput, 
			           kAudioDevicePropertyVolumeScalar, 
			           size,
			           &right);

    return ( err1 ==  noErr && err2 == noErr );
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This is for Hardware Play Thru (NOT soft play thru)
/* static */ int AudioHardware::SetHWPlayThru (IODeviceID deviceInfo, int channel, short hwPlayThruOn)
{
    OSStatus	err = noErr;
#if 0
    UInt32	dataSize,
    
    playThruOn = hwPlayThruOn;
                                            
    dataSize = sizeof(OSType);

    //err = AudioDeviceSetProperty (deviceInfo, 0, 0, true, kAudioDevicePropertyDataSource, dataSize, 1 );

    dataSize = sizeof(UInt32);
    
    err = AudioDeviceSetProperty( deviceInfo, 0, channel, true, kAudioDevicePropertyPlayThru, dataSize, &playThruOn);

    
#endif    
    return (err);
} 


// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
UInt32	GetTotalNumberChannels(const AudioBufferList *inBufferList)
{
	UInt32 theAnswer = 0;
	UInt32 theIndex;
    
	for(theIndex = 0; theIndex < inBufferList->mNumberBuffers; ++theIndex)
		theAnswer += inBufferList->mBuffers[theIndex].mNumberChannels;
	
	return theAnswer;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Boolean	GetBufferForChannel(const AudioBufferList *inBufferList, UInt32 inChannel, UInt32 *outBufferNumber, UInt32 *outBufferChannel)
{
	Boolean theAnswer = false;
	UInt32 theIndex = 0;
	
	while((theIndex < inBufferList->mNumberBuffers) && (inChannel >= inBufferList->mBuffers[theIndex].mNumberChannels))
	{
		++theIndex;
		inChannel -= inBufferList->mBuffers[theIndex].mNumberChannels;
	}
	
	if(theIndex < inBufferList->mNumberBuffers)
	{
		*outBufferNumber = theIndex;
		*outBufferChannel = inChannel;
		theAnswer = true;
	}
	
	return theAnswer;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void	BlitThisData(const AudioBufferList  *inBufferList, AudioBufferList *outBufferList, UInt32 inStartingOutputChannel)
{
	UInt32 theInputChannel = 0;
	UInt32 theNumberInputChannels = GetTotalNumberChannels(inBufferList);
	UInt32 theOutputChannel = inStartingOutputChannel;
	UInt32 theNumberOutputChannels = GetTotalNumberChannels(outBufferList);
	
#if 0
{
    // interesting  check to see if dropouts in playback are actually from a slow blitter
    // when using this case to play stereo data to a device that has stream one set to 2 channels
    // this will then just move the whoel block of data in one step
    
    memcpy(outBufferList->mBuffers[0].mData, inBufferList->mBuffers[0].mData, outBufferList->mBuffers[0].mDataByteSize);
    return;
}
#endif

	while((theInputChannel < theNumberInputChannels) && (theOutputChannel < theNumberOutputChannels))
	{
		UInt32 theInputBufferIndex;
		UInt32 theInputBufferChannel;
        UInt32 theInputBufferFrameSize;
        Float32* theInputBuffer;
		UInt32 theOutputBufferIndex;
		UInt32 theOutputBufferChannel;
        UInt32 theOutputBufferFrameSize;
        Float32* theOutputBuffer;
        UInt32 theInputBufferFrame;        
        UInt32 theOutputBufferFrame;
         
		GetBufferForChannel(inBufferList, theInputChannel, &theInputBufferIndex, &theInputBufferChannel);
		theInputBufferFrameSize = inBufferList->mBuffers[theInputBufferIndex].mDataByteSize / ((inBufferList->mBuffers[theInputBufferIndex].mNumberChannels) * sizeof(Float32));
		theInputBuffer = (Float32*)(inBufferList->mBuffers[theInputBufferIndex].mData);
		
		GetBufferForChannel(outBufferList, theOutputChannel, &theOutputBufferIndex, &theOutputBufferChannel);
		theOutputBufferFrameSize = outBufferList->mBuffers[theOutputBufferIndex].mDataByteSize / ((outBufferList->mBuffers[theOutputBufferIndex].mNumberChannels) * sizeof(Float32));
		theOutputBuffer = (Float32*)(outBufferList->mBuffers[theOutputBufferIndex].mData);
		
		theInputBufferFrame = theInputBufferChannel;
		theOutputBufferFrame = theOutputBufferChannel;
        
        {
            SInt32 numFramesToCopy = (theInputBufferFrameSize < theOutputBufferFrameSize) ? theInputBufferFrameSize : theOutputBufferFrameSize;
            Float32* src = &theInputBuffer[theInputBufferFrame];
            Float32* dest = &theOutputBuffer[theOutputBufferFrame];
            int nInputChannels = inBufferList->mBuffers[theInputBufferIndex].mNumberChannels;
            int nOutputChannels = outBufferList->mBuffers[theOutputBufferIndex].mNumberChannels;
            
            while (--numFramesToCopy >= 0) {
                *dest = *src;
                src += nInputChannels;
                dest += nOutputChannels;
            }
        }
        
#if 0 // old way
		while((theFrameIndex < theInputBufferFrameSize) && (theFrameIndex < theOutputBufferFrameSize))
		{
			theOutputBuffer[theOutputBufferFrame] = theInputBuffer[theInputBufferFrame];
			++theFrameIndex;
			theInputBufferFrame += inBufferList->mBuffers[theInputBufferIndex].mNumberChannels;
			theOutputBufferFrame += outBufferList->mBuffers[theOutputBufferIndex].mNumberChannels;
		}
#endif
		
		++theInputChannel;
		++theOutputChannel;
	}
}
