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

/*-
 * Copyright (c) 1996 by Tim Auckland <tda10.geo@yahoo.com>
 * Incorporating some code from Stephen Davies Copyright (c) 2000
 *
 * Search code based on techniques described in "Strange Attractors:
 * Creating Patterns in Chaos" by Julien C. Sprott
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation.
 *
 * This file is provided AS IS with no warranties of any kind.  The author
 * shall have no liability with respect to the infringement of copyrights,
 * trade secrets or any patents by this file or any part thereof.  In no
 * event will the author be liable for any lost revenue or profits or
 * other special, indirect and consequential damages.
 *
 * "flow" shows a variety of continuous phase-space flows around strange
 * attractors.  It includes the well-known Lorentz mask (the "Butterfly"
 * of chaos fame), two forms of Rossler's "Folded Band" and Poincare'
 * sections of the "Birkhoff Bagel" and Duffing's forced occilator.  "flow"
 * can now discover new attractors.
 *
 * Revision History:
 *
 * 29-Oct-2004: [TDA] Discover Attractors unknown to science.
 *   Replace 2D rendering of Periodic Attractors with a 3D
 *   'interrupted' rendering.  Replace "-/+allow2d" with "-/+periodic"
 *   Replace all ODE formulae with completely generic forms.
 *   Add '-search' option to perform background high-speed discovery
 *   for completely new attractors without impacting rendering
 *   performance.
 *   Use gaussian distribution for initial point positions and for
 *   parameter search.
 *   Add "+dbuf" option to allow Double-Buffering to be turned off on
 *   slow X servers.
 *   Remove redundant '-zoom' option.  Now automatically zooms if both
 *   rotation and riding are permitted.
 *   Replace dynamic bounding box with static one pre-calculated
 *   during discovery phase.
 *   Simplify and fix bounding box clipping code.  Should now be safe
 *   to run without double buffer on all XFree86 servers if desired.
 * 12-Oct-2004: [TDA] Merge Xscreensaver and Xlockmore branches
 *   Added Chalky's orbital camera, but made the zooming work by
 *   flying the camera rather than interpolating the view transforms.
 *   Added Chalky's Bounding Box, but time-averaged the boundaries to
 *   let the lost bees escape.
 *   Added Chalky's 'view-frustrum' clipping, but only applying it to
 *   the Bounding Box.  Trails make clipping less useful.
 *   Added Chalky's "-slow" and "-freeze" options for compatibility,
 *   but haven't implemented the features, since the results are ugly
 *   and make no mathematical contribution.
 *   Added Double-Buffering as a work-around for a persistent XFree86
 *   bug that left debris on the screen.
 * 21-Mar-2003: [TDA] Trails added (XLockmore branch)
 * 01-Nov-2000: [TDA] Allocation checks (XLockmore branch)
 * 21-Feb-2000: [Chalky] Major hackage (Stephen Davies, chalky@null.net)
 *   (Xscreensaver branch)
 *   Forced perspective mode, added 3d box around attractor which
 *   involved coding 3d-planar-clipping against the view-frustrum
 *   thingy. Also made view alternate between piggybacking on a 'bee'
 *   to zooming around outside the attractor. Most bees slow down and
 *   stop, to make the structure of the attractor more obvious.
 * 28-Jan-1999: [TDA] Catch 'lost' bees in flow.c and disable them.
 *   (XLockmore branch)
 *   I chose to disable them rather than reinitialise them because
 *   reinitialising can produce fake attractors.
 *   This has allowed me to relax some of the parameters and initial
 *   conditions slightly to catch some of the more extreme cases.  As a
 *   result you may see some bees fly away at the start - these are the ones
 *   that 'missed' the attractor.  If the bee with the camera should fly
 *   away the mode will restart  :-)
 * 31-Nov-1998: [TDA] Added Duffing  (what a strange day that was :) DAB)
 *   Duffing's forced oscillator has been added to the formula list and
 *   the parameters section has been updated to display it in Poincare'
 *   section.
 * 30-Nov-1998: [TDA] Added travelling perspective option
 *   A more exciting point-of-view has been added to all autonomous flows.
 *   This views the flow as seen by a particle moving with the flow.  In the
 *   metaphor of the original code, I've attached a camera to one of the
 *   trained bees!
 * 30-Nov-1998: [TDA] Much code cleanup.
 * 09-Apr-1997: [TDA] Ported to xlockmore-4
 * 18-Jul-1996: Adapted from swarm.c Copyright (c) 1991 by Patrick J. Naughton.
 * 31-Aug-1990: Adapted from xswarm by Jeff Butterworth. (butterwo@ncsc.org).
 */
