/*
 * sound_pulseaudio.c is part of Quisk, and is Copyright the following
 * authors:
 * 
 * Philip G. Lee <rocketman768@gmail.com>, 2014
 *
 * Quisk 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 of the License, or
 * (at your option) any later version.

 * Quisk 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, see <http://www.gnu.org/licenses/>.
 */

#include <Python.h>
#include <stdio.h>
#include <complex.h>
#include "quisk.h"

#include <pulse/simple.h>
#include <pulse/error.h>

// Current sound status
extern struct sound_conf quisk_sound_state;

// Buffer for float32 samples from sound
static float fbuffer[SAMP_BUFFER_SIZE];

/*!
 * \brief Read sound samples from PulseAudio.
 * 
 * Samples are converted to 32 bits with a range of +/- CLIP32 and placed into
 * cSamples.
 * 
 * \param dev Input. The device from which to read audio.
 * \param cSamples Output. The output samples.
 * 
 * \returns the number of samples placed into \c cSamples
 */
int quisk_read_pulseaudio(
   struct sound_dev* dev,
   complex double * cSamples
)
{
   pa_simple* padev = (pa_simple*)(dev->handle);
   int error = 0;
   int ret = 0;
   int i = 0;
   int nSamples = 0;
   int num_channels = dev->num_channels;
   // A frame is a sample from each channel
   int read_frames = dev->read_frames;
   // If 0, we are expected to do non-blocking read, but ignore for now and
   // keep doing the blocking read.
   if( read_frames == 0 )
      read_frames = (int)(quisk_sound_state.data_poll_usec * 1e-6 * dev->sample_rate + 0.5);
   int read_bytes = read_frames * num_channels * sizeof(float);
   float fi=0.f, fq=0.f;
   complex double c;
   
   // Read and check for errors.
   ret = pa_simple_read( padev, fbuffer, read_bytes, &error );
   if( ret < 0 )
   {
      dev->dev_error++;
      
      fprintf(
         stderr,
         __FILE__": quisk_read_pulseaudio() failed %s\n",
         pa_strerror(error)
      );
      
      return 0;
   }
   
   // Convert sampled data to complex data
   for( i = 0, nSamples = 0; nSamples < read_frames; i += num_channels, ++nSamples )
   {
      fi = fbuffer[i + dev->channel_I];
      fq = fbuffer[i + dev->channel_Q];
      if (fi >=  1.0 || fi <= -1.0)
         dev->overrange++;
      if (fq >=  1.0 || fq <= -1.0)
         dev->overrange++;
      cSamples[nSamples] = (fi + I * fq) * CLIP32;
   }
   
   // DC removal; R.G. Lyons page 553
   for (i = 0; i < nSamples; i++)
   {
      c = cSamples[i] + dev->dc_remove * 0.95;
      cSamples[i] = c - dev->dc_remove;
      dev->dc_remove = c;
   }
   
   return nSamples;
}

/*!
 * \brief Play the samples; write them to PulseAudio.
 * 
 * \param playdev Input. Device to which to play the samples.
 * \param nSamples Input. Number of samples to play.
 * \param cSamples Input. Sample buffer to play from.
 * \param report_latency Input. 1 to update \c quisk_sound_state.latencyPlay, 0 otherwise.
 * \param volume Input. Ratio in [0,1] by which to scale the played samples.
 */
void quisk_play_pulseaudio(
   struct sound_dev* playdev,
   int nSamples,
   complex double * cSamples,
   int report_latency,
   double volume
)
{
   pa_simple* padev = (pa_simple*)(playdev->handle);
   pa_usec_t latency = 0;
   size_t nBytes = nSamples * playdev->num_channels * sizeof(float);
   int error = 0;
   int ret = 0;
   float fi=0.f, fq=0.f;
   int i=0, n=0;

   if( !padev || nSamples <= 0)
      return;
   
   // Convert from complex data to framebuffer
   for(i = 0, n = 0; n < nSamples; i += playdev->num_channels, ++n)
   {
      fi = volume * creal(cSamples[n]);
      fq = volume * cimag(cSamples[n]);
      fbuffer[i + playdev->channel_I] = fi / CLIP32;
      fbuffer[i + playdev->channel_Q] = fq / CLIP32;
   }
   
   // Report the latency, if requested.
   if( report_latency )
   {
      latency = pa_simple_get_latency( padev, &error );
      if( latency == (pa_usec_t)(-1) )
      {
         fprintf(
            stderr,
            __FILE__": quisk_play_pulseaudio() failed %s\n",
            pa_strerror(error)
         );
         
         playdev->dev_error++;
      }
      else
      {
         // Samples left in play buffer
         //quisk_sound_state.latencyPlay = (latency * quisk_sound_state.playback_rate) / 1e6;
         quisk_sound_state.latencyPlay = latency;
      }
   }
   
   // Write data and check for errors
   ret = pa_simple_write( padev, fbuffer, nBytes, &error );
   if( ret < 0 )
   {
      fprintf(
         stderr,
         __FILE__": quisk_play_pulseaudio() failed %s\n",
         pa_strerror(error)
      );
      
      quisk_sound_state.write_error++;
      playdev->dev_error++;
   }
}

