/*
 * SDL_sound -- An abstract sound format decoding API.
 * Copyright (C) 2001  Ryan C. Gordon.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */

/*
 * QuickTime decoder for sound formats that QuickTime supports.
 * April 28, 2002
 *
 * This driver handles .mov files with a sound track. In
 * theory, it could handle any format that QuickTime supports.
 * In practice, it may only handle a select few of these formats.
 *
 * It seems able to play back AIFF and other standard Mac formats.
 * Rewinding is not supported yet.
 *
 * The routine QT_create_data_ref() needs to be
 * tweaked to support different media types. 
 * This code was originally written to get MP3 support,
 * as it turns out, this isn't possible using this method.
 *
 * The only way to get streaming MP3 support through QuickTime,
 * and hence support for SDL_RWops, is to write
 * a DataHandler component, which suddenly gets much more difficult :-(
 *
 *  This file was written by Darrell Walisser (walisser@mac.com)
 *  Portions have been borrowed from the "MP3Player" sample code,
 *  courtesy of Apple.
 */

#if HAVE_CONFIG_H
#  include <config.h>
#endif

#ifdef SOUND_SUPPORTS_QUICKTIME
#ifdef macintosh
typedef long int32_t;
#  define OPAQUE_UPP_TYPES 0
#  include <QuickTime.h>
#else
#  include <QuickTime/QuickTime.h>
#  include <Carbon/Carbon.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#include "SDL_sound.h"

#define __SDL_SOUND_INTERNAL__
#include "SDL_sound_internal.h"

static int QT_init(void);
static void QT_quit(void);
static int QT_open(Sound_Sample *sample, const char *ext);
static void QT_close(Sound_Sample *sample);
static Uint32 QT_read(Sound_Sample *sample);
static int QT_rewind(Sound_Sample *sample);
static int QT_seek(Sound_Sample *sample, Uint32 ms);

#define QT_MAX_INPUT_BUFFER (32*1024) /* Maximum size of internal buffer (internal->buffer_size) */

static const char *extensions_quicktime[] = { "mov", NULL };
const Sound_DecoderFunctions __Sound_DecoderFunctions_QuickTime =
  {
    {
      extensions_quicktime,
      "QuickTime format",
      "Darrell Walisser <dwaliss1@purdue.edu>",
      "http://www.icculus.org/SDL_sound/"
    },
    
    QT_init,       /* init() method   */
    QT_quit,       /* quit() method   */
    QT_open,       /* open() method   */
    QT_close,      /* close() method  */
    QT_read,       /* read() method   */
    QT_rewind,     /* rewind() method */
    QT_seek        /* seek() method   */
  };

typedef struct {

  ExtendedSoundComponentData	compData;
  Handle                        hSource;           /* source media buffer */
  Media                         sourceMedia;       /* sound media identifier */
  TimeValue                     getMediaAtThisTime;
  TimeValue                     sourceDuration;
  Boolean                       isThereMoreSource;
  UInt32                        maxBufferSize;

} SCFillBufferData, *SCFillBufferDataPtr;

typedef struct {

  Movie              movie;
  Track              track;
  Media              media;
  AudioFormatAtomPtr atom;
  SoundComponentData source_format;
  SoundComponentData dest_format;
  SoundConverter     converter;
  SCFillBufferData   buffer_data;
  SoundConverterFillBufferDataUPP fill_buffer_proc;

} qt_t;




/*
 * This procedure creates a description of the raw data
 * read from SDL_RWops so that QuickTime can identify
 * the codec it needs to use to decompress it.
 */
static Handle QT_create_data_ref (const char *file_extension) {

  Handle    tmp_handle, data_ref;
  StringPtr file_name = "\p"; /* empty since we don't know the file name! */
  OSType    file_type;
  StringPtr mime_type;
  long      atoms[3];

/*
  if (__Sound_strcasecmp (file_extension, "mp3")==0) {
    file_type = 'MPEG';
    mime_type = "\pvideo/mpeg";
  }
  else {
  
    return NULL;
  }
*/

  if (__Sound_strcasecmp (file_extension, "mov") == 0) {
  
  	file_type = 'MooV';
  	mime_type = "\pvideo/quicktime";
  }
  else {
  
  	return NULL;
  }

  tmp_handle = NewHandle(0);
  assert (tmp_handle != NULL);
  assert (noErr == PtrToHand (&tmp_handle, &data_ref, sizeof(Handle)));
  assert (noErr == PtrAndHand (file_name, data_ref, file_name[0]+1));
  
  atoms[0] = EndianU32_NtoB (sizeof(long) * 3);
  atoms[1] = EndianU32_NtoB (kDataRefExtensionMacOSFileType);
  atoms[2] = EndianU32_NtoB (file_type);

  assert (noErr == PtrAndHand (atoms, data_ref, sizeof(long)*3));

  atoms[0] = EndianU32_NtoB (sizeof(long)*2 + mime_type[0]+1);
  atoms[1] = EndianU32_NtoB (kDataRefExtensionMIMEType);
  
  assert (noErr == PtrAndHand (atoms, data_ref, sizeof(long)*2));
  assert (noErr == PtrAndHand (mime_type, data_ref, mime_type[0]+1));

  return data_ref;
}

