/*         ______   ___    ___ 
 *        /\  _  \ /\_ \  /\_ \ 
 *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___ 
 *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
 *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
 *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
 *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
 *                                           /\____/
 *                                           \_/__/
 *
 *      MacOS X Core Audio digital sound driver.
 *
 *      By Angelo Mottola.
 *
 *      See readme.txt for copyright information.
 */

#include "allegro.h"
#include "allegro/internal/aintern.h"
#include "allegro/platform/aintosx.h"

#ifndef ALLEGRO_MACOSX
#error something is wrong with the makefile
#endif


static int ca_detect(int);
static int ca_init(int, int);
static void ca_exit(int);
static int ca_buffer_size();
static int ca_mixer_volume(int);


static AUGraph graph;
static AudioUnit output_unit, converter_unit;
static int audio_buffer_size = 0;
static char ca_desc[512] = EMPTY_STRING;


DIGI_DRIVER digi_core_audio =
{
   DIGI_CORE_AUDIO,
   empty_string,
   empty_string,
   "CoreAudio",
   0,
   0,
   MIXER_MAX_SFX,
   MIXER_DEF_SFX,

   ca_detect,
   ca_init,
   ca_exit,
   ca_mixer_volume,

   NULL,
   NULL,
   ca_buffer_size,
   _mixer_init_voice,
   _mixer_release_voice,
   _mixer_start_voice,
   _mixer_stop_voice,
   _mixer_loop_voice,

   _mixer_get_position,
   _mixer_set_position,

   _mixer_get_volume,
   _mixer_set_volume,
   _mixer_ramp_volume,
   _mixer_stop_volume_ramp,

   _mixer_get_frequency,
   _mixer_set_frequency,
   _mixer_sweep_frequency,
   _mixer_stop_frequency_sweep,

   _mixer_get_pan,
   _mixer_set_pan,
   _mixer_sweep_pan,
   _mixer_stop_pan_sweep,

   _mixer_set_echo,
   _mixer_set_tremolo,
   _mixer_set_vibrato,
   0, 0,
   0,
   0,
   0,
   0,
   0,
   0
};



static OSStatus render_callback(void *inRefCon, AudioUnitRenderActionFlags *inActionFlags,
   const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumFrames, AudioBufferList *ioData)
{
   _mix_some_samples((unsigned long)ioData->mBuffers[0].mData, 0, TRUE);
   return 0;
}



static int ca_detect(int input)
{
   if (input) {
      ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Input is not supported"));
      return FALSE;
   }
   if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_1)
      return FALSE;
   return TRUE;
}



