/* libSoX device driver: MS-Windows audio   (c) 2009 SoX contributors
 *
 * 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
 */

#include "sox_i.h"

#include <windows.h>
#include <mmsystem.h>
#include <mmreg.h>

/* Larger means more latency (difference between the status line and the audio you hear),
 * but it means lower chance of stuttering/glitching. 2 buffers is usually enough. Use
 * 4 if you want to be extra-safe.
 */
#define num_buffers 4

typedef struct waveaudio_priv_t
{
  /* Handle to the input device (microphone, line in, etc.). NULL if playing. */
  HWAVEIN hin;

  /* Handle to the output device (speakers, line out, etc.). NULL if recording. */
  HWAVEOUT hout;

  /* Event that becomes signaled when a the system has finished processing a buffer. */
  HANDLE block_finished_event;

  /* Data transfer buffers. The lpData member of the first buffer points at
   * data[buf_len*sample_size*0], the second buffer's lpData points at
   * data[buf_len*sample_size*1], etc. The dwUser field contains the number
   * of samples of this buffer that have already been processed.
   */
  WAVEHDR headers[num_buffers];

  /* The combined data area shared by all transfer buffers.
   * size = (bufsiz rounded up to a multiple of 32 samples) * num_buffers.
   */
  char * data;

  /* The number of samples that can fit into one transfer buffer. */
  size_t buf_len;

  /* Index of the buffer that we're currently processing. For playback, this is the buffer
   * that will receive the next samples. For recording, this is the buffer from which we'll
   * be getting the next samples. If no buffers are ready for processing, this is the buffer
   * that will be the next to become ready.
   */
  unsigned current;

  /* Shift sample count by this many to get byte count:
   * 0 for 8-bit samples, 1 for 16-bit samples, or 2 for 32-bit samples.
   */
  unsigned char sample_shift;
} priv_t;

static void fail(sox_format_t* ft, unsigned code, const char* context)
{
  char message[256];
  unsigned formatMessageOk = FormatMessageA(
    FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
    NULL,
    code,
    0,
    message,
    sizeof(message) / sizeof(message[0]),
    NULL);
  if (formatMessageOk)
    lsx_fail_errno(ft, SOX_EOF, "%s failed with code %d: %s", context, (int)code, message);
  else
    lsx_fail_errno(ft, SOX_EOF, "%s failed with unrecognized MMSYSERR code: %d", context, (int)code);
}

static int stop(sox_format_t* ft)
{
  priv_t *priv = (priv_t*)ft->priv;
  if (priv == NULL) return SOX_EOF;

  if (priv->hin)
  {
    waveInReset(priv->hin);
    waveInClose(priv->hin);
  }
  
  if (priv->hout)
  {
    waveOutReset(priv->hout);
    waveOutClose(priv->hout);
  }

  if (priv->block_finished_event)
    CloseHandle(priv->block_finished_event);

  if (priv->data)
    free(priv->data);

  return SOX_SUCCESS;
}

static unsigned check_format(
    WAVEFORMATEXTENSIBLE* pfmt,
    int recording,
    unsigned dev,
    unsigned channels,
    unsigned width,
    unsigned hertz)
{
  /* GUID: KSDATAFORMAT_SUBTYPE_PCM */
  static unsigned char const SubformatPcm[] =
      "\x01\x00\x00\x00\x00\x00\x10\x00\x80\x00\x00\xAA\x00\x38\x9B\x71";

  const unsigned bytewidth = width > 16 ? 4 : width > 8 ? 2 : 1;
  const int extend = channels > 2 || width > 16;
  unsigned error;

  pfmt->Format.wFormatTag = extend ? WAVE_FORMAT_EXTENSIBLE : WAVE_FORMAT_PCM;
  pfmt->Format.nChannels = channels;
  pfmt->Format.nSamplesPerSec = hertz;
  pfmt->Format.nAvgBytesPerSec = channels * bytewidth * hertz;
  pfmt->Format.nBlockAlign = channels * bytewidth;
  pfmt->Format.wBitsPerSample = bytewidth * 8;
  pfmt->Format.cbSize = extend ? 22 : 0;
  pfmt->Samples.wValidBitsPerSample = width;
  pfmt->dwChannelMask = 0;
  memcpy(&pfmt->SubFormat, SubformatPcm, 16);

  if (recording)
    error = waveInOpen(0, dev, &pfmt->Format, 0, 0, WAVE_FORMAT_QUERY);
  else
    error = waveOutOpen(0, dev, &pfmt->Format, 0, 0, WAVE_FORMAT_QUERY);

  lsx_debug(
      "%s(QUERY: Dev %d %uHz %uCh %uPrec %uWide) returned %u",
      recording ? "waveInOpen" : "waveOutOpen",
      dev,
      hertz,
      channels,
      bytewidth * 8,
      width,
      error);
  return error;
}

