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

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


#define LRAND()   b_rand_uint32_range(0, 1 << 30)
#define NRAND(n)  ((int) (LRAND() % (n)))
#define NUMCOLORS 256

/* Originally done by Uli Siegmund <uli@wombat.okapi.sub.org> on Amiga
 *   for EGS in Cluster
 * Port from Cluster/EGS to C/Intuition by Harald Backert
 * Port to X11 and incorporation into xlockmore by Hubert Feyrer
 *   <hubert.feyrer@rz.uni-regensburg.de>
 *
 * 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:
 * 26-Aug-00: robert.nagtegaal@phil.uu.nl and roland@tschai.demon.nl:
 *            various improvements
 * 10-May-97: jwz@jwz.org: turned into a standalone program.
 * 18-Apr-97: Memory leak fixed by Tom Schmidt <tschmidt@micron.com>
 * 07-Apr-97: Modified by Dave Mitchell <davem@magnet.com>
 * 23-Oct-94: Modified by David Bagley <bagleyd@bigfoot.com>
 *  random star sizes
 *  colors change depending on velocity
 * 10-Oct-94: Add colors by Hubert Feyer
 * 30-Sep-94: Initial port by Hubert Feyer
 * 09-Mar-94: VMS can generate a random number 0.0 which results in a
 *            division by zero, corrected by Jouk Jansen
 *            <joukj@crys.chem.uva.nl>
 */

#define FLOATRAND b_rand_double_range(0, 1)

#define MINSIZE       1
#define MINGALAXIES    2
#define MAX_STARS    3000
#define MAX_IDELTAT    50
/* These come originally from the Cluster-version */
#define DEFAULT_GALAXIES  3
#define DEFAULT_STARS    1000
#define DEFAULT_HITITERATIONS  7500
#define DEFAULT_IDELTAT    200 /* 0.02 */
#define EPSILON 0.00000001

#define sqrt_EPSILON 0.0001

#define DELTAT (MAX_IDELTAT * 0.0001)

#define GALAXYRANGESIZE  0.1
#define GALAXYMINSIZE  0.15
#define QCONS    0.001


#define COLORBASE  16
/* Colors for stars start here */
#define COLORSTEP  (NUMCOLORS/COLORBASE) /* NUMCOLORS / COLORBASE colors per galaxy */


typedef struct {
  double      pos[3], vel[3];
} Star;

typedef struct {
  int         mass;
  int         nstars;
  Star       *stars;
  /*  XPoint     *oldpoints;*/
  /*  Point2d     *newpoints;*/
  double      pos[3], vel[3];
  int         galcol;
} Galaxy;

typedef struct {
  double      mat[3][3]; /* Movement of stars(?) */
  double      scale; /* Scale */
  int         midx; /* Middle of screen, x */
  int         midy; /* Middle of screen, y */
  double      size; /* */
  double      diff[3]; /* */
  Galaxy     *galaxies; /* the Whole Universe */
  int         ngalaxies; /* # galaxies */
  int         f_hititerations; /* # iterations before restart */
  int         step; /* */
#if 0
  double      rot_y; /* rotation of eye around center of universe, around y-axis */
  double      rot_x; /* rotation of eye around center of universe, around x-axis */
#endif
} unistruct;

static unistruct universe;


static void
free_galaxies(unistruct * gp)
{
  if (NULL != gp->galaxies) {
    for (int i = 0; i < gp->ngalaxies; i++) {
      Galaxy *gt = &gp->galaxies[i];

      free(gt->stars);
    }
    free((void *)gp->galaxies);
    gp->galaxies = NULL;
  }
}


