
/*
 *  Copyright 1994-2022 Olivier Girondel
 *  Copyright 2021-2022 Jérémie Astor
 *
 *  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 <lo/lo.h>
#include "context.h"
#include "settings.h"
#include "pthread_utils.h"

// plugin must define option and version
uint32_t options = BO_NONE;
uint32_t version = 0;

// default input size if not defined by the LEBINIOU_OSC_BUFSIZE environment variable
#define MIN_BUF_SIZE 256
#define MAX_BUF_SIZE 4096
#define INSIZE 1024
static uint16_t insize = INSIZE;

// default port for the OSC server if not defined by the LEBINIOU_OSC_PORT environment variable
#define DEFAULT_PORT "9999"

// get the position inside the input buffer
static uint16_t count = 0;

// OSC server
static lo_server_thread thread = NULL;

// internal input data
static double *input_l = NULL;
static double *input_r = NULL;

static void
osc_error_handler(int num, const char *msg, const char *where)
{
  fprintf(stderr, "[!] OSC: problem %i: %s %s !\n", num, msg, where);
}


static int
input_handler(const char *path, const char *type, lo_arg **argv,
              int argc, lo_message msg, void *data)
{
  Context_t *ctx = (Context_t*)data;

  assert(argc == 2);
  assert(NULL != argv[0]);
  assert(NULL != argv[1]);

  input_l[count] = argv[0]->d;
  input_r[count] = argv[1]->d;

  if (++count == insize) {

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

        for (uint16_t idx = 0; idx < insize; idx++) {
          ctx->input->data[A_LEFT][idx] = input_l[idx];
          ctx->input->data[A_RIGHT][idx] = input_r[idx];
        }

        Input_set(ctx->input, A_STEREO);

        xpthread_mutex_unlock(&ctx->input->mutex);
      }
    }
    count = 0;
  }

  return 0;
}


static const char *
get_port(void)
{
  const char *port = getenv("LEBINIOU_OSC_PORT");
  if (NULL != port) {

    return port;
  }

  return DEFAULT_PORT;
}


static int
init_osc(const Context_t *ctx)
{
  const char *port = get_port();

  thread = lo_server_thread_new(port, osc_error_handler);
  if (NULL == thread) {
    fprintf(stderr, "[!] OSC: cannot create server at port \"%s\" !\n", port);

    return 1;
  }
  if (lo_server_thread_start(thread)) {
    fprintf(stderr, "[!] OSC: cannot start server\n");

    return 1;
  }
  if (NULL == lo_server_thread_add_method(thread, "/lebiniou/audioinput", "dd", input_handler, (void *)ctx)) {
    fprintf(stderr, "[!] OSC: cannot create method\n");

    return 1;
  }

  return 0;
}


static uint16_t
sanitize_size(const uint16_t insize) {
  if (insize > MIN_BUF_SIZE && insize < MAX_BUF_SIZE) {

    return insize;
  }

  fprintf(stderr, "[!] OSC: invalid buffer size requested, using default value (%u)\n", INSIZE);

  return INSIZE;
}


int8_t
create(Context_t *ctx)
{
  const char *user_insize = getenv("LEBINIOU_OSC_BUFSIZE");
  const uint16_t req_insize = user_insize ? atoi(user_insize) : INSIZE;
  insize = sanitize_size(req_insize);

  ctx->input = Input_new(insize);
  input_l = xmalloc(sizeof(double) * insize);
  input_r = xmalloc(sizeof(double) * insize);

  if (init_osc(ctx)) {
    exit(1);
  }

  return 1;
}


void
destroy(Context_t *ctx)
{
  lo_server_thread_del_method(thread, "/lebiniou/audioinput", "dd");
  lo_server_thread_stop(thread);
  lo_server_thread_free(thread);
  Input_delete(ctx->input);
  xfree(input_l);
  xfree(input_r);
}
