#include <Adafruit_NeoPixel.h>
#include <limits.h>
#include <stdint.h>
#ifdef __AVR__
#include <avr/power.h>
#endif

// MACRO DEFINITIONS
#define PIN 21
#define NUMPIXELS 12
// pixel selection 32bit (4bytes)
// pixel color 1 byte per color, 3 colors (3bytes)
// \n ender (1bytes)
#define SERIAL_MESSAGE_LEN 8

// TYPES DEFINITION
typedef union {
  uint32_t value;
  uint8_t bytes[4];
} Address;
typedef uint8_t Color[3];

// GLOBAL VARIABLES
uint32_t read = 0;
uint8_t message[SERIAL_MESSAGE_LEN] = { 0 };
Address address = { 0 };
Color color = { 0 };
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);


uint8_t set_color(uint32_t address, uint8_t r, uint8_t g, uint8_t b) {
  if (address == UINT_MAX || address > NUMPIXELS || address < 0) {
    pixels.clear();
    for (int i = 0; i < NUMPIXELS; i++) {
      pixels.setPixelColor(i, pixels.Color(r, g, b));
    }
    pixels.show();
  } else {
    pixels.setPixelColor(address, pixels.Color(r, g, b));
    pixels.show();
  }
}

// SETUP PROCEDURE
void setup() {
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
  clock_prescale_set(clock_div_1);
#endif
  Serial.begin(9600);
  pixels.begin();
  // show test colors
  set_color(-1, 255, 0, 0);
  delay(1000);
  set_color(-1, 0, 255, 0);
  delay(1000);
  set_color(-1, 0, 0, 255);
  delay(1000);
  set_color(-1, 0, 0, 0);
  delay(1000);
  set_color(-1, 255, 255, 255);
  delay(2000);
  for(int cnt=255;cnt>=0;cnt--){
    set_color(-1, cnt, cnt,cnt);
    delay(20);   
  }
}

uint8_t parse_message(uint8_t *message, Address *address, Color color) {
  if (message[SERIAL_MESSAGE_LEN - 1] != '\n') {
    Serial.write("missing message termination (\\n)");
    return 1;
  }
  // little endian
  address->bytes[3] = message[0];
  address->bytes[2] = message[1];
  address->bytes[1] = message[2];
  address->bytes[0] = message[3];
  color[0] = message[4];
  color[1] = message[5];
  color[2] = message[6];
  return 0;
}

void loop() {
  read = Serial.readBytes(message, SERIAL_MESSAGE_LEN);
  if (read > 0 && read != SERIAL_MESSAGE_LEN) {
    Serial.write("bad message length\n");
    return;
  }
  if (parse_message(message, &address, color)) {
    Serial.write(", failed to parse message\n");
    return;
  }
  set_color(address.value, color[0], color[1], color[2]);
  Serial.write("ok\n");
}