/*!
 * \brief Open PulseAudio device.
 * 
 * \param dev Input/Output. Device to initialize. \c dev->sample_rate and
 *        \c dev->num_channels must be properly set before calling. On return,
 *        \c dev->handle holds a \c pa_simple pointer, and \c dev->driver is
 *        set to \c DEV_DRIVER_PULSEAUDIO.
 * \param direction Input. Usually either \c PA_STREAM_PLAYBACK or
 *        \c PA_STREAM_RECORD
 * \param stream_description Input. Can be null for no description, but please
 *        provide a unique one
 * 
 * \return zero for no error, and nonzero result of \c pa_simple_new() for
 *         error
 */
static int quisk_open_pulseaudio(
   struct sound_dev* dev,
   pa_stream_direction_t direction,
   const char* stream_description
)
{
   pa_sample_spec ss;
   //pa_buffer_attr ba;
   int error = 0;
   
   // Device without name: sad.
   if( ! dev->name[0] )
      return 0;
   
   // Construct sample specification
   ss.format = PA_SAMPLE_FLOAT32LE;
   ss.rate = dev->sample_rate;
   ss.channels = dev->num_channels;
   
   // Construct buffer attributes. Letting PulseAudio do it's thing seems to be
   // the best thing to do here.
   //if( direction == PA_STREAM_PLAYBACK )
   //   ba.maxlength = (sizeof(float)*dev->num_channels) * dev->sample_rate * quisk_sound_state.latency_millisecs / 1e3;
   
   pa_simple* s;
   s = pa_simple_new(
      NULL, // Default server
      "Quisk", // Application name
      direction,
      NULL, // Default device
      stream_description,
      &ss, // Sample format
      NULL, // Default channel map
      //&ba, // Buffering attributes
      NULL,
      &error
   );
   
   if( error )
   {
      fprintf(
         stderr,
         __FILE__": quisk_open_pulseaudio_capture() failed %s\n",
         pa_strerror(error)
      );
      
      dev->handle = NULL;
      dev->driver = DEV_DRIVER_NONE;
      
      return error;
   }

   dev->handle = (void*)(s);
   dev->driver = DEV_DRIVER_PULSEAUDIO;
   return 0;
}

/*!
 * \brief Search for and open PulseAudio devices.
 * 
 * \param pCapture Input/Output. Array of capture devices to search through.
 *        If a device has its \c sound_dev.driver field set to
 *        \c DEV_DRIVER_PULSEAUDIO, it will be opened for recording.
 * \param pPlayback Input/Output. Array of playback devices to search through.
 *        If a device has its \c sound_dev.driver field set to
 *        \c DEV_DRIVER_PULSEAUDIO, it will be opened for recording.
 */
void quisk_start_sound_pulseaudio(
   struct sound_dev** pCapture,
   struct sound_dev** pPlayback
)
{
   struct sound_dev* dev;
   
   // Open all capture devices
   while(1)
   {
      dev = *pCapture++;
      // Done if null
      if( !dev )
         break;
      // Continue if it's not our device
      else if( dev->driver != DEV_DRIVER_PULSEAUDIO )
         continue;
      
      if( quisk_open_pulseaudio(dev, PA_STREAM_RECORD, dev->stream_description) )
         return;
   }
   
   // Open all playback devices
   while(1)
   {
      dev = *pPlayback++;
      // Done if null
      if( !dev )
         break;
      // Continue if it's not our device
      else if( dev->driver != DEV_DRIVER_PULSEAUDIO )
         continue;
      
      if( quisk_open_pulseaudio(dev, PA_STREAM_PLAYBACK, dev->stream_description) )
         return;
   }
}

/*!
 * \brief Search for and close PulseAudio devices.
 * 
 * \param pCapture Input/Output. Array of capture devices to search through.
 *        If a device has its \c sound_dev.driver field set to
 *        \c DEV_DRIVER_PULSEAUDIO, it will be closed.
 * \param pPlayback Input/Output. Array of capture devices to search through.
 *        If a device has its \c sound_dev.driver field set to
 *        \c DEV_DRIVER_PULSEAUDIO, it will be closed.
 */
void quisk_close_sound_pulseaudio(
   struct sound_dev** pCapture,
   struct sound_dev** pPlayback
)
{
   struct sound_dev* dev;
   int ret;
   int error;
   
   while(1)
   {
      dev = *pCapture++;
      
      // Done if null
      if( !dev )
         break;
      // Continue if it's not our device
      else if( dev->driver != DEV_DRIVER_PULSEAUDIO )
         continue;
      
      ret = pa_simple_drain( (pa_simple*)(dev->handle), &error );
      if( ret < 0 )
      {
         fprintf(
            stderr,
            __FILE__": quisk_close_sound_pulseaudio() failed %s\n",
            pa_strerror(error)
         );
      }
      
      pa_simple_free( (pa_simple*)(dev->handle) );
      dev->handle = NULL;
      dev->driver = DEV_DRIVER_NONE;
   }
   
   while(1)
   {
      dev = *pPlayback++;
      if( !dev )
         break;
      
      // Done if null
      if( !dev )
         break;
      // Continue if it's not our device
      else if( dev->driver != DEV_DRIVER_PULSEAUDIO )
         continue;
      
      ret = pa_simple_flush( (pa_simple*)(dev->handle), &error );
      if( ret < 0 )
      {
         fprintf(
            stderr,
            __FILE__": quisk_close_sound_pulseaudio() failed %s\n",
            pa_strerror(error)
         );
      }
      
      pa_simple_free( (pa_simple*)(dev->handle) );
      dev->handle = NULL;
      dev->driver = DEV_DRIVER_NONE;
   }
}