/* Adapted for Le Biniou by <olivier@biniou.info> */

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

/* it's pseudo-3d */
uint32_t version = 0;
uint32_t options = BO_GFX | BO_SCHEMES;
enum LayerMode mode = LM_OVERLAY;
char desc[] = "Flow effect";
char dname[] = "Flow";

#define MINTRAIL 2

static Timer_t *changeme = NULL;

typedef struct {
  double      x;
  double      y;
  double      z;
} dvector;

typedef struct {
  double      a, b, c;
} Par;

#define BMIN(a,b)((a)<(b)?(a):(b))

#define BCOUNT 256
#define NCOL   255

#define MAXRAND 2147483648.0
/* Macros */
#define IX(C)   ((C) * sp->beecount + sp->cnsegs[(C)])
#define B(t,b)  (*(sp->p + (t) + (b) * sp->taillen))
#define X(t,b)  B((t),(b)).x
#define Y(t,b)  B((t),(b)).y
#define Z(t,b)  B((t),(b)).z
#define balance_rand(v)  ((lrand48()/MAXRAND*(v))-((v)/2))  /* random around 0 */
#define SCALE_X(A) (WIDTH/2+WIDTH/sp->size*(A))
#define SCALE_Y(A) (HEIGHT/2+HEIGHT/sp->size*(A))
#define LOST_IN_SPACE (sp->size * 10)
#define LOST_IN_TIME  1E+40

#define NRAND(x) b_rand_uint32_range(0, x)
/* #define MRAND(x) (b_rand_uint32_range(0, x)) */
/* #define RAND(x) (b_rand_uint32_range(0, x)) */

#define NB_FLOW 8

static void fill_flow_struct(void);

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

/* parameters */
static enum Mode flow_mode = MODE_RANDOM;
static int flow_id = 0;

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, flow_mode, MODE_NB-1, "Mode");
  plugin_parameters_add_int(params, BPP_FLOW_ID, flow_id, 0, NB_FLOW-1, 1, "Flow type");

  return params;
}


void
set_parameters(const Context_t *ctx, const json_t *in_parameters)
{
  int reload = plugin_parameter_parse_int_range(in_parameters, BPP_FLOW_ID, &flow_id) & PLUGIN_PARAMETER_CHANGED;

  int mode_idx = 0;
  if (plugin_parameter_parse_string_list_as_int_range(in_parameters, BPP_MODE, MODE_NB, mode_list, &mode_idx)) {
    flow_mode = (enum Mode)mode_idx;
    reload = 1;
  }

  if (reload) {
    fill_flow_struct();
  }
}


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


typedef struct {
  int         count;
  double      size;
  int         taillen;

  int         beecount;  /* number of bees */
  Line_t     *csegs;     /* bee lines */
  int        *cnsegs;
  double      step;
  dvector     centre;    /* centre */
  struct {
    double  depth;
    double  height;
  }           view;
  dvector    *p;   /* bee positions x[time][bee#] */
  struct {
    double  theta;
    double  dtheta;
    double  phi;
    double  dphi;
  }           tumble;
  dvector  (*ODE) (Par par, double x, double y, double z);
  Par         par;
} flowstruct;

static flowstruct flow;


static dvector
Lorenz(Par par, double x, double y, double z)
{
  dvector d;

  d.x = par.a * (y - x);
  d.y = x * (par.b - z) - y;
  d.z = x * y - par.c * z;
  return d;
}


static dvector
Rossler(Par par, double x, double y, double z)
{
  dvector d;

  d.x = -(y + par.a * z);
  d.y = x + y * par.b;
  d.z = par.c + z * (x - 5.7);
  return d;
}


static dvector
RosslerCone(Par par, double x, double y, double z)
{
  dvector d;

  d.x = -(y + par.a * z);
  d.y = x + y * par.b - z * z * par.c;
  d.z = 0.2 + z * (x - 5.7);
  return d;
}


static dvector
Birkhoff(Par par, double x, double y, double z)
{
  dvector d;

  d.x = -y + par.b * sin(z);
  d.y = 0.7 * x + par.a * y * (0.1 - x * x);
  d.z = par.c;
  return d;
}


static dvector
Duffing(Par par, double x, double y, double z)
{
  dvector d;

  d.x = -par.a * x - y/2 - y * y * y/8 + par.b * cos(z);
  d.y = 2*x;
  d.z = par.c;
  return d;
}

