/*
 * led_anim.c
 *
 * Copyright (C) 2023  Sylvain Munaut <tnt@246tNt.com>
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

#include <stdbool.h>
#include <stdint.h>
#include <string.h>

#include "led_anim.h"
#include "utils.h"

#define N_LEDS 42


/* -------------------------------------------------------------------------- */
/* Helper: Candle effect                                                      */
/* -------------------------------------------------------------------------- */

/* Sequences from half life, designed to run at 10 fps */
static const char * const candles[] = {
	"mmmmmaaaaammmmmaaaaaabcdefgabcdefg",
	"mmmaaaabcdefgmmmmaaaammmaamm",
	"mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa",
};

struct candle_state {
	uint8_t  sel;	/* Which candle ?                   */
	uint8_t  len;   /* Sequence lenght                  */
	uint8_t  spd;	/* Speed of sequence (64 = Nominal) */
	uint16_t pha;	/* Current phase (10.6 format)      */
};

static void
candle_init(struct candle_state *c)
{
	c->sel = rand16() % 3;
	c->len = strlen(candles[c->sel]);
	c->spd = 48 + (rand16() % 40);
	c->pha = rand16() % (c->len << 6);
}

static uint8_t
candle_exec(struct candle_state *c, uint8_t spd)
{
	int m;

	/* Advance */
		/* We're called at 30 fps, but anim string nominal is 10 fps */
		/* Also we need to modulate both by our internal speed factor
		 * and the one provided in argument */
	c->pha += (85 * spd * c->spd) >> 14;

	/* Wrap */
	m = (int)c->len << 6;
	while (c->pha >= m)
		c->pha -= m;

	/* Return current state */
	return candles[c->sel][c->pha >> 6] - 'a';
}


/* -------------------------------------------------------------------------- */
/* Anim: Sparkle                                                              */
/* -------------------------------------------------------------------------- */

struct la_sparkle_state {
	uint16_t time;
	bool     candle;
	struct {
		uint16_t raw;
		struct candle_state cdl;
	} leds[N_LEDS];
};

static void
la_sparkle_init_common(void *state, bool candle)
{
	struct la_sparkle_state *s = state;

	s->time = 0;
	s->candle = candle;

	for (int i=0; i<N_LEDS; i++) {
		s->leds[i].raw    = 0;
		candle_init(&s->leds[i].cdl);
	}
}

static void
la_sparkle1_init(void *state)
{
	la_sparkle_init_common(state, true);
}

static void
la_sparkle2_init(void *state)
{
	la_sparkle_init_common(state, false);
}

static void
la_sparkle_render(void *state, uint16_t *leds,
                  struct led_anim_config *conf, uint32_t frame)
{
	struct la_sparkle_state *s = state;
	bool has_ob = false;
	int i;

	/* Decrease lightness of all leds */
	for (i=0; i<N_LEDS; i++)
	{
		uint16_t dec;

		/* Base */
		dec = 2 * conf->speed;

		/* Fast decrease for high intensity leds */
		if (s->leds[i].raw > 40000)
			dec += ((uint32_t)(s->leds[i].raw - 40000) * conf->speed) >> 10;

		/* Decrement with low saturation to 0 */
		if ((s->leds[i].raw < dec) || (s->leds[i].raw < 500))
			s->leds[i].raw = 0;
		else
			s->leds[i].raw -= dec;

		/* Check if any led is over bright */
		if (s->leds[i].raw > 45000)
			has_ob = true;
	}

	/* Every ~0.25s we make a led brighter. If there is none already,
	 * make it overbright, else just normal */
	s->time += conf->speed;
	if (s->time > 512) {
		s->time -= 512;
		while (s->leds[i = (rand16() % N_LEDS)].raw > 10000);
		s->leds[i].raw = has_ob ? 35000 : 65535;
	}

	/* Final lightness is raw + candle flicker if it's < 6000 */
	for (i=0; i<N_LEDS; i++)
	{
		/* Base value */
		uint16_t r = s->leds[i].raw;

		/* Add candle flicker */
		if (s->candle && (r < 6000))
			r += (550 * candle_exec(&s->leds[i].cdl, conf->speed));

		/* Final assign */
		leds[i] = ((uint32_t)r * conf->dim) >> 8;
	}
}

static const struct led_anim la_sparkle1 = {
	.init   = la_sparkle1_init,
	.render = la_sparkle_render,
};

static const struct led_anim la_sparkle2 = {
	.init   = la_sparkle2_init,
	.render = la_sparkle_render,
};


/* -------------------------------------------------------------------------- */
/* Anim: Candle                                                               */
/* -------------------------------------------------------------------------- */

struct la_candle_state {
	struct candle_state cdl[N_LEDS];
	uint8_t brg[N_LEDS];
};

static void
la_candle_init(void *state)
{
	struct la_candle_state *s = state;

	for (int i=0; i<N_LEDS; i++) {
		candle_init(&s->cdl[i]);
		s->brg[i] = 128 + (rand16() & 127);
	}
}

static void
la_candle_render(void *state, uint16_t *leds,
                  struct led_anim_config *conf, uint32_t frame)
{
	struct la_candle_state *s = state;

	for (int i=0; i<N_LEDS; i++)
		leds[i] = (3750UL * s->brg[i] * conf->dim * candle_exec(&s->cdl[i], conf->speed)) >> 16;
}