/*
 * This procedure is a hook for QuickTime to grab data from the
 * SDL_RWOps data structure when it needs it
 */
static pascal OSErr QT_get_movie_data_proc (long offset, long size, 
                                            void *data, void *user_data)
{
  SDL_RWops* rw = (SDL_RWops*)user_data;
  OSErr error;
	
  if (offset == SDL_RWseek (rw, offset, SEEK_SET)) {
	  
    if (size == SDL_RWread (rw, data, 1, size)) {
      error = noErr;
    }
    else {
      error = notEnoughDataErr;
    }
  }
  else {
    error = fileOffsetTooBigErr;
  }
  
  return (error);
}

/* * ----------------------------
 * SoundConverterFillBufferDataProc
 *
 * the callback routine that provides the source data for conversion -
 * it provides data by setting outData to a pointer to a properly
 * filled out ExtendedSoundComponentData structure
 */
static pascal Boolean QT_sound_converter_fill_buffer_data_proc (SoundComponentDataPtr *outData, void *inRefCon)
{
  SCFillBufferDataPtr pFillData = (SCFillBufferDataPtr)inRefCon;
	
  OSErr err = noErr;
							
  /* if after getting the last chunk of data the total time is over
   * the duration, we're done
   */
  if (pFillData->getMediaAtThisTime >= pFillData->sourceDuration) {
    pFillData->isThereMoreSource = false;
    pFillData->compData.desc.buffer = NULL;
    pFillData->compData.desc.sampleCount = 0;
    pFillData->compData.bufferSize = 0;
  }
	
  if (pFillData->isThereMoreSource) {
	
    long		sourceBytesReturned;
    long		numberOfSamples;
    TimeValue	sourceReturnedTime, durationPerSample;
							
    HUnlock(pFillData->hSource);

    err = GetMediaSample
      (pFillData->sourceMedia,/* specifies the media for this operation */
       pFillData->hSource,    /* function returns the sample data into this handle */
       pFillData->maxBufferSize, /* maximum number of bytes of sample data to be returned */
       &sourceBytesReturned,  /* the number of bytes of sample data returned */
       pFillData->getMediaAtThisTime,/* starting time of the sample to
					be retrieved (must be in
					Media's TimeScale) */
       &sourceReturnedTime,/* indicates the actual time of the returned sample data */
       &durationPerSample, /* duration of each sample in the media */
       NULL, /* sample description corresponding to the returned sample data (NULL to ignore) */
       NULL, /* index value to the sample description that corresponds
		to the returned sample data (NULL to ignore) */
       0, /* maximum number of samples to be returned (0 to use a
	     value that is appropriate for the media) */
       &numberOfSamples, /* number of samples it actually returned */
       NULL);		 /* flags that describe the sample (NULL to ignore) */
							 
    HLock(pFillData->hSource);

    if ((noErr != err) || (sourceBytesReturned == 0)) {
      pFillData->isThereMoreSource = false;
      pFillData->compData.desc.buffer = NULL;
      pFillData->compData.desc.sampleCount = 0;		
      
      if ((err != noErr) && (sourceBytesReturned > 0))
	DebugStr("\pGetMediaSample - Failed in FillBufferDataProc");
    }
    
    pFillData->getMediaAtThisTime = sourceReturnedTime + (durationPerSample * numberOfSamples);
    pFillData->compData.bufferSize = sourceBytesReturned; 
  }
  
  /* set outData to a properly filled out ExtendedSoundComponentData struct */
  *outData = (SoundComponentDataPtr)&pFillData->compData;
	
  return (pFillData->isThereMoreSource);
}
    

static int QT_init_internal () {

  OSErr error;
	
  error = EnterMovies(); /* initialize the movie toolbox */
	
  return (error == noErr);
}

static void QT_quit_internal () {

  ExitMovies();
}

