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

#define G (-9.81 / 100.0) /* FIXME a tuner --oliv3 */


Particle_System_t *
Particle_System_new(const long max_particles)
{
  Particle_System_t *ps = xcalloc(1, sizeof(Particle_System_t));

  ps->max_particles = max_particles;
  ps->nb_particles = 0;

  return ps;
}


static void
Particle_delete(Particle_t *p)
{
  xfree(p);
}


void
Particle_System_delete(Particle_System_t *ps)
{
  if (NULL != ps) {
    GSList *p = ps->particles, *p0 = p;

    while (NULL != p) {
      Particle_t *dp = (Particle_t *)p->data;
      p = g_slist_next(p);
      Particle_delete(dp);
    }
    g_slist_free(p0);

    xfree(ps);
  }
}


static Particle_t *
Particle_new(float ttl, Point3d_t pos, Point3d_t vel, Point3d_t acc, float gfa)
{
  Particle_t *p = xcalloc(1, sizeof(Particle_t));

  p->ttl = ttl;
  p->pos = pos;
  p->vel = vel;
  p->acc = acc;
  p->gra.pos.y = gfa * -G;

  gettimeofday(&p->age, NULL);

  return p;
}


Particle_t *
Particle_new_indexed(float ttl, Pixel_t col, Point3d_t pos, Point3d_t vel, Point3d_t acc, float gfa)
{
  Particle_t *p = Particle_new(ttl, pos, vel, acc, gfa);

  p->col.idx = col;

  return p;
}


Particle_t *
Particle_new_rgba(float ttl, rgba_t rgba, Point3d_t pos, Point3d_t vel, Point3d_t acc, float gfa)
{
  Particle_t *p = Particle_new(ttl, pos, vel, acc, gfa);

  /* ici on est pourtant sur que les particules ont un bon RGBA */
  p->col.rgba = rgba;

  return p;
}


uint8_t
Particle_System_can_add(const Particle_System_t *ps)
{
  if (ps->max_particles == PS_NOLIMIT) {
    return 1;
  } else {
    return (ps->nb_particles < ps->max_particles);
  }
}


void
Particle_System_add(Particle_System_t *ps, const Particle_t *p)
{
  ps->particles = g_slist_prepend(ps->particles, (gpointer)p);
  ps->nb_particles++;
}


static void
Particle_System_decay(Particle_System_t *ps)
{
  GSList *tmp = ps->particles;
  struct timeval t;
  GSList *newp = NULL;

  gettimeofday(&t, NULL);

  while (NULL != tmp) {
    float age;
    struct timeval elapsed;
    Particle_t *dp = (Particle_t *)tmp->data;

    /* ok let's compute this particle's age */
    if (dp->age.tv_usec > t.tv_usec) {
      t.tv_usec += 1000000;
      t.tv_sec--;
    }
    elapsed.tv_sec = t.tv_sec - dp->age.tv_sec;
    elapsed.tv_usec = t.tv_usec - dp->age.tv_usec;
    age = elapsed.tv_sec + ((double)elapsed.tv_usec / 1e6);

    if (age >= dp->ttl) {
      /* die, particle, die ! */
      Particle_delete(dp);
      ps->nb_particles--;
    } else {
      newp = g_slist_prepend(newp, dp);
    }

    tmp = g_slist_next(tmp);
  }
  g_slist_free(ps->particles);

  ps->particles = newp;
}


static void
Particle_System_move(Particle_System_t *ps)
{
  GSList *p = ps->particles;

  while (NULL != p) {
    Particle_t *dp = (Particle_t *)p->data;

    /* change position */
    dp->pos = p3d_add(&dp->pos, &dp->vel);

    /* change speed */
    dp->vel = p3d_add(&dp->vel, &dp->acc);

    /* apply gravity */
    dp->vel = p3d_add(&dp->vel, &dp->gra);

    /* next patient, please */
    p = g_slist_next(p);
  }
}


void
Particle_System_go(Particle_System_t *ps)
{
  /* remove old particles */
  Particle_System_decay(ps);

  /* move the rest */
  Particle_System_move(ps);
}


void
Particle_System_draw(const Particle_System_t *ps, const Params3d_t *params3d, Buffer8_t *dst)
{
  GSList *p = ps->particles;

  while (NULL != p) {
    Particle_t *dp = (Particle_t *)p->data;
    set_pixel_3d(params3d, dst, &dp->pos, dp->col.idx);
    p = g_slist_next(p);
  }
}