static void
startover(void)
{
  unistruct  *gp = &universe;
  int         i, j; /* more tmp */
  double      w1, w2; /* more tmp */
  double      d, v, w, h; /* yet more tmp */

  gp->step = 0;
  /*
    gp->rot_y = 0;
    gp->rot_x = 0;
  */

  /* if (MI_BATCHCOUNT(mi) < -MINGALAXIES)*/
  free_galaxies(gp);
  /* gp->ngalaxies = MI_BATCHCOUNT(mi);*/
  if (gp->ngalaxies < -MINGALAXIES) {
    gp->ngalaxies = NRAND(-gp->ngalaxies - MINGALAXIES + 1) + MINGALAXIES;
  }

  else if (gp->ngalaxies < MINGALAXIES) {
    gp->ngalaxies = MINGALAXIES;
  }
  if (NULL == gp->galaxies) {
    gp->galaxies = xcalloc(gp->ngalaxies, sizeof(Galaxy));
  }

  for (i = 0; i < gp->ngalaxies; ++i) {
    Galaxy     *gt = &gp->galaxies[i];
    double      sinw1, sinw2, cosw1, cosw2;

    gt->galcol = NRAND(COLORBASE - 2);
    if (gt->galcol > 1) {
      gt->galcol += 2;  /* Mult 8; 16..31 no green stars */
    }
    /* Galaxies still may have some green stars but are not all green. */

    free(gt->stars);
    gt->stars = NULL;

    gt->nstars = (NRAND(MAX_STARS / 2)) + MAX_STARS / 2;
    gt->stars = xcalloc(gt->nstars, sizeof(Star));
    /*gt->oldpoints = xcalloc(gt->nstars, sizeof(XPoint));*/
    /*gt->newpoints = xcalloc(gt->nstars, sizeof(Point2d));*/

    w1 = 2.0 * M_PI * FLOATRAND;
    w2 = 2.0 * M_PI * FLOATRAND;
    sinw1 = sin(w1);
    sinw2 = sin(w2);
    cosw1 = cos(w1);
    cosw2 = cos(w2);

    gp->mat[0][0] = cosw2;
    gp->mat[0][1] = -sinw1 * sinw2;
    gp->mat[0][2] = cosw1 * sinw2;
    gp->mat[1][0] = 0.0;
    gp->mat[1][1] = cosw1;
    gp->mat[1][2] = sinw1;
    gp->mat[2][0] = -sinw2;
    gp->mat[2][1] = -sinw1 * cosw2;
    gp->mat[2][2] = cosw1 * cosw2;

    gt->vel[0] = FLOATRAND * 2.0 - 1.0;
    gt->vel[1] = FLOATRAND * 2.0 - 1.0;
    gt->vel[2] = FLOATRAND * 2.0 - 1.0;
    gt->pos[0] = -gt->vel[0] * DELTAT * gp->f_hititerations + FLOATRAND - 0.5;
    gt->pos[1] = -gt->vel[1] * DELTAT * gp->f_hititerations + FLOATRAND - 0.5;
    gt->pos[2] = -gt->vel[2] * DELTAT * gp->f_hititerations + FLOATRAND - 0.5;

    gt->mass = (int) (FLOATRAND * 1000.0) + 1;

    gp->size = GALAXYRANGESIZE * FLOATRAND + GALAXYMINSIZE;

    for (j = 0; j < gt->nstars; ++j) {
      Star       *st = &gt->stars[j];

      double      sinw, cosw;

      w = 2.0 * M_PI * FLOATRAND;
      sinw = sin(w);
      cosw = cos(w);
      d = FLOATRAND * gp->size;
      h = FLOATRAND * exp(-2.0 * (d / gp->size)) / 5.0 * gp->size;
      if (FLOATRAND < 0.5) {
        h = -h;
      }
      st->pos[0] = gp->mat[0][0] * d * cosw + gp->mat[1][0] * d * sinw +
                   gp->mat[2][0] * h + gt->pos[0];
      st->pos[1] = gp->mat[0][1] * d * cosw + gp->mat[1][1] * d * sinw +
                   gp->mat[2][1] * h + gt->pos[1];
      st->pos[2] = gp->mat[0][2] * d * cosw + gp->mat[1][2] * d * sinw +
                   gp->mat[2][2] * h + gt->pos[2];

      v = sqrt(gt->mass * QCONS / sqrt(d * d + h * h));
      st->vel[0] = -gp->mat[0][0] * v * sinw + gp->mat[1][0] * v * cosw +
                   gt->vel[0];
      st->vel[1] = -gp->mat[0][1] * v * sinw + gp->mat[1][1] * v * cosw +
                   gt->vel[1];
      st->vel[2] = -gp->mat[0][2] * v * sinw + gp->mat[1][2] * v * cosw +
                   gt->vel[2];

      st->vel[0] *= DELTAT;
      st->vel[1] *= DELTAT;
      st->vel[2] *= DELTAT;
    }
  }
}


int8_t
create(Context_t *ctx)
{
  unistruct  *gp;

  gp = &universe;

  gp->f_hititerations = 250; /*MI_CYCLES(mi);*/

  /*
    gp->scale = (double) (MI_WIN_WIDTH(mi) + MI_WIN_HEIGHT(mi)) / 8.0;
    gp->midx =  MI_WIN_WIDTH(mi)  / 2;
    gp->midy =  MI_WIN_HEIGHT(mi) / 2;
  */
  gp->scale = (double) (WIDTH + HEIGHT) / 8.0;
  gp->midx =  HWIDTH;
  gp->midy =  HHEIGHT;
  startover(/*mi*/);

  return 1;
}


