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

/*
 * This plugin in memoriam of Laurent "Lolo" Fileux.
 * Wherever you are now, it was a great experience hacking
 * with you. Rest in peace.
 */

#include "context.h"
#include "parameters.h"
#include "pthread_utils.h"


uint32_t version = 0;
uint32_t options = BO_GFX | BO_SCHEMES;
enum LayerMode mode = LM_OVERLAY;
char desc[] = "Rotors effect";
char dname[] = "Rotors";

/*
 * Display a rotors tree
 *
 * 4 levels of rotors in the tree, each rotor has 2 sons:
 * 1 + 2 + 4 + 8 = 15 rotors max
 *
 * 1      0      root
 * 2    /   \    2
 * 3   /\   /\   4
 * 4  /\/\ /\/\  8
 *
 * the first three levels (1+2+4=7 rotors) are not displayed,
 * so [1..8] rotors can be displayed
 */
#define MAX_ROTORS         15
#define SKIP_ROTORS        7
#define DISPLAYABLE_ROTORS (MAX_ROTORS - SKIP_ROTORS)


typedef struct Rotor {
  struct Rotor *fg, *fd;
  _Complex float centre;
  float rayon, freq, freq_var, ampl_var, freq_coul;
  uint8_t visible;
  Pixel_t coul;
} Rotor;

/*
 * float min_rnd_factor, max_rnd_factor;
 uint16_t length, nb_rotors, min_color, max_color;
 float freq_base_moy, freq_base_ect;
 float freq_var_moy, freq_var_ect;
 float ampl_var_moy, ampl_var_ect;
 float proba_visible, rotor_time;
 int max_prof;

 Rotor tab[MAX_ROTORS];
 float time_step;
*/

static _Complex float ci;
static float min_rnd_factor, max_rnd_factor;
static uint8_t nb_rotors, min_color, max_color;
static float freq_base_moy, freq_base_ect;
static float freq_var_moy, freq_var_ect;
static float ampl_var_moy, ampl_var_ect;
static float rotor_time;
static int max_prof;

static Rotor tab[MAX_ROTORS];
static float time_step;

static int nb_min_rotors = 0;
static double proba_visible = 0;
static int length = 0;
static double speed = 0;
static double scale = 0;
static pthread_mutex_t mutex;


static void post_init(void);


json_t *
get_parameters(const uint8_t fetch_all)
{
  json_t *params = json_object();

  plugin_parameters_add_int(params, BPP_NB_MIN_ROTORS, nb_min_rotors, 1, 8, 1, "Minimum number of rotors");
  plugin_parameters_add_double(params, BPP_PROBA_VISIBLE, proba_visible, 0.05, 1, 0.01, "Visibility probability");
  plugin_parameters_add_int(params, BPP_LENGTH, length, 10, 1000, 10, "Length");
  plugin_parameters_add_double(params, BPP_SPEED, speed, -10, 10, 0.01, "Speed");
  plugin_parameters_add_double(params, BPP_SCALE, scale, 0, 2, 0.01, "Scale");

  return params;
}


void
set_parameters(const Context_t *ctx, const json_t *in_parameters)
{
  int reload = 0;

  reload |= plugin_parameter_parse_int_range(in_parameters, BPP_NB_MIN_ROTORS, &nb_min_rotors) & PLUGIN_PARAMETER_CHANGED;
  reload |= plugin_parameter_parse_double_range(in_parameters, BPP_PROBA_VISIBLE, &proba_visible) & PLUGIN_PARAMETER_CHANGED;
  plugin_parameter_parse_int_range(in_parameters, BPP_LENGTH, &length);
  plugin_parameter_parse_double_range(in_parameters, BPP_SPEED, &speed);
  reload |= plugin_parameter_parse_double_range(in_parameters, BPP_SCALE, &scale) & PLUGIN_PARAMETER_CHANGED;

  if (reload) {
    post_init();
  }
}


