#ifndef SOLDERDOODLE_LEDS_H
#define SOLDERDOODLE_LEDS_H

#include <stdint.h>
#include "const.h"

#if DEV_WS2812
#include <Adafruit_NeoPixel.h>
#endif

typedef struct {
	uint8_t r;
	uint8_t g;
	uint8_t b;
} RGB8;

RGB8 leds[LED_COUNT];

#if DEV_WS2812
	Adafruit_NeoPixel strip = Adafruit_NeoPixel(7, LED_DATA, NEO_GRB + NEO_KHZ800);
#endif

// Forward declarations
void led_clear();
void led_show();

void led_init()
{
	pinMode(LED_DATA, OUTPUT);
	pinMode(LED_CLOCK, OUTPUT);

#	if DEV_WS2812
		strip.begin();
#	endif

	led_clear();
	led_show();
}

void led_clear()
{
	for (uint8_t i = 0; i < LED_COUNT; i++) {
		leds[i] = (RGB8){.r = 0, .g = 0, .b = 0};
	}
}

void led_set(uint8_t index, uint8_t r, uint8_t g, uint8_t b)
{
	leds[index] = (RGB8){.r = r, .g = g, .b = b};
}

void led_add(uint8_t index, uint8_t r, uint8_t g, uint8_t b)
{
	RGB8 * led = &leds[index];

	led->r = min((uint16_t)(r) + led->r, 0xff);
	led->g = min((uint16_t)(g) + led->g, 0xff);
	led->b = min((uint16_t)(b) + led->b, 0xff);
}

// amt==0x0: Color is not mixed in. amt==0xff: Color overrides old color.
// Known issue: resulting colors slide 1/256th toward black.
void led_mix(uint8_t index, uint8_t r, uint8_t g, uint8_t b, uint8_t amt)
{
	RGB8 * led = &leds[index];

	// Ensure we multiply in 16-bit space. We need the headroom.
	uint16_t amt16 = amt;
	uint16_t inv_amt = ((uint8_t)(~amt));

	led->r = ((led->r * inv_amt) + (r * amt16)) >> 8;
	led->g = ((led->g * inv_amt) + (g * amt16)) >> 8;
	led->b = ((led->b * inv_amt) + (b * amt16)) >> 8;
}

// Because native shiftOut() is slow.
//   See: https://forum.arduino.cc/index.php?topic=382936.0
inline void fast_shift_out(uint8_t data)
{
	uint8_t mask = 0x80;

	do {
		// MSB first
		digitalWrite(LED_DATA, (data & mask) ? HIGH : LOW);
		digitalWrite(LED_CLOCK, HIGH);
		digitalWrite(LED_CLOCK, LOW);

		mask >>= 1;

	} while (mask);
}

void led_show()
{
#	if DEV_WS2812

	for (uint8_t i = 0; i < LED_COUNT; i++) {
		strip.setPixelColor(i,
			(r * ((uint16_t)(LED_BRIGHTNESS))) >> 8,
			(g * ((uint16_t)(LED_BRIGHTNESS))) >> 8,
			(b * ((uint16_t)(LED_BRIGHTNESS))) >> 8
		);

		strip.show();
	}

#	else

		// Start frame: 4 bytes of 0x0
		for (uint8_t i = 0; i < 4; i++) {
			fast_shift_out(0x0);
		}

		// Color: Convert floats to bytes
		for (uint8_t i = 0; i < LED_COUNT; i++) {
			// First byte is: header and global brightness
			fast_shift_out(APA102_COLOR_HEADER);

			// Correct order is: BGR
			fast_shift_out(leds[i].b);
			fast_shift_out(leds[i].g);
			fast_shift_out(leds[i].r);
		}

		// End frame
		for (uint8_t i = 0; i < 4; i++) {
			fast_shift_out(0xff);
		}

# endif
}

#endif