static qt_t* QT_open_internal (Sound_Sample *sample, const char *extension)
{
  Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
	
  qt_t              *instance;
  OSErr              error;
  Movie              movie;
  Track              sound_track;
  Media              sound_track_media;
  AudioFormatAtomPtr source_sound_decomp_atom;
		
  SoundDescriptionV1Handle source_sound_description;
  Handle source_sound_description_extension;
  Size   source_sound_description_extension_size;
  Handle data_ref;

  data_ref = QT_create_data_ref (extension);

  /* create a movie that will read data using SDL_RWops */
  error = NewMovieFromUserProc 
    (&movie, 
     0, 
     NULL, 
     NewGetMovieUPP(QT_get_movie_data_proc),
     (void*) internal->rw,
     data_ref,
     'hndl');
	                      
  if (error != noErr) {
	
    return NULL;
  }
	
  /* get the first sound track of the movie; other tracks will be ignored */
  sound_track = GetMovieIndTrackType (movie, 1, SoundMediaType, movieTrackMediaType);
  if (sound_track == NULL) {
	
    /* movie needs a sound track! */
		
    return NULL;
  }

  /* get and return the sound track media */
  sound_track_media = GetTrackMedia (sound_track);
  if (sound_track_media == NULL) {
	
    return NULL;
  }
	
  /* create a description of the source sound so we can convert it later */
  source_sound_description = (SoundDescriptionV1Handle)NewHandle(0);
  assert (source_sound_description != NULL); /* out of memory */
	
  GetMediaSampleDescription (sound_track_media, 1, 
			     (SampleDescriptionHandle)source_sound_description);
  error = GetMoviesError();
  if (error != noErr) {
	
    return NULL;
  }
	
  source_sound_description_extension = NewHandle(0);
  assert (source_sound_description_extension != NULL); /* out of memory */
	
  error = GetSoundDescriptionExtension ((SoundDescriptionHandle) source_sound_description, 
					&source_sound_description_extension, 
					siDecompressionParams);
	
  if (error == noErr) {
	
    /* copy extension to atom format description if we have an extension */

    source_sound_description_extension_size = 
      GetHandleSize (source_sound_description_extension);
    HLock (source_sound_description_extension);

    source_sound_decomp_atom = (AudioFormatAtom*) 
      NewPtr (source_sound_description_extension_size);
    assert (source_sound_decomp_atom != NULL); /* out of memory */

    BlockMoveData (*source_sound_description_extension, 
		   source_sound_decomp_atom,
		   source_sound_description_extension_size);
		               
    HUnlock (source_sound_description_extension);
  }
	
  else {
	
    source_sound_decomp_atom = NULL;
  }

  instance = (qt_t*) malloc (sizeof(*instance));
  assert (instance != NULL); /* out of memory */
		
  instance->movie = movie;
  instance->track = sound_track;
  instance->media = sound_track_media;
  instance->atom  = source_sound_decomp_atom;
	
  instance->source_format.flags = 0;
  instance->source_format.format = (*source_sound_description)->desc.dataFormat;
  instance->source_format.numChannels = (*source_sound_description)->desc.numChannels;
  instance->source_format.sampleSize = (*source_sound_description)->desc.sampleSize;
  instance->source_format.sampleRate = (*source_sound_description)->desc.sampleRate;
  instance->source_format.sampleCount = 0;
  instance->source_format.buffer = NULL;
  instance->source_format.reserved = 0;
	
  instance->dest_format.flags = 0;
  instance->dest_format.format = kSoundNotCompressed;
  instance->dest_format.numChannels = (*source_sound_description)->desc.numChannels;
  instance->dest_format.sampleSize = (*source_sound_description)->desc.sampleSize;
  instance->dest_format.sampleRate = (*source_sound_description)->desc.sampleRate;
  instance->dest_format.sampleCount = 0;
  instance->dest_format.buffer = NULL;
  instance->dest_format.reserved = 0;
	
  sample->actual.channels = (*source_sound_description)->desc.numChannels;
  sample->actual.rate = (*source_sound_description)->desc.sampleRate >> 16;
	
  if ((*source_sound_description)->desc.sampleSize == 16) {
	
    sample->actual.format = AUDIO_S16SYS;
  }
  else if ((*source_sound_description)->desc.sampleSize == 8) {
	
    sample->actual.format = AUDIO_U8;
  }
  else {
	
    /* 24-bit or others... (which SDL can't handle) */
    return NULL;
  }
	
  DisposeHandle (source_sound_description_extension);
  DisposeHandle ((Handle)source_sound_description);
	
  /* This next code sets up the SoundConverter component */
  error = SoundConverterOpen (&instance->source_format, &instance->dest_format,
			      &instance->converter);
	
  if (error != noErr) {
	
    return NULL;
  }
	
  error = SoundConverterSetInfo (instance->converter, siDecompressionParams, 
				 instance->atom);
  if (error == siUnknownInfoType) {
		
    /* ignore */	
  } 
  else if (error != noErr) {
	
    /* reall error */
    return NULL;
  }

  error = SoundConverterBeginConversion (instance->converter);
  if (error != noErr) {
    
    return NULL;
  }
		
  instance->buffer_data.sourceMedia = instance->media;
  instance->buffer_data.getMediaAtThisTime = 0;		
  instance->buffer_data.sourceDuration = GetMediaDuration(instance->media);
  instance->buffer_data.isThereMoreSource = true;
  instance->buffer_data.maxBufferSize = QT_MAX_INPUT_BUFFER;
  /* allocate source media buffer */
  instance->buffer_data.hSource = NewHandle((long)instance->buffer_data.maxBufferSize); 
  assert (instance->buffer_data.hSource != NULL); /* out of memory */

  instance->buffer_data.compData.desc = instance->source_format;
  instance->buffer_data.compData.desc.buffer = (Byte *)*instance->buffer_data.hSource;
  instance->buffer_data.compData.desc.flags = kExtendedSoundData;
  instance->buffer_data.compData.recordSize = sizeof(ExtendedSoundComponentData);
  instance->buffer_data.compData.extendedFlags = 
    kExtendedSoundSampleCountNotValid | kExtendedSoundBufferSizeValid;
  instance->buffer_data.compData.bufferSize = 0;
	
  instance->fill_buffer_proc = 
    NewSoundConverterFillBufferDataUPP (QT_sound_converter_fill_buffer_data_proc);
	
  return (instance);

} /* QT_open_internal */

