/*
 *  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/>.
 */

#include <jack/jack.h>
#include "context.h"
#include "settings.h"
#include "pthread_utils.h"


uint32_t options = BO_NONE;
uint32_t version = 0;

/* JACK data */
static jack_port_t **input_ports;
static jack_client_t *client;
static const char *source_names[2] = { NULL, NULL };
static const char **ports;

// default input size if not defined in the configuration file
#define INSIZE 1024
static uint16_t insize = INSIZE;
// buffer jack samples here before copying to input->data
static double *data[2];
// number of jack frames to be read to reach input->size
static uint8_t chunks;


static void
jack_shutdown(void *arg)
{
  Context_t *ctx = (Context_t *)arg;
  printf("[!] JACK: server shut down, exiting\n");
  ctx->running = 0;
}


static int
process(jack_nframes_t nframes, void *arg)
{
  int chn;
  uint32_t i;
  Context_t *ctx = (Context_t *)arg;
  jack_default_audio_sample_t *in;
  static uint8_t chunk = 0;
  static uint16_t idx = 0;

  if (!ctx->input->mute) {
    uint16_t idx2 = idx;

    for (chn = 0; chn < 2; chn++) {
      in = jack_port_get_buffer(input_ports[chn], nframes);
      if (NULL != in) {
        for (i = 0; i < nframes; i++, idx++) {
          data[chn][idx] = in[i];
        }
        if (chn == 0) {
          idx = idx2;
        }
      }
    }

    chunk++;
#ifdef DEBUG_JACKAUDIO
    printf("[i] JACK: chunk= %d\n", chunk);
#endif

    if (chunk == chunks) {
#ifdef DEBUG_JACKAUDIO
      printf("[i] JACK: setting input, idx= %d\n", idx);
#endif
      if (!xpthread_mutex_lock(&ctx->input->mutex)) {
        for (i = 0; i < ctx->input->size; i++) {
          ctx->input->data[A_LEFT][i]  = data[0][i];
          ctx->input->data[A_RIGHT][i] = data[1][i];
        }
        Input_set(ctx->input, A_STEREO);
        xpthread_mutex_unlock(&ctx->input->mutex);
        idx = chunk = 0;
      }
    }
  }

  return 0;
}


static void
setup_ports(void)
{
  input_ports = xcalloc(2, sizeof(jack_port_t *));

  for (int i = 0; i < 2; i++) {
    char name[64];

    sprintf(name, "input_%d", i);

    if ((input_ports[i] = jack_port_register(client, name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0)) == 0) {
      fprintf(stderr, "[!] JACK: cannot register input port \"%s\" !\n", name);
      jack_client_close(client);
      exit(1);
    } else {
      printf("[i] JACK: registered %s\n", name);
    }
  }

  ports = jack_get_ports(client, NULL, NULL, JackPortIsPhysical|JackPortIsOutput);
  if (NULL == ports) {
    xerror("JACK: no physical capture ports\n");
  }
}


int8_t
create(Context_t *ctx)
{
  if ((client = jack_client_open(PACKAGE, JackNullOption, NULL)) == 0) {
    xerror("JACK server not running ?\n");
  }

  jack_set_process_callback(client, process, ctx);
  jack_on_shutdown(client, jack_shutdown, ctx);

  json_t *input = Settings_get_input();
  if (NULL != input) {
    source_names[0] = json_string_value(json_object_get(input, "jackaudioLeft"));
    source_names[1] = json_string_value(json_object_get(input, "jackaudioRight"));
  } else {
    source_names[0] = JACKAUDIO_DEFAULT_LEFT;
    source_names[1] = JACKAUDIO_DEFAULT_RIGHT;
  }

  printf("[i] JACK: left  capture from %s\n", source_names[0]);
  printf("[i] JACK: right capture from %s\n", source_names[1]);

  setup_ports();

  jack_nframes_t jack_size = jack_get_buffer_size(client);
  printf("[i] JACK: buffer size: %d\n", jack_size);
  if (jack_size >= insize) {
    chunks = 1;
    insize = jack_size;
  } else {
    chunks = insize / jack_size;
  }
  printf("[i] JACK: %d chunks to read\n", chunks);

  ctx->input = Input_new(insize);
  data[0] = xcalloc(insize, sizeof(double));
  data[1] = xcalloc(insize, sizeof(double));

  if (jack_activate(client)) {
    xerror("JACK: cannot activate client\n");
  }

  for (uint8_t i = 0; i < 2; i++) {
    if (jack_connect(client, ports[i], jack_port_name(input_ports[i]))) {
      jack_client_close(client);
      xerror("JACK: can not connect input port %s to %s\n", jack_port_name(input_ports[i]), source_names[i]);
    } else {
      printf("[i] JACK: connected %s to %s\n", source_names[i], jack_port_name(input_ports[i]));
    }
  }
  jack_free(ports);

  return 1;
}


void
destroy(Context_t *ctx)
{
  jack_client_close(client);
  Input_delete(ctx->input);
  xfree(data[0]);
  xfree(data[1]);
  xfree(input_ports);
}
