/*
 *  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 "context.h"
#include "pthread_utils.h"


uint32_t options = BO_NONE;
uint32_t version = 0;

#define INSIZE  256
#define FACT    0.40
#define DEVICE  "/dev/urandom"

#define SAMPLES_PER_SECOND  4410.0
#define MIN_FREQUENCY       80.0
#define MAX_FREQUENCY       (SAMPLES_PER_SECOND / 2)

#define MIN_SPEED  0.1
#define MAX_SPEED  (MAX_FREQUENCY / 100)

#define CHANNELS       2
#define CHANNEL_LEFT   0
#define CHANNEL_RIGHT  1

typedef struct {
  float  sample[CHANNELS];
} StereoSample;

static StereoSample *buffer = NULL;

static int  urandfd = -1;

static float  frequency[CHANNELS];
static float  scale[CHANNELS];
static float  speed[CHANNELS];
static float  xtime[CHANNELS];

/* return a random float number from 0.0 to 1.0 (included) */
static inline float
Rndq(void)
{
  uint32_t  a, b;
  ssize_t res;
  res = read(urandfd, (void *)&a, sizeof(uint32_t));
  if (res == -1) {
    xperror("read");
  }
  b = (~0);
  return (((float)a) / ((float)b));
}

/* return a random float number from 0.0 to x (included) */
static inline float
Rnd(float x)
{
  return (Rndq() * x);
}

/* return a random float number from x1 to x2 (included) */
static inline float
RangeRnd(float x1, float x2)
{
  return (Rnd(x2 - x1) + x1);
}

/* return a random uint32_t number, from 0 to ULONG_MAX */
static inline uint32_t
Randq(void)
{
  uint32_t  a;
  ssize_t res;
  res = read(urandfd, (void *)&a, sizeof(uint32_t));
  if (res == -1) {
    xperror("read");
  }
  return a;
}

/* return a random uint32_t number, from 0 to (range - 1) */
static inline uint32_t
Rand(uint32_t range)
{
  uint32_t  a = Randq();
  if (range != 0) {
    a %= range;
  }

  return a;
}

/* return a random uint32_t number, from n1 to n2 (included) */
/*static inline uint32_t
  RangeRand(uint32_t n1, uint32_t n2)
  {
  uint32_t  mi, ma;
  mi = (n1 <= n2 ? n1 : n2); // min of (n1,n2)
  ma = (n1 >= n2 ? n1 : n2); // max of (n1,n2)
  return (Rand(ma - mi + 1) + mi);
  } <-- unused here */


int8_t
create(Context_t *ctx)
{
  int  i;

  buffer = (StereoSample *) xcalloc(INSIZE, sizeof(StereoSample));
  if (NULL == buffer) {
    xerror("Unable to create samples buffer\n");
  }

  urandfd = open(DEVICE, O_RDONLY);
  if (urandfd == -1) {
    xerror("Unable to open `%s'\n", DEVICE);
  }

  ctx->input = Input_new(INSIZE);

  /* initialize twip */
  for (i = 0; i < CHANNELS; i++) {
    frequency[i] = RangeRnd(MIN_FREQUENCY, MAX_FREQUENCY);
    scale[i] = RangeRnd(-1, 1);
    speed[i] = RangeRnd(MIN_SPEED, MAX_SPEED);
    xtime[i] = Rndq();
  }

  return 1;
}


void
destroy(Context_t *ctx)
{
  Input_delete(ctx->input);
  if (urandfd != -1) {
    close(urandfd);
  }
  xfree(buffer);
}


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

  while (ctx->running) {
    int  j;

    for (j = 0; j < INSIZE; j++) {
      int  i;

      for (i = 0; i < CHANNELS; i++) {
        float  fr, newfr, sc, sp, t, y;

        /* get twip values */
        fr = frequency[i];
        sc = scale[i];
        sp = speed[i];
        t = xtime[i];

        /* compute sample */
        buffer[j].sample[i] = sinf(t * 2 * M_PI) * FACT;

        /* modify speed */
        if (Rand(SAMPLES_PER_SECOND / 100) == nearbyintf(MAX_FREQUENCY / 200)) {
          y = RangeRnd(-1, 1) / SAMPLES_PER_SECOND;
          sp += y;
          if ((sp > MAX_SPEED) || (sp < MIN_SPEED)) {
            sp -= (y * 2);
          }
        }

        /* modify frequency and/or scale */
        while (1) {
          newfr = powf(2, sc * sp / SAMPLES_PER_SECOND / 12) * fr;
          if (newfr < MIN_FREQUENCY) {
            sc = Rndq();
          } else if (newfr > MAX_FREQUENCY) {
            sc = -Rndq();
          } else if (Rand(SAMPLES_PER_SECOND / 10) == nearbyintf(MAX_FREQUENCY / 20)) {
            sc = RangeRnd(-1, 1);
          } else {
            break;
          }
        }

        /* update twip */
        frequency[i] = newfr;
        scale[i] = sc;
        speed[i] = sp;
        t += (newfr / SAMPLES_PER_SECOND);
        xtime[i] = (t - floorf(t));
      }
    }

    if (!ctx->input->mute) {
      if (!xpthread_mutex_lock(&ctx->input->mutex)) {
        for (j = 0; j < INSIZE; j++) {
          ctx->input->data[A_LEFT ][j] = buffer[j].sample[CHANNEL_LEFT ];
          ctx->input->data[A_RIGHT][j] = buffer[j].sample[CHANNEL_RIGHT];
        }

        /* buffer written, build FFTs, etc. */
        Input_set(ctx->input, A_STEREO);
        xpthread_mutex_unlock(&ctx->input->mutex);
      }
    }
  }

  /* that's all, folks ! */
  return NULL;
}
