/*
 *  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 "infinity.h"


uint32_t version = 0;
uint32_t options = BO_DISPLACE | BO_WARP;
char dname[] = "Infinity";
char desc[] = "Infinity effect";


/* Infinity plugin port.
 * Original source code has been heavily modified, to take only
 * the vector fields. So modified it's nearly a rewrite.
 * Changes have also been made to reflect style(9).
 * See the original infinity plugin source code for exact details:
 * https://github.com/dprotti/infinity-plugin
 */
#define NB_FCT 6

static Shuffler_t *shuffler = NULL;
static VectorField_t *vf = NULL;
static Timer_t *timer = NULL;

enum Mode { MODE_SELECTED = 0, MODE_RANDOM, MODE_NB } Mode_e;
const char *mode_list[MODE_NB] = { "Selected", "Random" };

/* parameters */
static enum Mode mode = MODE_RANDOM;
static int effect = 0;
static int delay = 5;


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

  plugin_parameters_add_string_list(params, BPP_MODE, MODE_NB, mode_list, mode, MODE_NB-1, "Mode");
  plugin_parameters_add_int(params, BPP_EFFECT, effect, 0, NB_FCT-1, 1, "Effect");
  plugin_parameters_add_int(params, BPP_DELAY, delay, 1, 60, 1, "Delay");

  return params;
}


void
set_parameters(const Context_t *ctx, const json_t *in_parameters)
{
  plugin_parameter_parse_int_range(in_parameters, BPP_EFFECT, &effect);
  if (plugin_parameter_parse_int_range(in_parameters, BPP_DELAY, &delay) & PLUGIN_PARAMETER_CHANGED) {
    Timer_start(timer);
  }

  if (plugin_parameter_parse_string_list_as_int_range(in_parameters, BPP_MODE, MODE_NB, mode_list, (int *)&mode) & PLUGIN_PARAMETER_CHANGED) {
    if (mode == MODE_RANDOM) {
      effect = Shuffler_get(shuffler);
    }
  }
}


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);
}


static t_complex
fct(t_complex a, guint32 n, gint32 p1, gint32 p2)   /* p1 et p2:0-4 */
{
  t_complex b;
  gfloat fact;
  gfloat an;
  gfloat circle_size;
  gfloat speed;
  gfloat co,si;

  a.x -= HWIDTH;
  a.y -= HHEIGHT;

  switch (n) {
    case 0:
      an = 0.025*(p1-2)+0.002;
      co = cosf(an);
      si = sinf(an);
      circle_size = HEIGHT*0.25;
      speed = (gfloat)2000+p2*500;
      b.x = (co*a.x-si*a.y);
      b.y = (si*a.x+co*a.y);
      fact = -(sqrtf(b.x*b.x+b.y*b.y)-circle_size)/speed+1;
      b.x = (b.x*fact);
      b.y = (b.y*fact);
      break;

    case 1:
      an = 0.015*(p1-2)+0.002;
      co = cosf(an);
      si = sinf(an);
      circle_size = HEIGHT*0.45;
      speed = (gfloat)4000+p2*1000;
      b.x = (co*a.x-si*a.y);
      b.y = (si*a.x+co*a.y);
      fact = (sqrtf(b.x*b.x+b.y*b.y)-circle_size)/speed+1;
      b.x = (b.x*fact);
      b.y = (b.y*fact);
      break;

    case 2:
      an = 0.002;
      co = cosf(an);
      si = sinf(an);
      circle_size = HEIGHT*0.25;
      speed = (gfloat)400+p2*100;
      b.x = (co*a.x-si*a.y);
      b.y = (si*a.x+co*a.y);
      fact = -(sqrtf(b.x*b.x+b.y*b.y)-circle_size)/speed+1;
      b.x = (b.x*fact);
      b.y = (b.y*fact);
      break;

    case 3:
      an = (sinf(sqrtf(a.x*a.x+a.y*a.y)/20)/20)+0.002;
      co = cosf(an);
      si = sinf(an);
      circle_size = HEIGHT*0.25;
      speed = (gfloat)4000;
      b.x = (co*a.x-si*a.y);
      b.y = (si*a.x+co*a.y);
      fact = -(sqrtf(b.x*b.x+b.y*b.y)-circle_size)/speed+1;
      b.x = (b.x*fact);
      b.y = (b.y*fact);
      break;

    case 4:
      an = 0.002;
      co = cosf(an);
      si = sinf(an);
      circle_size = HEIGHT*0.25;
      speed = sinf(sqrtf(a.x*a.x+a.y*a.y)/5)*3000+4000;
      b.x = (co*a.x-si*a.y);
      b.y = (si*a.x+co*a.y);
      fact = -(sqrtf(b.x*b.x+b.y*b.y)-circle_size)/speed+1;
      b.x = (b.x*fact);
      b.y = (b.y*fact);
      break;

    case 5:
      an = 0.002;
      co = cosf(an);
      si = sinf(an);
      circle_size = HEIGHT*0.25;
      fact = 1+cosf(atanf(a.x/(a.y+0.00001))*6)*0.02;
      b.x = (co*a.x-si*a.y);
      b.y = (si*a.x+co*a.y);
      b.x = (b.x*fact);
      b.y = (b.y*fact);
      break;

    default:
      b.x = 0.0;
      b.y = 0.0;
  }

  b.x += HWIDTH;
  b.y += HHEIGHT;

  /* because infinity access pixels at (b.x + 1, b.y + 1) */
  b.x = MIN( MAX(b.x, 0), MAXX-1);
  b.y = MIN( MAX(b.y, 0), MAXY-1);

  return b;
}


int8_t
create(Context_t *ctx)
{
  vf = VectorField_new(NB_FCT, &fct);
  timer = Timer_new("infinity");
  shuffler = Shuffler_new(NB_FCT);

  mode = MODE_RANDOM;
  effect = 0;

  return 1;
}


void
destroy(Context_t *ctx)
{
  VectorField_delete(vf);
  Timer_delete(timer);
  Shuffler_delete(shuffler);
}


void
on_switch_on(Context_t *ctx)
{
  delay = b_rand_uint32_range(5, 21);
  effect = Shuffler_get(shuffler);
  if (b_rand_boolean()) {
    mode = MODE_SELECTED;
  } else {
    mode = MODE_RANDOM;
    Timer_start(timer);
  }
}


void
run(Context_t *ctx)
{
  VectorField_run(vf, ctx, effect);

  if ((mode == MODE_RANDOM) && (Timer_elapsed(timer) > delay)) {
    on_switch_on(ctx);
  }
}
