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


uint32_t version = 0;
uint32_t options = BO_GFX | BO_SFX | BO_SCHEMES;
enum LayerMode mode = LM_OVERLAY;
char desc[] = "Falling drops";
char dname[] = "Drops";

#define FACTOR 0.0005     /* empirical value, as usual */

#define MIN_X_SPEED -1
#define MAX_X_SPEED 1
#define MIN_Y_SPEED 4
#define MAX_Y_SPEED 10

#define DELTA_X 1


typedef struct Drop_s {
  int active;
  Point2d_t pos, speed;
  uint8_t height;
} Drop_t;


static Drop_t *drops = NULL;
static uint8_t drops_mode = 0;
static uint32_t active_drops = 0;
static uint32_t ndrops = 0;


int8_t
create(Context_t *ctx)
{
  uint32_t d;

  ndrops = (uint32_t)((float)BUFFSIZE * FACTOR);
  if (ndrops < 1) {
    ndrops = 1;
  }

  drops = xcalloc(ndrops, sizeof(Drop_t));

  for (d = 0; d < ndrops; d++) {
    drops[d].active = 0;
  }

  active_drops = 0;

  return 1;
}


void
destroy(Context_t *ctx)
{
  xfree(drops);
}


static void
do_drops(Context_t *ctx)
{
  const Buffer8_t *src = active_buffer(ctx);
  Buffer8_t *dst = passive_buffer(ctx);
  uint32_t d;
  short m;

  Buffer8_clear(dst);

  for (d = 0; d < ndrops; d++) {
    if (!drops[d].active) {
      continue;
    }

    for (m = 0; m < -drops[d].speed.y; m++) {
      uint8_t z;
      Point2d_t C;
      Pixel_t Cz;

      drops[d].pos.x += drops[d].speed.x;
      if (drops[d].pos.x < 0) {
        drops[d].pos.x = 0;
      } else if (drops[d].pos.x > MAXX) {
        drops[d].pos.x = MAXX;
      }

      /* drops[d].pos = p2d_add (&drops[d].pos, &drops[d].speed); */
      drops[d].pos.y--;

      if (!drops[d].pos.y) {
        continue;
      }

      z = drops[d].height;

      /* now, move and set new height */
      /* go to center first */
      C.x = drops[d].pos.x;
      C.y = drops[d].pos.y - 1; /* is >= 0 */

      Cz = get_pixel /*_nc*/ (src, C.x, C.y);

      /* ceci est un commentaire ANSI C */
      if ((!drops_mode && (Cz < z)) || (drops_mode && (Cz > z))) {
        drops[d].speed.y++;
        drops[d].height--;
      } else if (Cz == z) {
        /* drops[d].speed.y++; */
        drops[d].height--;
      } else {
        Point2d_t L, R;
        Pixel_t Lz, Rz;

        /* if (!drops[d].height++)
           drops[d].height = 255; */

        drops[d].height--;

        L.x = drops[d].pos.x-1;
        if (L.x < 0) {
          L.x = 0;
        } else if (L.x > MAXX) {
          L.x = MAXX;
        }
        L.y = C.y;

        R.x = drops[d].pos.x+1;
        if (R.x < 0) {
          R.x = 0;
        } else if (R.x > MAXX) {
          R.x = MAXX;
        }
        R.y = C.y;

        Lz = get_pixel/*_nc*/(src, L.x, L.y);
        Rz = get_pixel/*_nc*/(src, R.x, R.y);

        /* choose either LCR or RCL */
        if (b_rand_boolean()) {
          /* LCR */
          if (Lz <= z) {
            /* drops[d].speed.x--; */
            drops[d].pos.x -= DELTA_X;
          } else {
            /* drops[d].speed.x++; */
            drops[d].pos.x += DELTA_X;
          }
        } else {
          /* RCL */
          if (Rz <= z) {
            /* drops[d].speed.x++; */
            drops[d].pos.x += DELTA_X;
          } else {
            /* drops[d].speed.x--; */
            drops[d].pos.x -= DELTA_X;
          }
        }
      }

      /* if (drops[d].height)
      drops[d].height--; */

      /* clamp speed */
      if (drops[d].speed.x < MIN_X_SPEED) {
        drops[d].speed.x = MIN_X_SPEED;
      } else if (drops[d].speed.x > MAX_X_SPEED) {
        drops[d].speed.x = MAX_X_SPEED;
      }

      /* ok this one is tricky, i admit ;) */
      if (drops[d].speed.y > -MIN_Y_SPEED) {
        drops[d].speed.y = -MIN_Y_SPEED;
      } else if (drops[d].speed.y < -MAX_Y_SPEED) {
        drops[d].speed.y = -MAX_Y_SPEED;
      }
    }

    if (drops[d].pos.y) {
      set_pixel/*_nc*/(dst, drops[d].pos.x, drops[d].pos.y, drops[d].height);
    }
  }
}


static void
decay_drops(void)
{
  uint32_t d;
  for (d = 0; d < ndrops; d++)
    if (drops[d].active)
      if ((drops[d].pos.y <= 0) || !drops[d].height) {
        drops[d].active = 0;
        active_drops--;
      }
}


void
run(Context_t *ctx)
{
  uint16_t nd;
  uint32_t idx;

  if (!xpthread_mutex_lock(&ctx->input->mutex)) {
    for (nd = 0, idx = 0;
         (nd < ctx->input->size) && (idx < ndrops)
           && (active_drops < ndrops);
         idx++) {
      if (drops[idx].active) {
        continue;
      }

      /* create a new drop */
      drops[idx].pos.x = b_rand_int() % WIDTH;
      drops[idx].pos.y = b_rand_int() % (MAXY - HHEIGHT) + HHEIGHT;
      drops[idx].speed.x = 0;
      drops[idx].speed.y = -(Input_random_u_u_float(ctx->input) * (MAX_Y_SPEED - MIN_Y_SPEED) + MIN_Y_SPEED);
      drops[idx].active = 1;
      drops[idx].height = 255; /* Input_random_u_float() * 128 + 127; */
      active_drops++;
      nd++;
      assert(active_drops <= ndrops);
    }
    xpthread_mutex_unlock(&ctx->input->mutex);
  }

  decay_drops();
  do_drops(ctx);

  /* printf ("%li active drops\n", active_drops); */
}
