/* -*- c++ -*- */
/*
 * Copyright 2006-2011,2013 Free Software Foundation, Inc.
 *
 * This file is part of GNU Radio.
 *
 * GNU Radio 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 3, or (at your option)
 * any later version.
 *
 * GNU Radio 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 GNU Radio; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "audio_registry.h"
#include <osx_sink.h>
#include <osx_impl.h>
#include <gr_io_signature.h>
#include <stdexcept>

namespace gr {
  namespace audio {

#define _OSX_AU_DEBUG_ 0

    AUDIO_REGISTER_SINK(REG_PRIO_HIGH, osx)(int sampling_rate,
                                            const std::string &device_name,
                                            bool ok_to_block)
    {
      return sink::sptr
        (new osx_sink(sampling_rate, device_name, ok_to_block));
    }

    osx_sink::osx_sink(int sample_rate,
                       const std::string device_name,
                       bool do_block,
                       int channel_config,
                       int max_sample_count)
      : gr_sync_block("audio_osx_sink",
                      gr_make_io_signature(0, 0, 0),
                      gr_make_io_signature(0, 0, 0)),
        d_sample_rate(0.0), d_channel_config(0), d_n_channels(0),
        d_queueSampleCount(0), d_max_sample_count(0),
        d_do_block(do_block), d_internal(0), d_cond_data(0),
        d_OutputAU(0)
    {
      if(sample_rate <= 0) {
        std::cerr << "Invalid Sample Rate: " << sample_rate << std::endl;
        throw std::invalid_argument ("audio_osx_sink::audio_osx_sink");
      }
      else
        d_sample_rate = (Float64)sample_rate;

      if(channel_config <= 0 & channel_config != -1) {
        std::cerr << "Invalid Channel Config: " << channel_config << std::endl;
        throw std::invalid_argument ("audio_osx_sink::audio_osx_sink");
      }
      else if(channel_config == -1) {
        // no user input; try "device name" instead
        int l_n_channels = (int)strtol(device_name.data(), (char**)NULL, 10);
        if((l_n_channels == 0) & errno) {
          std::cerr << "Error Converting Device Name: " << errno << std::endl;
          throw std::invalid_argument("audio_osx_sink::audio_osx_sink");
        }
        if(l_n_channels <= 0)
          channel_config = 2;
        else
          channel_config = l_n_channels;
      }

      d_n_channels = d_channel_config = channel_config;

      // set the input signature

      set_input_signature(gr_make_io_signature(1, d_n_channels, sizeof(float)));

      // check that the max # of samples to store is valid

      if(max_sample_count == -1)
        max_sample_count = sample_rate;
      else if(max_sample_count <= 0) {
        std::cerr << "Invalid Max Sample Count: " << max_sample_count << std::endl;
        throw std::invalid_argument ("audio_osx_sink::audio_osx_sink");
      }

      d_max_sample_count = max_sample_count;

      // allocate the output circular buffer(s), one per channel

      d_buffers = (circular_buffer<float>**) new
        circular_buffer<float>* [d_n_channels];
      UInt32 n_alloc = (UInt32) ceil((double)d_max_sample_count);
      for(UInt32 n = 0; n < d_n_channels; n++) {
        d_buffers[n] = new circular_buffer<float>(n_alloc, false, false);
      }

      // create the default AudioUnit for output
      OSStatus err = noErr;

      // Open the default output unit
#ifndef GR_USE_OLD_AUDIO_UNIT
      AudioComponentDescription desc;
#else
      ComponentDescription desc;
#endif

      desc.componentType = kAudioUnitType_Output;
      desc.componentSubType = kAudioUnitSubType_DefaultOutput;
      desc.componentManufacturer = kAudioUnitManufacturer_Apple;
      desc.componentFlags = 0;
      desc.componentFlagsMask = 0;

#ifndef GR_USE_OLD_AUDIO_UNIT
      AudioComponent comp = AudioComponentFindNext(NULL, &desc);
      if(comp == NULL) {
        std::cerr << "AudioComponentFindNext Error" << std::endl;
        throw std::runtime_error("audio_osx_sink::audio_osx_sink");
      }
#else
      Component comp = FindNextComponent(NULL, &desc);
      if(comp == NULL) {
        std::cerr << "FindNextComponent Error" << std::endl;
        throw std::runtime_error("audio_osx_sink::audio_osx_sink");
      }
#endif

#ifndef GR_USE_OLD_AUDIO_UNIT
      err = AudioComponentInstanceNew(comp, &d_OutputAU);
      CheckErrorAndThrow(err, "AudioComponentInstanceNew",
                         "audio_osx_sink::audio_osx_sink");
#else
      err = OpenAComponent(comp, &d_OutputAU);
      CheckErrorAndThrow(err, "OpenAComponent",
                         "audio_osx_sink::audio_osx_sink");
#endif

      // Set up a callback function to generate output to the output unit

      AURenderCallbackStruct input;
      input.inputProc = (AURenderCallback)(osx_sink::AUOutputCallback);
      input.inputProcRefCon = this;

      err = AudioUnitSetProperty(d_OutputAU,
                                 kAudioUnitProperty_SetRenderCallback,
                                 kAudioUnitScope_Input,
                                 0,
                                 &input,
                                 sizeof (input));
      CheckErrorAndThrow(err, "AudioUnitSetProperty Render Callback",
                         "audio_osx_sink::audio_osx_sink");

      // tell the Output Unit what format data will be supplied to it
      // so that it handles any format conversions

      AudioStreamBasicDescription streamFormat;
      streamFormat.mSampleRate = (Float64)(sample_rate);
      streamFormat.mFormatID = kAudioFormatLinearPCM;
      streamFormat.mFormatFlags = (kLinearPCMFormatFlagIsFloat |
                                   GR_PCM_ENDIANNESS |
                                   kLinearPCMFormatFlagIsPacked |
                                   kAudioFormatFlagIsNonInterleaved);
      streamFormat.mBytesPerPacket = 4;
      streamFormat.mFramesPerPacket = 1;
      streamFormat.mBytesPerFrame = 4;
      streamFormat.mChannelsPerFrame = d_n_channels;
      streamFormat.mBitsPerChannel = 32;

      err = AudioUnitSetProperty(d_OutputAU,
                                 kAudioUnitProperty_StreamFormat,
                                 kAudioUnitScope_Input,
                                 0,
                                 &streamFormat,
                                 sizeof(AudioStreamBasicDescription));
      CheckErrorAndThrow(err, "AudioUnitSetProperty StreamFormat",
                         "audio_osx_sink::audio_osx_sink");

      // create the stuff to regulate I/O

      d_cond_data = new gruel::condition_variable();
      if(d_cond_data == NULL)
        CheckErrorAndThrow(errno, "new condition (data)",
                           "audio_osx_sink::audio_osx_sink");

      d_internal = new gruel::mutex();
      if(d_internal == NULL)
        CheckErrorAndThrow(errno, "new mutex (internal)",
                           "audio_osx_sink::audio_osx_sink");

      // initialize the AU for output

      err = AudioUnitInitialize(d_OutputAU);
      CheckErrorAndThrow(err, "AudioUnitInitialize",
                         "audio_osx_sink::audio_osx_sink");

#if _OSX_AU_DEBUG_
      std::cerr << "audio_osx_sink Parameters:" << std::endl;
      std::cerr << "  Sample Rate is " << d_sample_rate << std::endl;
      std::cerr << "  Number of Channels is " << d_n_channels << std::endl;
      std::cerr << "  Max # samples to store per channel is " << d_max_sample_count << std::endl;
#endif
}

    bool
    osx_sink::IsRunning()
    {
      UInt32 AURunning = 0, AUSize = sizeof(UInt32);

      OSStatus err = AudioUnitGetProperty(d_OutputAU,
                                          kAudioOutputUnitProperty_IsRunning,
                                          kAudioUnitScope_Global,
                                          0,
                                          &AURunning,
                                          &AUSize);
      CheckErrorAndThrow(err, "AudioUnitGetProperty IsRunning",
                         "audio_osx_sink::IsRunning");

      return (AURunning);
    }

    bool
    osx_sink::start()
    {
      if(!IsRunning()) {
        OSStatus err = AudioOutputUnitStart(d_OutputAU);
        CheckErrorAndThrow(err, "AudioOutputUnitStart",
                           "audio_osx_sink::start");
      }

      return (true);
    }

    bool
    osx_sink::stop()
    {
      if(IsRunning ()) {
        OSStatus err = AudioOutputUnitStop(d_OutputAU);
        CheckErrorAndThrow(err, "AudioOutputUnitStop",
                           "audio_osx_sink::stop");

        for(UInt32 n = 0; n < d_n_channels; n++) {
          d_buffers[n]->abort();
        }
      }

      return (true);
    }

    osx_sink::~osx_sink()
    {
      // stop and close the AudioUnit
      stop();
      AudioUnitUninitialize(d_OutputAU);
#ifndef GR_USE_OLD_AUDIO_UNIT
      AudioComponentInstanceDispose(d_OutputAU);
#else
      CloseComponent(d_OutputAU);
#endif

      // empty and delete the queues
      for(UInt32 n = 0; n < d_n_channels; n++) {
        delete d_buffers[n];
        d_buffers[n] = 0;
      }
      delete [] d_buffers;
      d_buffers = 0;

      // close and delete control stuff
      delete d_cond_data;
      d_cond_data = 0;
      delete d_internal;
      d_internal = 0;
    }

    int
    osx_sink::work(int noutput_items,
                   gr_vector_const_void_star &input_items,
                   gr_vector_void_star &output_items)
    {
      gruel::scoped_lock l(*d_internal);

      /* take the input data, copy it, and push it to the bottom of the queue
         mono input are pushed onto queue[0];
         stereo input are pushed onto queue[1].
         Start the AudioUnit if necessary. */

      UInt32 l_max_count;
      int diff_count = d_max_sample_count - noutput_items;
      if(diff_count < 0)
        l_max_count = 0;
      else
        l_max_count = (UInt32)diff_count;