json_t *
parameters(const Context_t *ctx, const json_t *in_parameters, const uint8_t fetch_all)
{
  if (NULL != in_parameters) {
    set_parameters(ctx, in_parameters);
  }

  return get_parameters(fetch_all);
}


/* note: this is _NOT_ a perfect binary-tree,
 * ie a node might have 1 or 2 sons
 */
static int
prof_rotor_tree(Rotor *rotor)
{
  int pg, pd;

  if (NULL == rotor) {
    return 0;
  }

  pg = prof_rotor_tree(rotor->fg);
  pd = prof_rotor_tree(rotor->fd);

  return 1 + ((pg > pd) ? pg : pd);
}


static void
build_rotor_tree(void)
{
  int i;
  int *arite;

  for (i = 0; i < nb_rotors; i++) {
    tab[i].fg = tab[i].fd = NULL;
  }

  arite = xcalloc(nb_rotors, sizeof(int));
  arite[0] = 2;

  for (i = 1; i < nb_rotors; i++) {
    /* Tirage au sort du pere a la roulette */
    int hasard = (int)(drand48() * i), j = 0;

    while ((j < i) && (hasard >= 0)) {
      hasard -= arite[j];
      j++;
    }
    j--;

    /* Le resultat de la roulette est dans 'j' */
    arite[j]--;
    if (NULL == tab[j].fg) {
      tab[j].fg = tab + i;
    } else if (NULL == tab[j].fd) {
      tab[j].fd = tab + i;
    }
    arite[i] = 2;
  }

  xfree(arite);
  max_prof = prof_rotor_tree(tab);
}


static void
init_rotor_tree(Rotor *rotor, float max_ray)
{
  int prof = prof_rotor_tree(rotor);

  rotor->rayon = (drand48() * (max_rnd_factor - min_rnd_factor) + min_rnd_factor) * (float)max_ray / (float)prof;
  if (NULL != rotor->fg) {
    init_rotor_tree(rotor->fg, max_ray - rotor->rayon);
  }
  if (NULL != rotor->fd) {
    init_rotor_tree(rotor->fd, max_ray - rotor->rayon);
  }
}


static void
build(void)
{
  build_rotor_tree();
  init_rotor_tree(tab, scale * MINSCREEN);
}


static inline float
norm_freq(void)
{
  float d = (float)(drand48() * 2.0 - 1.0);

  return freq_var_moy + freq_var_ect * d;
}


static inline float
norm_ampl(void)
{
  float d = (float)(drand48() * 2.0 - 1.0);

  return ampl_var_moy + ampl_var_ect * d;
}


static inline float
norm_freq_base(void)
{
  float d = (float)(drand48() * 2.0 - 1.0);

  return freq_base_moy + freq_base_ect * d;
}


static inline Pixel_t
color(void)
{
  float d = drand48() * (max_color - min_color) + min_color;

  return (Pixel_t)d;
}


static void
set_random_visible(void)
{
  uint8_t n_visible = 0;
  VERBOSE(printf("[r] Building rotors, minimum %d: ", nb_min_rotors));

  for (uint8_t i = 0; i < nb_rotors; i++)  {
    tab[i].visible = (drand48() <= proba_visible);
    if (tab[i].visible) {
      n_visible++;
      VERBOSE(printf("rnd %d ", n_visible));
    }
  }

  while (n_visible < nb_min_rotors) {
    uint8_t rnd;
    do {
      rnd  = b_rand_uint32_range(7, MAX_ROTORS);
    }
    while (tab[rnd].visible);
    tab[rnd].visible = 1;
    n_visible++;
    VERBOSE(printf("add %d ", n_visible));
  }
  VERBOSE(printf("done, %d rotors\n", n_visible));
}