#define deallocate(p,t) {free(p); p=(t*)NULL; }
#define allocate(p,t,s) if (NULL == (p=malloc(sizeof(t)*s))) \
    {free_flow(sp);return;}


static void
free_flow(flowstruct *sp)
{
  deallocate(sp->csegs, Line_t);
  deallocate(sp->cnsegs, int);
  deallocate(sp->p, dvector);
}


static void
fill_flow_struct(void)
{
  flowstruct *sp;
  int         b;
  double      beemult = 1 ;
  dvector     range;

  sp = &flow;
  sp->count = 0;
  sp->tumble.theta = balance_rand(M_PI);
  sp->tumble.phi = balance_rand(M_PI);
  sp->tumble.dtheta = 0.002;
  sp->tumble.dphi = 0.001;
  sp->view.height = 0;
  sp->view.depth = 0; /* no perspective view */
  sp->taillen = -10;

  if (sp->taillen < -MINTRAIL) {
    /* Change by sqr so its seems more variable */
    sp->taillen = NRAND((int) sqrt((double) (-sp->taillen - MINTRAIL + 1)));
    sp->taillen = sp->taillen * sp->taillen + MINTRAIL;
  } else if (sp->taillen < MINTRAIL) {
    sp->taillen = MINTRAIL;
  }

  switch (flow_id) {
    case 0:
      sp->view.depth = 10;
      sp->view.height = 0.2;
      beemult = 3;

    /* fallthrough */
    case 1:
      sp->ODE = Lorenz;
      sp->step = 0.02;
      sp->size = 60;
      sp->centre.x = 0;
      sp->centre.y = 0;
      sp->centre.z = 24;
      range.x = 5;
      range.y = 5;
      range.z = 1;
      sp->par.a = 10 + balance_rand(5);
      sp->par.b = 28 + balance_rand(5);
      sp->par.c = 2 + balance_rand(1);
      break;

    case 2:
      sp->view.depth = 10;
      sp->view.height = 0.1;
      beemult = 4;

    /* fallthrough */
    case 3:
      sp->ODE = Rossler;
      sp->step = 0.05;
      sp->size = 24;
      sp->centre.x = 0;
      sp->centre.y = 0;
      sp->centre.z = 3;
      range.x = 6;
      range.y = 6;
      range.z = 5;
      sp->par.a = 2 + balance_rand(1);
      sp->par.b = 0.2 + balance_rand(0.1);
      sp->par.c = 0.2 + balance_rand(0.1);
      break;

    case 4:
      sp->view.depth = 10;
      sp->view.height = 0.1;
      beemult = 3;

    /* fallthrough */
    case 5:
      sp->ODE = RosslerCone;
      sp->step = 0.05;
      sp->size = 24;
      sp->centre.x = 0;
      sp->centre.y = 0;
      sp->centre.z = 3;
      range.x = 6;
      range.y = 6;
      range.z = 6;
      sp->par.a = 2;
      sp->par.b = 0.2;
      sp->par.c = 0.331 + balance_rand(0.01);
      break;

    case 6:
      sp->ODE = Birkhoff;
      sp->step = 0.04;
      sp->size = 2.6;
      sp->centre.x = 0;
      sp->centre.y = 0;
      sp->centre.z = 0;
      range.x = 3;
      range.y = 4;
      range.z = 0;
      sp->par.a = 10 + balance_rand(5);
      sp->par.b = 0.35 + balance_rand(0.25);
      sp->par.c = 1.57;
      sp->tumble.theta = 0;
      sp->tumble.phi = 0;
      sp->tumble.dtheta = 0;
      sp->tumble.dphi = 0;
      break;

    case 7:
    default:
      sp->ODE = Duffing;
      sp->step = 0.02;
      sp->size = 30;
      sp->centre.x = 0;
      sp->centre.y = 0;
      sp->centre.z = 0;
      range.x = 20;
      range.y = 20;
      range.z = 0;
      sp->par.a = 0.2 + balance_rand(0.1);
      sp->par.b = 27.0 + balance_rand(3.0);
      sp->par.c = 1.33;
      sp->tumble.theta = 0;
      sp->tumble.phi = 0;
      sp->tumble.dtheta = -NRAND(2)*sp->par.c*sp->step;
      sp->tumble.dphi = 0;
      beemult = 0.5;
      break;
  }

  if (sp) {
    free_flow(sp);  /* beecount changes with beemult */
  }
  sp->beecount = (int) (beemult * BCOUNT);
  if (sp->beecount < 0) {                    /* random variations */
    sp->beecount = NRAND(-sp->beecount) + 1; /* Minimum 1 */
  }

  /* Allocate memory. */
  if (NULL == sp->csegs) {
    allocate(sp->csegs, Line_t, NCOL * sp->beecount * sp->taillen);
    allocate(sp->cnsegs, int, NCOL);
    allocate(sp->p, dvector, sp->beecount * sp->taillen);
  }

  /* Initialize point positions, velocities, etc. */
  for (b = 0; b < sp->beecount; b++) {
    X(1, b) = X(0, b) = balance_rand(range.x);
    Y(1, b) = Y(0, b) = balance_rand(range.y);
    Z(1, b) = Z(0, b) = balance_rand(range.z);
  }
}