static int ca_init(int input, int voices)
{
   AudioDeviceID audio_device;
   ComponentDescription desc;
   AUNode output_node, converter_node;
   AURenderCallbackStruct render_cb;
   UInt32 property_size, buffer_size;
   AudioStreamBasicDescription input_format_desc, output_format_desc;
   char device_name[64], manufacturer[64];
   UInt32 device_name_size, manufacturer_size;
   char tmp1[256], tmp2[256], tmp3[256];

   if (input) {
      ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Input is not supported"));
      return -1;
   }
   if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_1) {
      ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("MacOS X.2 or newer required by this driver"));
      return -1;
   }
   
   NewAUGraph(&graph);

   desc.componentType = kAudioUnitType_FormatConverter;
   desc.componentSubType = kAudioUnitSubType_AUConverter;
   desc.componentManufacturer = kAudioUnitManufacturer_Apple;
   desc.componentFlags = 0;        
   desc.componentFlagsMask = 0;   
   AUGraphNewNode(graph, &desc, 0, NULL, &converter_node);
   
   desc.componentType = kAudioUnitType_Output;
   desc.componentSubType = kAudioUnitSubType_DefaultOutput;
   desc.componentManufacturer = kAudioUnitManufacturer_Apple;
   desc.componentFlags = 0;        
   desc.componentFlagsMask = 0;   
   AUGraphNewNode(graph, &desc, 0, NULL, &output_node);
   
   AUGraphOpen(graph);
   AUGraphInitialize(graph);
   
   AUGraphGetNodeInfo(graph, output_node, NULL, NULL, NULL, &output_unit);
   AUGraphGetNodeInfo(graph, converter_node, NULL, NULL, NULL, &converter_unit);

   property_size = sizeof(output_format_desc);
   if (AudioUnitGetProperty(output_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &output_format_desc, &property_size)) {
      ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Cannot detect output audio format"));
      ca_exit(FALSE);
      return -1;
   }
   
   input_format_desc.mSampleRate = output_format_desc.mSampleRate;
   input_format_desc.mFormatID = kAudioFormatLinearPCM;
   input_format_desc.mFormatFlags = kAudioFormatFlagIsBigEndian | kAudioFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
   input_format_desc.mBytesPerPacket = 4;
   input_format_desc.mFramesPerPacket = 1;
   input_format_desc.mBytesPerFrame = 4;
   input_format_desc.mChannelsPerFrame = 2;
   input_format_desc.mBitsPerChannel = 16;
   
   if (AudioUnitSetProperty(converter_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &output_format_desc, sizeof(output_format_desc)) ||
       AudioUnitSetProperty(converter_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &input_format_desc, sizeof(input_format_desc))) {
      ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Cannot configure format converter audio unit"));
      ca_exit(FALSE);
      return -1;
   }
   
   _sound_bits = 16;
   _sound_stereo = TRUE;
   _sound_freq = (int)output_format_desc.mSampleRate;
   
   AUGraphConnectNodeInput(graph, converter_node, 0, output_node, 0);
   
   render_cb.inputProc = render_callback;
   render_cb.inputProcRefCon = NULL;
   if (AudioUnitSetProperty(converter_unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &render_cb, sizeof(render_cb))) {
      ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Cannot set audio rendering callback"));
      ca_exit(FALSE);
      return -1;
   }

   property_size = sizeof(audio_device);
   if (AudioUnitGetProperty(output_unit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Output, 0, &audio_device, &property_size)) {
      ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Cannot get CoreAudio device"));
      ca_exit(FALSE);
      return -1;
   }
   
   property_size = sizeof(buffer_size);
   if (AudioDeviceGetProperty(audio_device, 0, false, kAudioDevicePropertyBufferSize, &property_size, &buffer_size)) {
      ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Cannot get CoreAudio device buffer size"));
      ca_exit(FALSE);
      return -1;
   }
   audio_buffer_size = buffer_size / sizeof(float) * sizeof(short);
   digi_core_audio.voices = voices;
   if (_mixer_init(audio_buffer_size / sizeof(short), _sound_freq, _sound_stereo, TRUE, &digi_core_audio.voices)) {
      ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Error initializing mixer"));
      ca_exit(FALSE);
      return -1;
   }
   
   AUGraphStart(graph);
   
   device_name_size = sizeof(device_name);
   manufacturer_size = sizeof(manufacturer);
   if (!AudioDeviceGetProperty(audio_device, 0, false, kAudioDevicePropertyDeviceName, &device_name_size, device_name) &&
       !AudioDeviceGetProperty(audio_device, 0, false, kAudioDevicePropertyDeviceManufacturer, &manufacturer_size, manufacturer)) {
      uszprintf(ca_desc, sizeof(ca_desc), get_config_text("%s (%s), 16 bits (%d real), %d bps, %s"),
         uconvert_ascii(device_name, tmp1), uconvert_ascii(manufacturer, tmp2), output_format_desc.mBitsPerChannel,
	 _sound_freq, uconvert_ascii(_sound_stereo ? "stereo" : "mono", tmp3));
   }
   else {
      uszprintf(ca_desc, sizeof(ca_desc), get_config_text("16 bits (%d real), %d bps, %s"), output_format_desc.mBitsPerChannel,
	 _sound_freq, uconvert_ascii(_sound_stereo ? "stereo" : "mono", tmp3));
   }
   digi_core_audio.desc = ca_desc;
   
   return 0;
}



static void ca_exit(int input)
{
   if (input)
      return;
   AUGraphStop(graph);
   AUGraphUninitialize(graph);
   AUGraphClose(graph);
   DisposeAUGraph(graph);
   _mixer_exit();
}



static int ca_buffer_size()
{
   return audio_buffer_size;
}



static int ca_mixer_volume(int volume)
{
   float value = (float)volume / 255.0;

   return AudioUnitSetParameter(converter_unit, kAudioUnitParameterUnit_LinearGain, kAudioUnitScope_Output, 0, value, 0);
}