static unsigned negotiate_format(sox_format_t* ft, WAVEFORMATEXTENSIBLE* pfmt, unsigned dev)
{
  int recording = ft->mode == 'r';
  unsigned error = 0;

  unsigned precision = ft->encoding.bits_per_sample;
  if (precision > 32)
    precision = 32;
  else if (precision < 8)
    precision = 8;

  while (precision > 0)
  {
    error = check_format(pfmt, recording, dev, ft->signal.channels, precision, (unsigned)ft->signal.rate);
    if (error == MMSYSERR_NOERROR)
      return MMSYSERR_NOERROR;
    precision = (precision - 1) & ~(7u);
  }

  return error;
}

static int start(sox_format_t* ft)
{
  size_t i;
  unsigned dev;
  WAVEFORMATEXTENSIBLE fmt;
  int recording = ft->mode == 'r';
  unsigned error;
  priv_t *priv = (priv_t*)ft->priv;
  if (priv == NULL) return SOX_EOF;
  memset(&fmt, 0, sizeof(fmt));

  /* Handle AUDIODEV device selection:
   * NULL, blank, or "default" gets you the default device (WAVE_MAPPER = -1).
   * An integer value gets you the device with that device number, if it exists (counting starts at 0).
   * A string gets you the device with that name, if it exists.
   */
  if (ft->filename == 0 || ft->filename[0] == 0 || !strcasecmp("default", ft->filename))
  {
    dev = WAVE_MAPPER;
  }
  else
  {
    WAVEINCAPSA incaps;
    WAVEOUTCAPSA outcaps;
    const char *dev_name;
    char* dev_num_end;
    dev = strtoul(ft->filename, &dev_num_end, 0);
    if (dev_num_end[0] == 0)
    {
      if (recording)
        error = waveInGetDevCapsA(dev, &incaps, sizeof(incaps));
      else
        error = waveOutGetDevCapsA(dev, &outcaps, sizeof(outcaps));

      if (error)
      {
        lsx_fail_errno(ft, ENODEV, "device not found");
        return SOX_EOF;
      }
    }
    else
    {
      unsigned dev_count = recording ? waveInGetNumDevs() : waveOutGetNumDevs();
      size_t name_len = strlen(ft->filename);
      if (name_len > 31)
          name_len = 31;
      for (dev = WAVE_MAPPER; dev != dev_count; dev++)
      {
        if (recording)
        {
          error = waveInGetDevCapsA(dev, &incaps, sizeof(incaps));
          dev_name = incaps.szPname;
          lsx_debug("Enumerating input device %2d: \"%s\"", dev, dev_name);
        }
        else
        {
          error = waveOutGetDevCapsA(dev, &outcaps, sizeof(outcaps));
          dev_name = outcaps.szPname;
          lsx_debug("Enumerating output device %2d: \"%s\"", dev, dev_name);
        }

        if (error)
        {
          fail(ft, error, recording ? "waveInGetDevCapsA" : "waveOutGetDevCapsA");
          return SOX_EOF;
        }

        if (!strncasecmp(ft->filename, dev_name, name_len))
        {
          lsx_report("requested name \"%s\" matched device %d: \"%s\"", ft->filename, dev, dev_name);
          break;
        }
      }

      if (dev == dev_count)
      {
        lsx_fail_errno(ft, ENODEV, "the requested device was not found");
        return SOX_EOF;
      }
    }
  }

  error = negotiate_format(ft, &fmt, dev);
  if (error != MMSYSERR_NOERROR)
  {
    fail(ft, error, "sample format negotiation");
    return SOX_EOF;
  }

  switch (fmt.Format.wBitsPerSample)
  {
  case 8:
      priv->sample_shift = 0;
      break;
  case 16:
      priv->sample_shift = 1;
      break;
  case 32:
      priv->sample_shift = 2;
      break;
  default:
      lsx_fail_errno(ft, E2BIG, "unexpected value for wBitsPerSample: %u", fmt.Format.wBitsPerSample);
      return SOX_EOF;
  }

  ft->signal.precision = fmt.Samples.wValidBitsPerSample;
  ft->signal.channels = fmt.Format.nChannels;
  if (dev == WAVE_MAPPER)
  {
      lsx_report(
          "Using default %s device at %uHz %uCh %uPrec %uWide.",
          recording ? "input" : "output",
          (unsigned)fmt.Format.nSamplesPerSec,
          (unsigned)fmt.Format.nChannels,
          (unsigned)fmt.Samples.wValidBitsPerSample,
          (unsigned)fmt.Format.wBitsPerSample);
  }
  else
  {
      lsx_report(
          "Using %s device #%d at %uHz %uCh %uPrec %uWide.",
          recording ? "input" : "output",
          (int)dev,
          (unsigned)fmt.Format.nSamplesPerSec,
          (unsigned)fmt.Format.nChannels,
          (unsigned)fmt.Samples.wValidBitsPerSample,
          (unsigned)fmt.Format.wBitsPerSample);
  }

  priv->buf_len = ((sox_globals.bufsiz >> priv->sample_shift) + 31) & ~31u;
  priv->data = lsx_malloc((priv->buf_len * num_buffers) << priv->sample_shift);

  priv->block_finished_event = CreateEventA(NULL, sox_false, sox_false, NULL);
  if (!priv->block_finished_event)
  {
    error = GetLastError();
    fail(ft, error, "CreateEventA");
    stop(ft);
    return SOX_EOF;
  }

  if (recording)
  {
    error = waveInOpen(
        &priv->hin,
        dev,
        &fmt.Format,
        (DWORD_PTR)priv->block_finished_event,
        0,
        CALLBACK_EVENT);
  }
  else
  {
    error = waveOutOpen(
        &priv->hout,
        dev,
        &fmt.Format,
        (DWORD_PTR)priv->block_finished_event,
        0,
        CALLBACK_EVENT);
  }

  if (error != MMSYSERR_NOERROR)
  {
    fail(ft, error, recording ? "waveInOpen" : "waveOutOpen");
    stop(ft);
    return SOX_EOF;
  }

  for (i = 0; i != num_buffers; i++)
  {
    priv->headers[i].lpData = priv->data + ((priv->buf_len * i) << priv->sample_shift);
    priv->headers[i].dwBufferLength = priv->buf_len << priv->sample_shift;

    if (recording)
      error = waveInPrepareHeader(priv->hin, &priv->headers[i], sizeof(priv->headers[i]));
    else
      error = waveOutPrepareHeader(priv->hout, &priv->headers[i], sizeof(priv->headers[i]));

    if (error != MMSYSERR_NOERROR)
    {
      fail(ft, error, recording ? "waveInPrepareHeader" : "waveOutPrepareHeader");
      stop(ft);
      return SOX_EOF;
    }

    if (recording)
    {
      error = waveInAddBuffer(priv->hin, &priv->headers[i], sizeof(priv->headers[i]));
      if (error != MMSYSERR_NOERROR)
      {
        fail(ft, error, "waveInAddBuffer");
        stop(ft);
        return SOX_EOF;
      }
    }
  }

  if (recording)
  {
    error = waveInStart(priv->hin);
    if (error != MMSYSERR_NOERROR)
    {
      fail(ft, error, "waveInStart");
      stop(ft);
      return SOX_EOF;
    }
  }

  return SOX_SUCCESS;
}

