/*
 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 "buffer.h"

void Convert16BitIntegerTo32Float(const void *in16BitDataPtr, 
				  Ptr out32BitDataPtr, 
				  UInt32 totalBytes)
{
  UInt32 samples = totalBytes / 2 /*each 16 bit sample is 2 bytes*/;
  UInt32 count;
  SInt16 *inDataPtr = (SInt16 *) in16BitDataPtr;
  Float32 *outDataPtr = (Float32 *) out32BitDataPtr;
    
  for (count = 0; count < samples; count++)
    {
      *outDataPtr = (Float32) *inDataPtr;
      if (*outDataPtr > 0)
	*outDataPtr /= 32767.0;
      else
	*outDataPtr /= 32768.0;
      
      outDataPtr++;
      inDataPtr++;
    }
}

void Convert8BitIntegerTo32Float(const void *in16BitDataPtr, 
				  Ptr out32BitDataPtr, 
				  UInt32 totalBytes)
{
  UInt32 samples = totalBytes/2;
  UInt32 count;
  SInt8 *inDataPtr = (SInt8 *) in16BitDataPtr;
  Float32 *outDataPtr = (Float32 *) out32BitDataPtr;
    
  for (count = 0; count < samples; count++)
    {
      *outDataPtr = (Float32) *inDataPtr;
      if (*outDataPtr > 0)
	*outDataPtr /= 32767.0;
      else
	*outDataPtr /= 32768.0;
      
      outDataPtr++;
      inDataPtr++;
    }
}

@implementation BufferStack

- initWithSize:(int)ssize bufferSize:(int)bsize
{
  size = ssize;
  buffer_size = bsize;
  stack = malloc(sizeof(BufferEntry *)*size);
  stack_length = 0;

#ifdef USE_PTHREADS
  {
    int rc;

    rc = pthread_mutex_init(&mutex, NULL);
    if (rc) 
      {
	fprintf(stderr, "ao_macosx_open: pthread_mutex_init returned %d\n", rc);
      }
    
    rc = pthread_cond_init(&condition, NULL);
    if (rc) 
      {
	fprintf(stderr, "ao_macosx_open: pthread_mutex_init returned %d\n", rc);
      }
  }
#else
  clock = [[NSConditionLock alloc] init];
#endif

  return [super init];
}

- (void)dealloc
{
  int i;
  for ( i = 0; i < stack_length; i++ )
    {
      free(stack[i]->buffer);
    }
  free(stack);
  [super dealloc];
}

- (void)lock:(NSString *)from
{
#ifdef DEBUG_LOCKS
  NSLog(@"lock: %@", from);
#endif
#ifdef USE_PTHREADS
  pthread_mutex_lock(&mutex);
#else
  [clock lock];
#endif
}

- (void)poplock
{
#ifdef DEBUG_LOCKS
  NSLog(@"lock: pop");
#endif
#ifdef USE_PTHREADS
  pthread_mutex_lock(&mutex);
#else
  [clock lock];
#endif
}

- (void)unlock:(NSString *)from
{
#ifdef DEBUG_LOCKS
  NSLog(@"unlock: %@", from);
#endif
#ifdef USE_PTHREADS
  pthread_mutex_unlock(&mutex);
#else
  [clock unlock];
#endif
}

- (void)popunlock
{
#ifdef DEBUG_LOCKS
  NSLog(@"unlock: pop");
#endif
#ifdef USE_PTHREADS
  pthread_mutex_unlock(&mutex);
#else
  [clock unlockWithCondition:1];
#endif
}

- (void)signal
{
#ifdef USE_PTHREADS
#ifdef DEBUG_LOCKS
  NSLog(@"signal");
#endif
  pthread_cond_signal(&condition);
#endif
}

- (void)wait
{
#ifdef DEBUG_LOCKS
  NSLog(@"wait");
#endif
#ifdef USE_PTHREADS
  pthread_cond_wait(&condition, &mutex);
#else
  [clock lockWhenCondition:1];
#endif
#ifdef DEBUG_LOCKS
  NSLog(@"back from wait");
#endif
}

- (void)stop
{
  stopped = YES;
  [self lock:@"stop"];
  [self signal];
  [self unlock:@"stop"];
}

- (void)flush
{
  int i;

  [self lock:@"flush"];

  for ( i = 0; i < stack_length; i++ )
    {
      free(stack[i]->buffer);
      stack[i]->len = 0;
    }

  stack_length = 0;
  [self unlock:@"flush"];
}

- (void)cancelWrite
{
  [self lock:@"cancelWrite"];
  stopped = YES;
  [self signal];
  stopped = NO;
  [self unlock:@"cancelWrite"];
}

- (void *)pop
{
  void *res = 0;
  [self poplock];
  if ( stack_length > 0 
       && (stack[0]->len == buffer_size || waitingForFinish))
    {
      res = stack[0]->buffer;
      if (stack[0]->len < buffer_size)
	memset(res+stack[0]->len, 0, buffer_size-stack[0]->len);
      memmove(stack, stack+1, (size-1)*sizeof(BufferEntry *));
      stack_length--;
#ifdef DEBUG_STACK
      NSLog(@"popped some data");
#endif
    }
#ifdef DEBUG_STACK
  else
    {
      NSLog(@"popped nothing");
    }
#endif
  [self signal];
  [self popunlock];
  return res;
}

- (BOOL)isFull
{
  return stack_length < size ? NO : YES;
}

- (void)push:(const void *)buffer length:(int)len
{
#ifdef DEBUG_STACK
  NSLog(@"push: %d", len);
#endif
  
  while ( len > 0 )
    {
      int copy;
      void *dst;
      BufferEntry *entry;

      [self lock:@"push"];

      if ( [self isFull] == YES )
	{
	  [self wait];

	  if ( stopped )
	    {
	      [self unlock:@"push"];
	      return;
	    }
	}

      if ( stack_length > 0 && stack[stack_length-1]->len != buffer_size)
	{
	  int in_buffer, left;
	  entry = stack[stack_length-1];
	  in_buffer = entry->len;
	  left = buffer_size - in_buffer;
	  dst = entry->buffer + in_buffer;
	  copy = (len*2) < left ? len : left/2;
	}
      else
	{
	  stack[stack_length] = malloc(sizeof(BufferEntry));
	  entry = stack[stack_length];
	  entry->buffer = malloc(buffer_size);
	  entry->len = 0;
	  stack_length++;
	  dst = entry->buffer;
	  copy = (len*2) < buffer_size ? len : (buffer_size/2);
	}
      
      entry->len += (copy*2);

      Convert16BitIntegerTo32Float(buffer, dst, copy);
      //Convert8BitIntegerTo32Float(buffer, dst, copy);
      //memcpy( dst, buffer, copy );
      
      buffer += copy;
      len -= copy;
      [self unlock:@"push"];
    }
}

- (void)waitTillDone
{
  waitingForFinish = YES;
  while ( stack_length > 0 )
    {
      [self lock:@"waitTillDone"];
      [self wait];
      [self unlock:@"waitTillDone"];
    }
  waitingForFinish = NO;
}

- (int)length
{
  return stack_length;
}

@end