#if 0
      if(l_max_count < d_queueItemLength->back()) {
        // allow 2 buffers at a time, regardless of length
        l_max_count = d_queueItemLength->back();
      }
#endif

#if _OSX_AU_DEBUG_
      std::cerr << "work1: qSC = " << d_queueSampleCount
                << ", lMC = "<< l_max_count
                << ", dmSC = " << d_max_sample_count
                << ", nOI = " << noutput_items << std::endl;
#endif

      if(d_queueSampleCount > l_max_count) {
        // data coming in too fast; do_block decides what to do
        if(d_do_block == true) {
          // block until there is data to return
          while(d_queueSampleCount > l_max_count) {
            // release control so-as to allow data to be retrieved;
            // block until there is data to return
            d_cond_data->wait(l);
            // the condition's 'notify' was called; acquire control
            // to keep thread safe
          }
        }
      }
      // not blocking case and overflow is handled by the circular buffer

      // add the input frames to the buffers' queue, checking for overflow

      UInt32 l_counter;
      int res = 0;
      float* inBuffer = (float*)input_items[0];
      const UInt32 l_size = input_items.size();
      for(l_counter = 0; l_counter < l_size; l_counter++) {
        inBuffer = (float*)input_items[l_counter];
        int l_res = d_buffers[l_counter]->enqueue(inBuffer,
                                                  noutput_items);
        if(l_res == -1)
          res = -1;
      }
      while(l_counter < d_n_channels) {
        // for extra channels, copy the last input's data
        int l_res = d_buffers[l_counter++]->enqueue(inBuffer,
                                                    noutput_items);
        if(l_res == -1)
          res = -1;
      }

      if(res == -1) {
        // data coming in too fast
        // drop oldest buffer
        fputs("aO", stderr);
        fflush(stderr);
        // set the local number of samples available to the max
        d_queueSampleCount = d_buffers[0]->buffer_length_items();
      }
      else {
        // keep up the local sample count
        d_queueSampleCount += noutput_items;
      }