static const struct led_anim la_candle = {
	.init   = la_candle_init,
	.render = la_candle_render,
};


/* -------------------------------------------------------------------------- */
/* Anim: Breathe                                                              */
/* -------------------------------------------------------------------------- */

static const uint8_t breathe[256] = {
	  2,   2,   2,   2,   3,   3,   3,   4,   4,   4,   5,   5,   5,   6,   6,   7,
	  8,   8,   9,  10,  10,  11,  12,  13,  14,  15,  16,  18,  19,  20,  22,  23,
	 25,  26,  28,  30,  32,  34,  36,  38,  41,  43,  46,  48,  51,  54,  57,  60,
	 63,  67,  70,  73,  77,  81,  85,  89,  93,  97, 101, 105, 109, 114, 118, 123,
	127, 132, 137, 141, 146, 151, 156, 160, 165, 170, 174, 179, 184, 188, 193, 197,
	201, 206, 210, 214, 217, 221, 225, 228, 231, 234, 237, 240, 242, 245, 247, 249,
	250, 252, 253, 254, 254, 255, 255, 255, 255, 255, 254, 254, 253, 253, 252, 251,
	250, 249, 248, 247, 246, 245, 243, 242, 240, 238, 237, 235, 233, 231, 229, 227,
	224, 222, 220, 217, 215, 213, 210, 207, 205, 202, 199, 196, 194, 191, 188, 185,
	182, 179, 176, 173, 170, 167, 164, 161, 158, 155, 152, 149, 146, 143, 140, 137,
	134, 131, 128, 125, 122, 119, 116, 113, 110, 108, 105, 102,  99,  97,  94,  91,
	 89,  86,  84,  81,  79,  77,  74,  72,  70,  68,  65,  63,  61,  59,  57,  55,
	 53,  52,  50,  48,  46,  45,  43,  41,  40,  38,  37,  36,  34,  33,  32,  30,
	 29,  28,  27,  26,  25,  24,  23,  22,  21,  20,  19,  18,  17,  17,  16,  15,
	 14,  14,  13,  12,  12,  11,  11,  10,  10,   9,   9,   8,   8,   8,   7,   7,
	  6,   6,   6,   5,   5,   5,   5,   4,   4,   4,   4,   3,   3,   3,   3,   3
};

struct la_breathe_state {
	int n_ob;
	struct {
		uint8_t  spd;	/* Speed (64=Nominal) */
		uint16_t pha;	/* Phase (8.8 FP) */
		uint8_t  brg;	/* Brightness (128=Nominal, 184=OverBright) */
		bool     ob;
	} leds[N_LEDS];
};

static void
la_breathe_init(void *state)
{
	struct la_breathe_state *s = state;

	s->n_ob = 0;

	for (int i=0; i<N_LEDS; i++) {
		s->leds[i].spd = 48 + (rand16() % 40);
		s->leds[i].pha = rand16();
		s->leds[i].brg = 64 + (rand16() & 63);
		s->leds[i].ob  = false;
	}
}

static void
la_breathe_render(void *state, uint16_t *leds,
                  struct led_anim_config *conf, uint32_t frame)
{
	struct la_breathe_state *s = state;

	for (int i=0; i<N_LEDS; i++) {
		/* Advance */
		uint16_t pha = s->leds[i].pha + ((28 * s->leds[i].spd * conf->speed) >> 8);

		/* Wrap around */
		if (pha < s->leds[i].pha) {
			/* Pick new speed */
			s->leds[i].spd = 48 + (rand16() % 40);

			/* New brightness, possibly OverBright */
			if (!s->leds[i].ob && (s->n_ob < 2) && (rand16() & 1)) {
				s->leds[i].brg = 152 + (rand16() & 31);
				s->leds[i].ob = true;
				s->n_ob++;
			} else {
				s->leds[i].brg = 64 + (rand16() & 63);
			}
		}

		s->leds[i].pha = pha;

		/* Early OverBright release */
			/* After pha >= 28000, we know that it's safe to allow a new overbright led
			 * because we'll be past the peak by the time they start needing overbright */
		if (s->leds[i].ob && (s->leds[i].pha > 28000)) {
			s->leds[i].ob = false;
			s->n_ob--;
		}

		/* Render */
		leds[i] = (350UL * conf->dim * s->leds[i].brg * breathe[s->leds[i].pha >> 8]) >> 16;
	}
}

static const struct led_anim la_breathe = {
	.init   = la_breathe_init,
	.render = la_breathe_render,
};


/* -------------------------------------------------------------------------- */
/* Anim: Constant                                                             */
/* -------------------------------------------------------------------------- */

static void
la_const_init(void *state)
{
	/* Nothing to do */
}

static void
la_const_render(void *state, uint16_t *leds,
                struct led_anim_config *conf, uint32_t frame)
{
	for (int i=0; i<N_LEDS; i++)
		leds[i] = conf->dim * 192;
}

static const struct led_anim la_const = {
	.init   = la_const_init,
	.render = la_const_render,
};


/* -------------------------------------------------------------------------- */
/* Anim list                                                                  */
/* -------------------------------------------------------------------------- */

const struct led_anim * const all_anims[] = {
	&la_sparkle1,
	&la_sparkle2,
	&la_candle,
	&la_breathe,
	&la_const,
	NULL /* guard */
};