static void
post_init(void)
{
  if (!xpthread_mutex_lock(&mutex)) {
    for (int i = 0; i < nb_rotors; i++)  {
      tab[i].coul      = color();
      tab[i].freq      = norm_freq_base();
      tab[i].freq_var  = norm_freq();
      tab[i].ampl_var  = norm_ampl();
    }
    set_random_visible();

    rotor_time = 0;
    build();
    xpthread_mutex_unlock(&mutex);
  }
}


void on_switch_on(Context_t *);


int8_t
create(Context_t *ctx)
{
  xpthread_mutex_init(&mutex, NULL);

  ci = cexp(I*M_PI/2);
  /* printf("Rotors:  %f+%f*i\n", creal(ci), cimag(ci)); */

  nb_rotors = MAX_ROTORS;
  min_color = 200;
  max_color = 250;
  min_rnd_factor = 0.3;
  max_rnd_factor = 2;
  freq_base_moy = 0;
  freq_base_ect = 70;
  freq_var_moy = 0.6;
  freq_var_ect = 0.005;
  ampl_var_moy = 0;
  ampl_var_ect = 0.1;
  time_step = 0.00001;

  on_switch_on(ctx);

  return 1;
}


void
destroy(Context_t *ctx)
{
  xpthread_mutex_destroy(&mutex);
}


static void
refresh(Rotor *rotor)
{
  if ((NULL != rotor->fg) || (NULL != rotor->fd))  {
    float f, arg, alpha;
    _Complex float pos_rel;

    f = rotor->freq * exp(rotor->ampl_var * cos(2 * M_PI * rotor->freq_var * rotor_time));
    arg = f * rotor_time;
    arg -= (long)(arg);
    alpha = 2 * M_PI * arg;

    pos_rel = rotor->rayon * cexp(ci * alpha);

    if (NULL != rotor->fg) {
      rotor->fg->centre = rotor->centre + pos_rel;
      refresh(rotor->fg);
    }
    if (NULL != rotor->fd) {
      rotor->fd->centre = rotor->centre - pos_rel;
      refresh(rotor->fd);
    }
  }
}


static void
display(Context_t *ctx)
{
  uint16_t i;
  Buffer8_t *dst = passive_buffer(ctx);

  for (i = 7; i < MAX_ROTORS; i++)
    /* pas la racine ni les 2 premiers niveaux -> 1+2+4 rotors non affiches */
    if (tab[i].visible) {
      short x, y;
      x = (short)(creal(tab[i].centre) + CENTERX);
      y = (short)(cimag(tab[i].centre) + CENTERY);

#if 1
      set_pixel(dst, x, y, tab[i].coul);
#else /* testing neg_pixel, if it's nice we can cleanup tab[i].coul etc */
      neg_pixel(dst, x, y);
#endif
    }
}


void
run(Context_t *ctx)
{
  Buffer8_clear(passive_buffer(ctx));

  if (!xpthread_mutex_lock(&mutex)) {
    for (int i = 0; i < length; i++) {
      rotor_time += time_step * speed;
      refresh(tab);
      display(ctx);
    }
    xpthread_mutex_unlock(&mutex);
  }
}

/*
  SAMPLE config from the v1.O

  ROTORS 12
  MIN_COLOR 200
  MAX_COLOR 250
  MIN_RND_FACTOR 0.3
  MAX_RND_FACTOR 2
  FRQ_BASE_MOY 0
  FRQ_BASE_ECT 70
  FRQ_VARIANCE_MOY 0.6
  FRQ_VARIANCE_ECT 0.005
  AMP_VARIANCE_MOY 0
  AMP_VARIANCE_ECT 0.1
  PROBA_VISIBLE 0.15
  TIME_STEP 0.00001
  TRAIL_LENGTH 200

*/

void
on_switch_on(Context_t *ctx)
{
  nb_min_rotors = b_rand_uint32_range(3, 6);
  proba_visible = 0.05;
  length = 200;
  speed = 1;
  scale = 1;
  post_init();
}