#if _OSX_AU_DEBUG_
      std::cerr << "work2: #OI = "
                << noutput_items << ", #Cnt = "
                << d_queueSampleCount << ", mSC = "
                << d_max_sample_count << std::endl;
#endif

      return (noutput_items);
    }

    OSStatus
    osx_sink::AUOutputCallback(void *inRefCon,
                               AudioUnitRenderActionFlags *ioActionFlags,
                               const AudioTimeStamp *inTimeStamp,
                               UInt32 inBusNumber,
                               UInt32 inNumberFrames,
                               AudioBufferList *ioData)
    {
      osx_sink* This = (osx_sink*)inRefCon;
      OSStatus err = noErr;

      gruel::scoped_lock l(*This->d_internal);

#if _OSX_AU_DEBUG_
      std::cerr << "cb_in: SC = " << This->d_queueSampleCount
                << ", in#F = " << inNumberFrames << std::endl;
#endif

      if(This->d_queueSampleCount < inNumberFrames) {
        // not enough data to fill request
        err = -1;
      }
      else {
        // enough data; remove data from our buffers into the AU's buffers
        int l_counter = This->d_n_channels;

        while(--l_counter >= 0) {
          size_t t_n_output_items = inNumberFrames;
          float* outBuffer = (float*)ioData->mBuffers[l_counter].mData;
          This->d_buffers[l_counter]->dequeue(outBuffer, &t_n_output_items);
          if(t_n_output_items != inNumberFrames) {
            throw std::runtime_error("audio_osx_sink::AUOutputCallback(): "
                                     "number of available items changing "
                                     "unexpectedly.\n");
          }
        }

        This->d_queueSampleCount -= inNumberFrames;
      }

#if _OSX_AU_DEBUG_
      std::cerr << "cb_out: SC = " << This->d_queueSampleCount << std::endl;
#endif

      // signal that data is available
      This->d_cond_data->notify_one();

      return (err);
    }

  } /* namespace audio */
} /* namespace gr */