void
on_switch_on(Context_t *ctx)
{
  startover();
}


void
run(Context_t *ctx)
{
  Buffer8_t *dst = passive_buffer(ctx);
  unistruct  *gp = &universe;
  double      d, eps /*, cox, six, cor, sir*/;  /* tmp */
  int         i, j, k; /* more tmp */
  /*    XPoint    *dummy = NULL;*/
  Pixel_t color;

  Buffer8_clear(dst);

  /*
    gp->rot_y += 0.01;
    gp->rot_x += 0.004;

    cox = cos(gp->rot_y);
    six = sin(gp->rot_y);
    cor = cos(gp->rot_x);
    sir = sin(gp->rot_x);
  */

  eps = 1/(EPSILON * sqrt_EPSILON * DELTAT * DELTAT * QCONS);

  for (i = 0; i < gp->ngalaxies; ++i) {
    Galaxy     *gt = &gp->galaxies[i];

    for (j = 0; j < gp->galaxies[i].nstars; ++j) {
      Star       *st = &gt->stars[j];
      /*   Point2d     *newp = &gt->newpoints[j];*/
      double      v0 = st->vel[0];
      double      v1 = st->vel[1];
      double      v2 = st->vel[2];

      for (k = 0; k < gp->ngalaxies; ++k) {
        Galaxy     *gtk = &gp->galaxies[k];
        double      d0 = gtk->pos[0] - st->pos[0];
        double      d1 = gtk->pos[1] - st->pos[1];
        double      d2 = gtk->pos[2] - st->pos[2];

        d = d0 * d0 + d1 * d1 + d2 * d2;
        if (d > EPSILON) {
          d = gt->mass / (d * sqrt(d)) * DELTAT * DELTAT * QCONS;
        } else {
          d = gt->mass * eps;
        }
        v0 += d0 * d;
        v1 += d1 * d;
        v2 += d2 * d;
      }

      st->vel[0] = v0;
      st->vel[1] = v1;
      st->vel[2] = v2;

      st->pos[0] += v0;
      st->pos[1] += v1;
      st->pos[2] += v2;

      /*
      newp->x = (short) (((cox * st->pos[0]) - (six * st->pos[2])) *
      gp->scale) + gp->midx;
      newp->y = (short) (((cor * st->pos[1]) - (sir * ((six * st->pos[0]) +
      (cox * st->pos[2])))) * gp->scale) + gp->midy;
      xclip (&newp->x);
      yclip (&newp->y);
      */
    }

    for (k = i + 1; k < gp->ngalaxies; ++k) {
      Galaxy     *gtk = &gp->galaxies[k];
      double      d0 = gtk->pos[0] - gt->pos[0];
      double      d1 = gtk->pos[1] - gt->pos[1];
      double      d2 = gtk->pos[2] - gt->pos[2];

      d = d0 * d0 + d1 * d1 + d2 * d2;
      if (d > EPSILON) {
        d = gt->mass * gt->mass / (d * sqrt(d)) * DELTAT * QCONS;
      } else {
        d = gt->mass * gt->mass / (EPSILON * sqrt_EPSILON) * DELTAT * QCONS;
      }

      d0 *= d;
      d1 *= d;
      d2 *= d;
      gt->vel[0] += d0 / gt->mass;
      gt->vel[1] += d1 / gt->mass;
      gt->vel[2] += d2 / gt->mass;
      gtk->vel[0] -= d0 / gtk->mass;
      gtk->vel[1] -= d1 / gtk->mass;
      gtk->vel[2] -= d2 / gtk->mass;
    }

    gt->pos[0] += gt->vel[0] * DELTAT;
    gt->pos[1] += gt->vel[1] * DELTAT;
    gt->pos[2] += gt->vel[2] * DELTAT;

    /*
      XSetForeground(display, gc, MI_WIN_BLACK_PIXEL(mi));
      XDrawPoints(display, window, gc, gt->oldpoints, gt->nstars,
      CoordModeOrigin);
    */
    color = COLORSTEP * gt->galcol;
    for (j = 0; j < gt->nstars; j++)
#if 0
      set_pixel (dst, gt->newpoints[j].x, gt->newpoints[j].y, color);
#else
    {
      Point3d_t p;
      p.pos.x = gt->stars[j].pos[0];
      p.pos.y = gt->stars[j].pos[1];
      p.pos.z = gt->stars[j].pos[2];
      set_pixel_3d(&ctx->params3d, dst, &p, color);
    }
#endif
  }

  gp->step++;
  if (gp->step > gp->f_hititerations * 4) {
    startover();
  }
}


void
destroy(Context_t *ctx)
{
  free_galaxies(&universe);
}
