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

/* swarm --- swarm of bees */

/*-
 * Copyright (c) 1991 by Patrick J. Naughton.
 *
 * 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.
 *
 * Revision History:
 * 01-Nov-2000: Allocation checks
 * 13-Jul-2000: Bee trails implemented by Juan Heguiabehere <jheguia@usa.net>
 * 10-May-1997: Compatible with xscreensaver
 * 31-Aug-1990: Adapted from xswarm by Jeff Butterworth <butterwo@ncsc.org>
 */

#include "context.h"


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

#define MINBEES  1    /* min number of bees */
#define MINTRAIL 3    /* min number of time positions recorded */
#define BEEACC   3    /* acceleration of bees */
#define WASPACC  5    /* maximum acceleration of wasp */
#define BEEVEL  17    /* maximum bee velocity */
#define WASPVEL 15    /* maximum wasp velocity */

#define NRAND(x) (b_rand_uint32_range(0, x))

/* Macros */
#define X(t,b)  (swarm.x[((t)*swarm.beecount+(b))])
#define Y(t,b)  (swarm.y[((t)*swarm.beecount+(b))])
#define balance_rand(v)  ((NRAND(v))-((v-1)/2))  /* random number around 0, input odd */


typedef struct {
  int         pix;
  int         border;   /* wasp won't go closer than this to the edge */
  int         beecount; /* number of bees */
  Line_t      *segs;    /* bee lines */
  float       *x, *y;   /* bee positions x[time][bee#] */
  float       *xv, *yv; /* bee velocities xv[bee#] */
  short       wx[3];
  short       wy[3];
  short       wxv;
  short       wyv;
  short       tick;
  short       rolloverflag;
  short       taillen;
} swarmstruct;


static swarmstruct swarm;


int8_t
create(Context_t *ctx)
{
  int b, t;

  swarm.beecount = 100;
  swarm.taillen = 100;
  swarm.border = 50;
  swarm.tick = 0;
  swarm.rolloverflag = 0;

  /* Allocate memory. */
  swarm.segs = xmalloc(sizeof(Line_t) * swarm.beecount);
  swarm.x = xmalloc(sizeof(float) * swarm.beecount * swarm.taillen);
  swarm.y = xmalloc(sizeof(float) * swarm.beecount * swarm.taillen);
  swarm.xv = xmalloc(sizeof(float) * swarm.beecount);
  swarm.yv = xmalloc(sizeof(float) * swarm.beecount);

  /* Initialize point positions, velocities, etc. */
  /* wasp */
  swarm.wx[0] = swarm.border + NRAND(WIDTH - 2 * swarm.border);
  swarm.wy[0] = swarm.border + NRAND(HEIGHT - 2 * swarm.border);
  swarm.wx[1] = swarm.wx[0];
  swarm.wy[1] = swarm.wy[0];
  swarm.wxv = 0;
  swarm.wyv = 0;

  /* bees */
  for (b = 0; b < swarm.beecount; b++) {
    X(0, b) = NRAND(WIDTH);
    Y(0, b) = NRAND(HEIGHT);
    for (t = 1; t < swarm.taillen; t++) {
      X(t, b) = X(0, b);
      Y(t, b) = Y(0, b);
    }
    swarm.xv[b] = balance_rand(7);
    swarm.yv[b] = balance_rand(7);
  }

  return 1;
}


void
run(Context_t *ctx)
{
  int       b, newlimit;
  short     prev;
  float     speed;
  Buffer8_t *dst = passive_buffer(ctx);

  Buffer8_clear(dst);

  /* <=- Wasp -=> */
  /* Age the arrays. */
  swarm.wx[2] = swarm.wx[1];
  swarm.wx[1] = swarm.wx[0];
  swarm.wy[2] = swarm.wy[1];
  swarm.wy[1] = swarm.wy[0];

  swarm.wxv += balance_rand(WASPACC);
  swarm.wyv += balance_rand(WASPACC);

  /* Speed Limit Checks */
  speed = sqrt((double) swarm.wxv * swarm.wxv + swarm.wyv * swarm.wyv);
  if (speed > WASPVEL) {
    newlimit = (int) ((NRAND(WASPVEL) + WASPVEL / 2) / speed);
    swarm.wxv *= newlimit;
    swarm.wyv *= newlimit;
  }

  /* Move */
  swarm.wx[0] = swarm.wx[1] + swarm.wxv;
  swarm.wy[0] = swarm.wy[1] + swarm.wyv;

  /* Bounce Checks */
  if ((swarm.wx[0] < swarm.border) || (swarm.wx[0] > WIDTH - swarm.border - 1)) {
    swarm.wxv = -swarm.wxv;
    swarm.wx[0] += swarm.wxv;
  }
  if ((swarm.wy[0] < swarm.border) || (swarm.wy[0] > HEIGHT - swarm.border - 1)) {
    swarm.wyv = -swarm.wyv;
    swarm.wy[0] += swarm.wyv;
  }

  /* Don't let things settle down. */
  swarm.xv[NRAND(swarm.beecount)] += balance_rand(3);
  swarm.yv[NRAND(swarm.beecount)] += balance_rand(3);

  /* <=- Bees -=> */
  swarm.tick = (swarm.tick+1) % (swarm.taillen);
  prev = (swarm.tick) ? swarm.tick - 1 : swarm.taillen - 1;

  if (swarm.tick == swarm.taillen - 1) {
    swarm.rolloverflag = 1;
  }
  for (b = 0; b < swarm.beecount; b++) {
    int distance, dx, dy;

    /* Accelerate */
    dx = (int) (swarm.wx[1] - X(prev, b));
    dy = (int) (swarm.wy[1] - Y(prev, b));
    distance = (int) sqrt((double) dx * dx + dy * dy);
    if (distance == 0) {
      distance = 1;
    }
    swarm.xv[b] += dx * BEEACC / (2 * distance);
    swarm.yv[b] += dy * BEEACC / (2 * distance);

    /* Speed Limit Checks */
    speed = sqrt(swarm.xv[b] * swarm.xv[b] + swarm.yv[b] * swarm.yv[b]);
    if (speed > BEEVEL) {
      newlimit = (int) ((NRAND(BEEVEL) + BEEVEL / 2) / speed);
      swarm.xv[b] *= newlimit;
      swarm.yv[b] *= newlimit;
    }

    /* Move */
    X(swarm.tick, b) = X(prev, b) + swarm.xv[b];
    Y(swarm.tick, b) = Y(prev, b) + swarm.yv[b];

    /* Fill the segment lists. */
    swarm.segs[b].x1 = (short) X(swarm.tick, b);
    swarm.segs[b].y1 = (short) Y(swarm.tick, b);
    swarm.segs[b].x2 = (short) X(prev, b);
    swarm.segs[b].y2 = (short) Y(prev, b);
  }
  draw_line(dst,  swarm.wx[0], swarm.wy[0], swarm.wx[1], swarm.wy[1],250);

  for (b = 0; b < swarm.beecount; b++) {
    draw(dst, &swarm.segs[b], (Pixel_t)b);
  }
}


void
destroy(Context_t *ctx)
{
  xfree(swarm.segs);
  xfree(swarm.x);
  xfree(swarm.y);
  xfree(swarm.xv);
  xfree(swarm.yv);
}