static size_t waveread(sox_format_t * ft, sox_sample_t* buf, size_t len)
{
  size_t copied = 0;
  priv_t *priv = (priv_t*)ft->priv;
  unsigned error = 0;

  if (priv == NULL)
      return (size_t)SOX_EOF;

  while (!error && copied < len)
  {
    LPWAVEHDR header = &priv->headers[priv->current];
    if (0 == (header->dwFlags & WHDR_INQUEUE) ||
      0 != (header->dwFlags & WHDR_DONE))
    {
      size_t length = header->dwBytesRecorded >> priv->sample_shift;
      size_t ready = min(len - copied, length - header->dwUser);
      size_t i;

      switch (priv->sample_shift)
      {
      case 0:
          for (i = 0; i < ready; ++i)
          {
            buf[copied++] = SOX_UNSIGNED_8BIT_TO_SAMPLE(((uint8_t *)header->lpData)[header->dwUser++], dummy);
          }
          break;
      case 1:
          for (i = 0; i < ready; ++i)
          {
            buf[copied++] = SOX_SIGNED_16BIT_TO_SAMPLE(((int16_t *)header->lpData)[header->dwUser++], dummy);
          }
          break;
      case 2:
          for (i = 0; i < ready; ++i)
          {
            buf[copied++] = SOX_SIGNED_32BIT_TO_SAMPLE(((int32_t *)header->lpData)[header->dwUser++], dummy);
          }
          break;
      }

      if (header->dwUser == length)
      {
        error = waveInAddBuffer(priv->hin, header, sizeof(*header));
        priv->current = (priv->current + 1) % num_buffers;
        priv->headers[priv->current].dwUser = 0;
        if (error)
        {
          fail(ft, error, "waveInAddBuffer");
          copied = 0;
        }
      }
    }
    else
    {
      WaitForSingleObject(priv->block_finished_event, INFINITE);
    }
  }

  return copied;
}