void
on_switch_on(Context_t *ctx)
{
  flow_id = NRAND(NB_FLOW);
  fill_flow_struct();

  Timer_start(changeme);
}


int8_t
create(Context_t *ctx)
{
  changeme = Timer_new("flow");
  on_switch_on(ctx);

  return 1;
}


void
run(Context_t *ctx)
{
  int         b, i;
  int         col, begin, end;
  double      M[3][3]; /* transformation matrix */
  flowstruct *sp;
  Buffer8_t *dst = passive_buffer(ctx);

  Buffer8_clear(dst);

  sp = &flow;
  if (NULL == sp->csegs) {
    return;
  }

  if (!sp->view.depth) { /* simple 3D tumble */
    double      sint, cost, sinp, cosp;
    sp->tumble.theta += sp->tumble.dtheta;
    sp->tumble.phi += sp->tumble.dphi;
    sint = sin(sp->tumble.theta);
    cost = cos(sp->tumble.theta);
    sinp = sin(sp->tumble.phi);
    cosp = cos(sp->tumble.phi);
    M[0][0]= cost;
    M[0][1]=-sint*cosp;
    M[0][2]= sint*sinp;
    M[1][0]= sint;
    M[1][1]= cost*cosp;
    M[1][2]=-cost*sinp;
    M[2][0]= 0;
    M[2][1]= 0;
    M[2][2]= 1;
  } else { /* initialize matrix */
    M[0][0]= 0;
    M[0][1]= 0;
    M[0][2]= 0;
    M[1][0]= 0;
    M[1][1]= 0;
    M[1][2]= 0;
    M[2][0]= 0;
    M[2][1]= 0;
    M[2][2]= 0;
  }

  for (col = 0; col < NCOL; col++) {
    sp->cnsegs[col] = 0;
  }

  /* <=- Bees -=> */
  for (b = 0; b < sp->beecount; b++) {
    if (fabs(X(0, b)) > LOST_IN_SPACE ||
        fabs(Y(0, b)) > LOST_IN_SPACE ||
        /* if z is always invisible it's probably time */
        fabs(Z(0, b)) > ((sp->tumble.dphi!=0 ||
                          sp->tumble.phi!=0)?LOST_IN_SPACE:LOST_IN_TIME)) {
      if (sp->view.depth && b==0) { /* lost camera - reset */
        (void)create(ctx);
        return;
      }
      continue;
    }
    /* Age the tail. */
    for (i = sp->taillen - 1; i > 0; i--) {
      B(i, b) = B(i-1, b);
    }

    /* 2nd order Kunge Kutta */
    {
      dvector     k1, k2;

      k1 = sp->ODE(sp->par, X(1, b), Y(1, b), Z(1, b));
      k1.x *= sp->step;
      k1.y *= sp->step;
      k1.z *= sp->step;
      k2 = sp->ODE(sp->par, X(1, b) + k1.x, Y(1, b) + k1.y, Z(1, b) + k1.z);
      k2.x *= sp->step;
      k2.y *= sp->step;
      k2.z *= sp->step;
      X(0, b) = X(1, b) + (k1.x + k2.x) / 2.0;
      Y(0, b) = Y(1, b) + (k1.y + k2.y) / 2.0;
      Z(0, b) = Z(1, b) + (k1.z + k2.z) / 2.0;
    }

    /* Colour according to bee */
    col = b % (NCOL - 1) ;

    /* Fill the segment lists. */

    if (sp->view.depth) { /* perspective view has special points */
      if (b==0) { /* point of view */
        sp->centre.x=X(0, b);
        sp->centre.y=Y(0, b);
        sp->centre.z=Z(0, b);
      } else if (b==1) { /* neighbour: used to compute local axes */
        double x[3], p[3], x2=0, xp=0;
        int j;

        /* forward */
        x[0] = X(0, 0) - X(1, 0);
        x[1] = Y(0, 0) - Y(1, 0);
        x[2] = Z(0, 0) - Z(1, 0);

        /* neighbour */
        p[0] = X(0, 1) - X(1, 0);
        p[1] = Y(0, 1) - Y(1, 0);
        p[2] = Z(0, 1) - Z(1, 0);

        for (i=0; i<3; i++) {
          x2+= x[i]*x[i];    /* X . X */
          xp+= x[i]*p[i];    /* X . P */
          M[0][i] = x[i];    /* X */
        }

        for (i=0; i<3; i++) {             /* (X x P) x X */
          M[1][i] = x2*p[i] - xp*x[i];  /* == (X . X) P - (X . P) X */
        }

        M[2][0] =  x[1]*p[2] - x[2]*p[1]; /* X x P */
        M[2][1] = -x[0]*p[2] + x[2]*p[0];
        M[2][2] =  x[0]*p[1] - x[1]*p[0];

        /* normalise axes */
        for (j=0; j<3; j++) {
          double A=0;
          for (i=0; i<3; i++) {
            A+=M[j][i]*M[j][i];  /* sum squares */
          }
          A=sqrt(A);
          for (i=0; i<3; i++) {
            M[j][i]/=A;
          }
        }

        X(0, 1)=X(0, 0)+M[1][0]; /* adjust neighbour */
        Y(0, 1)=Y(0, 0)+M[1][1];
        Z(0, 1)=Z(0, 0)+M[1][2];
      }
    }

    begin = 0; /* begin new trail */
    end = BMIN(sp->taillen, sp->count);
    for (i=0; i < end; i++) {
      double x=X(i,b)-sp->centre.x;
      double y=Y(i,b)-sp->centre.y;
      double z=Z(i,b)-sp->centre.z;
      double XM=M[0][0]*x + M[0][1]*y + M[0][2]*z;
      double YM=M[1][0]*x + M[1][1]*y + M[1][2]*z;
      double ZM=M[2][0]*x + M[2][1]*y + M[2][2]*z+sp->view.height;
      double absx, absy;
      if (sp->view.depth) {
        if (XM <= 0) {
          /* off screen - new trail */
          begin = i + 1;
          continue;
        }
        absx=SCALE_X(sp->view.depth*YM/XM);
        absy=SCALE_Y(sp->view.depth*ZM/XM);
      } else {
        absx=SCALE_X(XM);
        absy=SCALE_Y(YM);
      }
      if (absx <= 0 || absx >= WIDTH ||
          absy <= 0 || absy >= HEIGHT) {
        /* off screen - new trail */
        begin = i + 1;
        continue;
      }
      if (i > begin) {  /* complete previous segment */
        sp->csegs[IX(col)].x2 = (short) absx;
        sp->csegs[IX(col)].y2 = (short) absy;
        sp->cnsegs[col]++;
      }
      if (i < end -1) { /* start new segment */
        sp->csegs[IX(col)].x1 = (short) absx;
        sp->csegs[IX(col)].y1 = (short) absy;
      }
    }
  }

  for (col = 0; col < NCOL; col++) {
    if (sp->cnsegs[col] > 0)
      for (b = 0; b < sp->cnsegs[col]; b++) {
        /*Line *l = &sp->csegs[b] + col * sp->beecount;*/
        Line_t *l = &sp->csegs[sp->beecount * col + b];
        /*if ((l->x1 > 0) && (l->x1 < WIDTH))*/
        /*
         * if ((l->x1 < MINX) || (l->x1 > MAXX) || (l->y1 < MINY) || (l->y1 > MAXY)
         || (l->x2 < MINX) || (l->x2 > MAXX) || (l->y2 < MINY) || (l->y2 > MAXY))
         continue;
        */
        draw(dst, l, (Pixel_t)col);
      }
    /*draw_line_3d (Buffer8_t *, const Point3d *, const Point3d *, const guchar);*/
  }
  /*for (b = 0; b < sp->cnsegs[col]; b++) {

  if (++sp->count > 512)*/
  ++sp->count;
  if (flow_mode == MODE_RANDOM && Timer_elapsed(changeme) > 30) {
    on_switch_on(ctx);
    Context_websocket_send_sequence(ctx);
  }
}


void
destroy(Context_t *ctx)
{
  free_flow(&flow);
  Timer_delete(changeme);
}
