#include "rpi_microphone.h"

#include <alsa/asoundlib.h>
#include <stdio.h>

#define CHANNELS_COUNT 1
#define SAMPLE_RATE 44100

snd_pcm_t *capture_handle;

int write_to_fftw_input_buffers(int16_t frames, int16_t buf[frames * 2], audio_data_t *audio);

void *microphone_listen(void *arg) {
  audio_data_t *audio = (audio_data_t *)arg;
  microphone_setup(audio);
  while (1) {
    microphone_exec(audio);
  }
}

void microphone_setup(audio_data_t *audio) {
  int err;
  snd_pcm_hw_params_t *hw_params;
  snd_pcm_uframes_t frames = audio->FFTtreblebufferSize;
  unsigned int sampleRate = SAMPLE_RATE;

  if ((err = snd_pcm_open(&capture_handle, audio->source, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
    fprintf(stderr, "cannot open audio device %s (%s)\n", audio->source, snd_strerror(err));
    exit(1);
  }

  if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
    fprintf(stderr, "cannot allocate hardware parameter structure (%s)\n", snd_strerror(err));
    exit(1);
  }

  if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0) {
    fprintf(stderr, "cannot initialize hardware parameter structure (%s)\n", snd_strerror(err));
    exit(1);
  }

  if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params,
                                          SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
    fprintf(stderr, "cannot set access type (%s)\n", snd_strerror(err));
    exit(1);
  }

  if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
    fprintf(stderr, "cannot set sample format (%s)\n", snd_strerror(err));
    exit(1);
  }

  if ((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, CHANNELS_COUNT)) < 0) {
    fprintf(stderr, "cannot set channel count (%s)\n", snd_strerror(err));
    exit(1);
  }

  if ((err = snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, &sampleRate, NULL)) < 0) {
    fprintf(stderr, "cannot set sample rate (%s)\n", snd_strerror(err));
    exit(1);
  }

  if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params, &frames, NULL)) <
      0) {
    fprintf(stderr, "cannot set period size (%s)\n", snd_strerror(err));
    exit(1);
  }

  if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0) {
    fprintf(stderr, "cannot set parameters (%s)\n", snd_strerror(err));
    exit(1);
  }

  snd_pcm_hw_params_get_rate(hw_params, &audio->rate, NULL);

  snd_pcm_hw_params_free(hw_params);

  if ((err = snd_pcm_prepare(capture_handle)) < 0) {
    fprintf(stderr, "cannot prepare audio interface for use (%s)\n", snd_strerror(err));
    exit(1);
  }
}

void microphone_exec(audio_data_t *audio) {
  int err;
  snd_pcm_uframes_t buffer_size;
  snd_pcm_uframes_t period_size;
  snd_pcm_uframes_t frames = audio->FFTtreblebufferSize;

  snd_pcm_get_params(capture_handle, &buffer_size, &period_size);
  int16_t buf[period_size];
  frames = period_size / 2;

  err = snd_pcm_readi(capture_handle, buf, frames);

  if (err == -EPIPE) {
    /* EPIPE means overrun */
    fprintf(stderr, "overrun occurred\n");
    snd_pcm_prepare(capture_handle);
  } else if (err < 0) {
    fprintf(stderr, "error from read: %s\n", snd_strerror(err));
  } else if (err != (int)frames) {
    fprintf(stderr, "short read, read %d %d frames\n", err, (int)frames);
  }

  pthread_mutex_lock(&audio->lock);
  write_to_fftw_input_buffers(frames, buf, audio);
  pthread_mutex_unlock(&audio->lock);
}

void reset_output_buffers(audio_data_t *data) {
  memset(data->in_bass_l, 0, sizeof(double) * data->FFTbassbufferSize);
  memset(data->in_mid_l, 0, sizeof(double) * data->FFTmidbufferSize);
  memset(data->in_treble_l, 0, sizeof(double) * data->FFTtreblebufferSize);
  memset(data->in_bass_l_raw, 0, sizeof(double) * data->FFTbassbufferSize);
  memset(data->in_mid_l_raw, 0, sizeof(double) * data->FFTmidbufferSize);
  memset(data->in_treble_l_raw, 0, sizeof(double) * data->FFTtreblebufferSize);
}

int write_to_fftw_input_buffers(int16_t frames, int16_t buf[frames * 2], audio_data_t *audio) {
  if (frames == 0)
    return 0;

  for (uint16_t n = audio->FFTbassbufferSize; n > frames; n = n - frames) {
    for (uint16_t i = 1; i <= frames; i++) {
      audio->in_bass_l_raw[n - i] = audio->in_bass_l_raw[n - i - frames];
    }
  }
  for (uint16_t n = audio->FFTmidbufferSize; n > frames; n = n - frames) {
    for (uint16_t i = 1; i <= frames; i++) {
      audio->in_mid_l_raw[n - i] = audio->in_mid_l_raw[n - i - frames];
    }
  }
  for (uint16_t n = audio->FFTtreblebufferSize; n > frames; n = n - frames) {
    for (uint16_t i = 1; i <= frames; i++) {
      audio->in_treble_l_raw[n - i] = audio->in_treble_l_raw[n - i - frames];
    }
  }
  uint16_t n = frames - 1;

  for (uint16_t i = 0; i < frames; i++) {
    audio->in_bass_l_raw[n] = buf[i * 2];
    audio->in_mid_l_raw[n] = audio->in_bass_l_raw[n];
    audio->in_treble_l_raw[n] = audio->in_bass_l_raw[n];
    n--;
  }

  // Hann Window
  for (int i = 0; i < audio->FFTbassbufferSize; i++) {
    audio->in_bass_l[i] = audio->bass_multiplier[i] * audio->in_bass_l_raw[i];
  }
  for (int i = 0; i < audio->FFTmidbufferSize; i++) {
    audio->in_mid_l[i] = audio->mid_multiplier[i] * audio->in_mid_l_raw[i];
  }
  for (int i = 0; i < audio->FFTtreblebufferSize; i++) {
    audio->in_treble_l[i] = audio->treble_multiplier[i] * audio->in_treble_l_raw[i];
  }
  return 0;
}