static void QT_close_internal (qt_t *instance)
{

} /* QT_close_internal */

static Uint32 QT_read_internal(Sound_Sample *sample)
{
  Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
  qt_t *instance = (qt_t*) internal->decoder_private;
  long output_bytes, output_frames, output_flags;
  OSErr error;
		
  error = SoundConverterFillBuffer 
    (instance->converter,	     /* a sound converter */
     instance->fill_buffer_proc,     /* the callback UPP */
     &instance->buffer_data,	     /* refCon passed to FillDataProc */
     internal->buffer,		     /* the decompressed data 'play' buffer */
     internal->buffer_size,          /* size of the 'play' buffer */
     &output_bytes,	             /* number of output bytes */
     &output_frames,                 /* number of output frames */
     &output_flags);                 /* fillbuffer retured advisor flags */
    
  if (output_flags & kSoundConverterHasLeftOverData) {
    
    sample->flags |= SOUND_SAMPLEFLAG_EAGAIN;
  }
  else {
    
    sample->flags |= SOUND_SAMPLEFLAG_EOF;
  }
    
  if (error != noErr) {
    
    sample->flags |= SOUND_SAMPLEFLAG_ERROR;
  }
    
  return (output_bytes);

} /* QT_read_internal */

static int QT_rewind_internal (Sound_Sample *sample)
{

  return 0;

} /* QT_rewind_internal */



static int QT_init(void)
{
  return (QT_init_internal());

} /* QT_init */

static void QT_quit(void)
{
  QT_quit_internal();

} /* QT_quit */

static int QT_open(Sound_Sample *sample, const char *ext)
{
  Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
  qt_t *instance;

  instance = QT_open_internal(sample, ext);
  internal->decoder_private = (void*)instance;

  return(instance != NULL);
    
} /* QT_open */


static void QT_close(Sound_Sample *sample)
{
  Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
  qt_t *instance = (qt_t *) internal->decoder_private;

  QT_close_internal (instance);

  free (instance);

} /* QT_close */


static Uint32 QT_read(Sound_Sample *sample)
{    
  return(QT_read_internal(sample));

} /* QT_read */


static int QT_rewind(Sound_Sample *sample)
{
    
  return(QT_rewind_internal(sample));
    
} /* QT_rewind */


static int QT_seek(Sound_Sample *sample, Uint32 ms)
{
    BAIL_MACRO("QUICKTIME: Seeking not implemented", 0);
} /* QT_seek */


#endif /* SOUND_SUPPORTS_QUICKTIME */

/* end of quicktime.c ... */

