/*
 *  Copyright 1994-2022 Olivier Girondel
 *
 *  This file is part of lebiniou.
 *
 *  lebiniou 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 2 of the License, or
 *  (at your option) any later version.
 *
 *  lebiniou 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 lebiniou. If not, see <http://www.gnu.org/licenses/>.
 */

/* Use the newer ALSA API */
#define ALSA_PCM_NEW_HW_PARAMS_API
#include <alsa/asoundlib.h>
#include "context.h"
#include "pthread_utils.h"


uint32_t options = BO_NONE;
uint32_t version = 0;


static snd_pcm_t *pcm_handle = NULL;
static snd_pcm_stream_t stream = SND_PCM_STREAM_CAPTURE;
static snd_pcm_hw_params_t *hwparams = NULL;
#define DEFAULT_PCM_NAME "default"

static unsigned int rate = 44100, exact_rate;
static int size;
static snd_pcm_uframes_t frames;
static int dir;

static char *data = NULL;


int8_t
create(Context_t *ctx)
{
  frames = Context_get_input_size(ctx);
  const char *pcm_name = NULL;
  
  if (NULL == (pcm_name = getenv("LEBINIOU_ALSA_PCM"))) {
    pcm_name = DEFAULT_PCM_NAME;
  }
  VERBOSE(printf("[+] %s: using capture device: %s\n", __FILE__, pcm_name));

  snd_pcm_hw_params_alloca(&hwparams);

  if (snd_pcm_open(&pcm_handle, pcm_name, stream, 0) < 0) {
    xerror("error opening PCM device %s\n", pcm_name);
  } else {
    VERBOSE(printf("[+] %s: %s ready for capture\n", __FILE__, pcm_name));
  }

  if (snd_pcm_hw_params_any(pcm_handle, hwparams) < 0) {
    xerror("error configuring PCM device %s\n", pcm_name);
  } else {
    VERBOSE(printf("[+] %s: %s configured\n", __FILE__, pcm_name));
  }

  if (snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
    xerror("error setting access mode\n");
  } else {
    VERBOSE(printf("[+] %s: access mode set\n", __FILE__));
  }

  if (snd_pcm_hw_params_set_format(pcm_handle, hwparams, SND_PCM_FORMAT_S16_LE) < 0) {
    xerror("error setting format\n");
  } else {
    VERBOSE(printf("[+] %s: format set\n", __FILE__));
  }

  exact_rate = rate;
  if (snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &exact_rate, 0) < 0) {
    xerror("error setting rate\n");
  } else {
    VERBOSE(printf("[+] %s: setting format: wanted %dHz, got %dHz\n", __FILE__, rate, exact_rate));
  }

  if (snd_pcm_hw_params_set_channels(pcm_handle, hwparams, 2) < 0) {
    xerror("error setting channels\n");
  } else {
    VERBOSE(printf("[+] %s: channels set\n", __FILE__));
  }

  VERBOSE(printf("[+] %s: asking for %ld frames\n", __FILE__, frames));
  if (snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &frames, &dir) < 0) {
    xerror("error setting period size");
  } else {
    VERBOSE(printf("[+] %s: effectively using %ld frames\n", __FILE__, frames));
  }

  if (snd_pcm_hw_params(pcm_handle, hwparams) < 0) {
    xerror("error setting HW params\n");
  } else {
    VERBOSE(printf("[+] %s: HW params set\n", __FILE__));
  }

  /* Use a buffer large enough to hold one period */
  snd_pcm_hw_params_get_period_size(hwparams, &frames, &dir);
  size = frames * 4; /* 2 bytes/sample, 2 channels */

  data = xmalloc(size * sizeof(char));

  ctx->input = Input_new(frames);

  return 1;
}


void *
jthread(void *args)
{
  Context_t *ctx = (Context_t *)args;

  while (ctx->running) {
    short *in = (short *)data;

    while (snd_pcm_readi(pcm_handle, data, frames) < 0) {
      snd_pcm_prepare(pcm_handle);
#ifdef DEBUG_ALSA
      fprintf(stderr, "[!] %s: Buffer overrun\n", __FILE__);
#endif
    }

    if (!ctx->input->mute) {
      if (!xpthread_mutex_lock(&ctx->input->mutex)) {
        uint16_t n = 0;

        for (uint16_t idx = 0; idx < frames; idx++) {
          ctx->input->data[A_LEFT][idx] =
            (float)(((float)(in[n])) / (float)-SHRT_MIN);
          n++;
          ctx->input->data[A_RIGHT][idx] =
            (float)(((float)(in[n])) / (float)-SHRT_MIN);
          n++;
        }
        Input_set(ctx->input, A_STEREO);
        xpthread_mutex_unlock(&ctx->input->mutex);
      }
    }
  }

  return NULL;
}


void
destroy(Context_t *ctx)
{
  Input_delete(ctx->input);
  xfree(data);
  snd_pcm_close(pcm_handle);
  snd_config_update_free_global();
}