static size_t wavewrite(sox_format_t * ft, const sox_sample_t* buf, size_t len)
{
  size_t copied = 0;
  priv_t *priv = (priv_t*)ft->priv;
  unsigned error = 0;
  unsigned clips = 0;

  if (priv == NULL)
      return (size_t)SOX_EOF;

  while (!error && copied < len)
  {
    LPWAVEHDR header = &priv->headers[priv->current];
    if (0 == (header->dwFlags & WHDR_INQUEUE) ||
      0 != (header->dwFlags & WHDR_DONE))
    {
      size_t ready = min(len - copied, priv->buf_len - header->dwUser);
      size_t i;

      switch (priv->sample_shift)
      {
      case 0:
          for (i = 0; i < ready; ++i)
          {
            SOX_SAMPLE_LOCALS;
            ((uint8_t *)header->lpData)[header->dwUser++] = SOX_SAMPLE_TO_UNSIGNED_8BIT(buf[copied++], clips);
          }
          break;
      case 1:
          for (i = 0; i < ready; ++i)
          {
            SOX_SAMPLE_LOCALS;
            ((int16_t *)header->lpData)[header->dwUser++] = SOX_SAMPLE_TO_SIGNED_16BIT(buf[copied++], clips);
          }
          break;
      case 2:
          for (i = 0; i < ready; ++i)
          {
            ((int32_t *)header->lpData)[header->dwUser++] = SOX_SAMPLE_TO_SIGNED_32BIT(buf[copied++], clips);
          }
          break;
      }

      header->dwBufferLength = header->dwUser << priv->sample_shift;
      error = waveOutWrite(priv->hout, header, sizeof(*header));
      priv->current = (priv->current + 1) % num_buffers;
      priv->headers[priv->current].dwUser = 0;

      if (error)
      {
        fail(ft, error, "waveOutWrite");
        copied = 0;
      }
    }
    else
    {
      WaitForSingleObject(priv->block_finished_event, INFINITE);
    }
  }

  return copied;
}

LSX_FORMAT_HANDLER(waveaudio)
{
  static const char * const names[] = {"waveaudio", NULL};
  static unsigned const write_encodings[] = {
    SOX_ENCODING_SIGN2, 32, 24, 16, 8, 0,
    0};
  static sox_format_handler_t const handler = {SOX_LIB_VERSION_CODE,
  "Windows Multimedia Audio", names,
  SOX_FILE_DEVICE | SOX_FILE_NOSTDIO,
  start, waveread, stop,
  start, wavewrite, stop,
  NULL, write_encodings, NULL, sizeof(priv_t)
  };
  return &handler;
}
