/*
 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
 */

#import "xtvaudio.h"

@implementation Buffer

static int block_number = 0;

+(Buffer*) initWithData:(char*) _data size:(int) _size
{
    Buffer * b = [ Buffer alloc];

    b->m_data = malloc( _size );
    b->m_size = _size;
    b->m_nr   = block_number++;
    
    memcpy( b->m_data, _data, _size );

    return b;
}

+(Buffer*) initWithBlock:(char*) _data size:(int) _size
{
    Buffer * b = [ Buffer alloc];

    b->m_data = _data;
    b->m_size = _size;
    b->m_nr   = block_number++;
    
    return b;
}

- (void)dealloc
{
  //free(m_data);
  [super dealloc];
}

-(char*) data
{
    return m_data;
}

-(int) size
{
    return m_size;
}

-(int) nr
{
    return m_nr;
}

@end


@implementation BufferList

- (id)init
{
    int rc;
    
    [ super init];
    
    mBuffers = [ List alloc ];
    
    rc = pthread_mutex_init( & mMutex, NULL);
        
    if (rc) {
        fprintf(stderr, "BufferList: pthread_mutex_init returned %d\n", rc);
    }
    
    rc = pthread_cond_init( &mCondition, NULL);
    
    if (rc) {
        fprintf(stderr, "BufferList: pthread_mutex_init returned %d\n", rc);
    }

    return self;
}

- (void) dealloc
{
    pthread_mutex_destroy( & mMutex );
    pthread_cond_destroy ( & mCondition );
}

-(int) buffers
{
    unsigned backlog;

    pthread_mutex_lock( &mMutex );
        
    backlog = mBuffers->numElements;
        
    pthread_mutex_unlock( &mMutex );

    return backlog;
}

- (void) discard
{
    Buffer * b = NULL;
    
    pthread_mutex_lock( &mMutex );

    while( mBuffers->numElements && b != NULL ) {
        
        b = (Buffer*) [ self->mBuffers objectAt: 0 ];
            
        if( b ) {
            [ b release ];
        }
    }        
    
    pthread_mutex_unlock( &mMutex );
}

- (void) write:(void*)data size:(int) size
{
    Buffer * buf = [ Buffer initWithBlock: data size:size ];

    pthread_mutex_lock( &mMutex );

    //puts("audio put buffer");

    [ mBuffers addObject: buf ];

    pthread_mutex_unlock( &mMutex );

    pthread_cond_signal( &mCondition );
}

-(Buffer*) read
{
    Buffer * buffer = 0;
    
    pthread_mutex_lock( &mMutex );

#if 0
    while( mBuffers->numElements <= 0 ) {
    
        pthread_cond_wait( &mCondition, &mMutex);
    }
#endif

    if( mBuffers->numElements > 0 ) {
    
        //puts("audio get buffer");
    
        buffer = (Buffer*) [ self->mBuffers objectAt: 0 ];

        if( buffer ) {

            [ mBuffers removeObjectAt: 0 ];

        }
    }
    
    pthread_mutex_unlock( &mMutex );

    return buffer;
}

@end

static int GetAudioDevices( Ptr * devices, short * devicesAvailable )
{
    OSStatus	err = noErr;
    UInt32 		outSize;
    Boolean		outWritable;
    AudioDeviceID	devIDs[4];
    
    // 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 )
      {
	NSLog(@"No devices");
	return 0;
      }
    
    // make space for the devices we are about to get
    *devices = malloc(outSize);			
    memset( *devices, 0, outSize );			
    // get an array of AudioDeviceIDs
    err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &outSize, (void *) &devIDs[0]);	
    err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &outSize, (void *) *devices);	

    if (err != noErr )
      return 0;

    return 1;
}

static OSStatus  outputIOProc( 	AudioDeviceID inDevice, 
                                const AudioTimeStamp *inNow, 
                                const AudioBufferList *inInputData, 
                                const AudioTimeStamp *inInputTime, 
                                AudioBufferList *outOutputData, 
                                const AudioTimeStamp *inOutputTime, 
                                void *_self)
{
    //puts("outputIOProc");

    if( outOutputData && outOutputData->mNumberBuffers ) {

        xtvaudio * self = (xtvaudio*) _self;

        Buffer * buffer = [ self read ];
        
        if( buffer ) {

            BlockMoveData( [ buffer data ], outOutputData->mBuffers[0].mData, [ buffer size ] );

            [ buffer release ];
        }
        else {
            memset(outOutputData->mBuffers[0].mData, 0, [ self bufferSize ] );
        }
    }
    
    return noErr;
}

static OSStatus inputIOProc (	AudioDeviceID inDevice, const AudioTimeStamp *inNow, 
                                const AudioBufferList *inInputData,   const AudioTimeStamp *inInputTime, 
                                      AudioBufferList *outOutputData, const AudioTimeStamp *inOutputTime, 
                                void *_self )
{
    //puts("inputIOProc");

    if( inInputData && inInputData->mNumberBuffers ) {

        unsigned i;
        
        xtvaudio * self = (xtvaudio*) _self;

        for( i = 0; i <  inInputData->mNumberBuffers; i++ ) {

            [ self  write:inInputData->mBuffers[i].mData size:inInputData->mBuffers[i].mDataByteSize ];
        }
    }

    return noErr;
}

@implementation xtvaudio

+ (xtvaudio*) new
{
    xtvaudio * audio = [ xtvaudio alloc ];

    return [ audio init ];
}

- (id)init
{    
    [ super init];
    
    mBuffers = [ BufferList alloc ];
    
    [ mBuffers init];
    
    return self;
}

-(int) bufferSize
{
    return mOutputBufferSize;
}

-(int) buffers
{
    return [ mBuffers buffers ];
}
        
- (void) write:(void*)data size:(int) size
{
    return [ mBuffers write:data size:size ];
}

-(Buffer*) read
{
    return [ mBuffers read ];
}

-(void) start
{
    AudioDeviceStart( mOutput, outputIOProc );
    AudioDeviceStart( mInput,  inputIOProc  );
}

-(void) stop
{
    AudioDeviceStop( mOutput, outputIOProc );
    AudioDeviceStop( mInput,  inputIOProc  );
}

- (int) getAudioDevices:(Ptr*) devices devicesAvailable:(short*) devicesAvailable
{
    return GetAudioDevices( devices, devicesAvailable );
}

- (BOOL) open:(AudioDeviceID) dev rate:(int)rate numChannels:(int)nch  isOut:(BOOL) isOut
{
  OSErr err;
  UInt32 outSize;
  UInt32 isAlive;

  if( isOut ) {
    mOutput = dev;
  }
  else {
    mInput = dev;
  }
  
  outSize = sizeof(mOutDeviceFormat);

  err = AudioDeviceGetProperty( dev,
                                0, 
                                false, 
                                kAudioDevicePropertyStreamFormat, 
                                &outSize, 
                                &mOutDeviceFormat);

  //NSLog( @"AudioDeviceGetProperty = %d", err );

  isAlive = 0;

  outSize = sizeof(UInt32);

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

  //NSLog( @"AudioDeviceGetProperty = %d", err );
  
  if ( isAlive == 0 )
    {
      //NSLog(@"audio device is not alive");
    }

    outSize = sizeof( mOutDeviceFormat );

    err = AudioDeviceGetProperty( dev, 0, false,  kAudioDevicePropertyStreamFormatMatch,
                                  &outSize, &mOutDeviceFormat );

    mOutputBufferSize = OUTPUT_BUFFER_SIZE;

    err = AudioDeviceSetProperty( dev, 
                                  0, 
			          0,
			          false,
			          kAudioDevicePropertyBufferSize,
			          sizeof(UInt32),
			         &mOutputBufferSize );

    outSize = sizeof( mOutputBufferSize );
  
    err = AudioDeviceGetProperty( dev, 0, false, kAudioDevicePropertyBufferSize, &outSize, &mOutputBufferSize);

    //fprintf(stderr, "deviceBufferSize = %ld\n", outputBufferSize);

    if ( err != noErr )  {
        NSLog(@"Can't set bufferSize");
        return NO;
    }

    if( isOut ) {
        err = AudioDeviceAddIOProc( dev, outputIOProc,    (void *)self);
    }
    else {
        err = AudioDeviceAddIOProc( dev, inputIOProc,    (void *)self);
    }
    
    return YES;
}

- (void)getVolume:(int *)l right:(int *)r
{
  Float32 left, right;
  UInt32 size;
  OSErr err;

  size = sizeof(left);
  err = AudioDeviceGetProperty( mOutput, 
			        1, 
			        false, 
			        kAudioDevicePropertyVolumeScalar, 
			        &size,
			        &left);

  err = AudioDeviceGetProperty( mOutput, 
			        2, 
			        false, 
			        kAudioDevicePropertyVolumeScalar, 
			        &size,
			        &right);

  *l = left * 100.0;
  *r = right * 100.0;
}

- (void)setVolume:(int )l right:(int)r
{
  Float32 left, right;
  UInt32 size;
  OSErr err;

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

  size = sizeof(left);
  
  err = AudioDeviceSetProperty( mOutput, 
			        0,
			        1, 
			        false, 
			        kAudioDevicePropertyVolumeScalar, 
			        size,
			        &left);

  err = AudioDeviceSetProperty( mOutput, 
			        0,
			        2, 
			        false, 
			        kAudioDevicePropertyVolumeScalar, 
			        size,
			        &right);
}



@end
