#include <FastLED.h>
#include "main.h"

#define WS2812B_PIN 14
#define LED_DATA_PIN  13 // D7 // Hardware-MOSI
#define LED_CLOCK_PIN 14 // D5 // Hardware-CLK
#define STATUS_LED_PIN 16
#define FIRMWARE_VERSION_MAJOR 1
#define FIRMWARE_VERSION_MINOR 3
#define MAX_LED_COUNT 10000

CRGB* leds;
uint16_t led_count;
uint8_t blink_mode = 0;
unsigned long blink_timer_ts;
unsigned long blink_mode_timeout_timer_ts;


void setup() {
	Serial.begin(9600);
	Serial.setTimeout(1);
	//Serial.println("Online!");
	led_count = 256;
	leds = new CRGB[256];
	//FastLED.addLeds<WS2812B, WS2812B_PIN, GRB>(leds, 256);
	FastLED.addLeds<APA102, LED_DATA_PIN, LED_CLOCK_PIN, GRB, DATA_RATE_MHZ(1)>(leds, 256);
	FastLED[0].setLeds(leds, 256);
	pinMode(STATUS_LED_PIN, OUTPUT);
	set_blink(0);
	blink_timer_ts = blink_mode_timeout_timer_ts = millis();
}

void loop() {
	Serial.setTimeout(1);
	byte buffer=0;
	size_t received = Serial.readBytes(&buffer, 1);
	if (received==1) {
		switch(buffer) {
			case 'L': cmd_length(); break;
			case 'F': cmd_fill(); break;
			case 'R': cmd_receive(); break;
			case 'O': cmd_output(); break;
			case 'C': cmd_clear(); break;
			case 'V': cmd_version(); break;
			case 'M': cmd_get_max(); break;
			case 'r': cmd_random_color(); break;
			case 't': cmd_test(); break;
			case '?': cmd_help(); break;
			default: send_nack(); break;
		}
		set_blink(1);
	}
	blink();
}

void set_blink(uint8_t mode) {
	blink_mode = mode;
	blink_mode_timeout_timer_ts = millis();
}

void blink() {
	unsigned long now = millis();
	unsigned long blink_timer = now - blink_timer_ts;
	unsigned long blink_mode_timeout_timer = now - blink_mode_timeout_timer_ts;
	
	switch(blink_mode) {
		case 0: // active until first command is received
			if(blink_timer < 1500) {
				status_led_on();
			} else if(blink_timer < 1600) {
				status_led_off();
			} else {
				blink_timer_ts = now;
				status_led_on();
			}
			break;
		case 1: // active after receiving a command - ends after 500ms
			/*if(blink_timer > 30) {
				blink_timer_ts = now;
				status_led_toggle();
			}
			if(blink_mode_timeout_timer > 500) {
				set_blink(2);
			}
			break;
		case 2: // waiting for commands
			if (blink_timer < 1500) {
				status_led_off();
			} else if (blink_timer < 1600) {
				status_led_on();
			} else if (blink_timer < 1700) {
				status_led_off();
			} else if (blink_timer < 1800) {
				status_led_on();
			} else {
				blink_timer_ts = now;
				status_led_off();
			}*/
			status_led_toggle();
			set_blink(2);
			break;
		case 2: // do nothing
			break;
		default: // should newer be reached
			if (blink_timer > 1000) {
				status_led_toggle();
				blink_timer_ts = now;
			}
			break;
	}
}

void cmd_output() {
	FastLED.show();
	send_ack();
}

void cmd_fill() {
	uint16_t first_led = receive_word();
	uint16_t cmd_leds = receive_word();
	CRGB color = receive_color();
	
	if (first_led <= led_count && cmd_leds>0 && first_led + cmd_leds + 1 <= led_count) {
		uint16_t end_led = first_led + cmd_leds;
		for(uint16_t i=first_led; i<end_led; i++) {
			leds[i] = color;
		}
		send_ack();
	} else {
		send_nack();
	}
}

void cmd_receive() {
	uint16_t first_led = receive_word();
	uint16_t cmd_leds = receive_word();
	if (first_led <= led_count && cmd_leds>0 && first_led + cmd_leds <= led_count) {
		uint16_t end_led = first_led + cmd_leds;
		for(uint16_t i=first_led; i<end_led; i++) {
			leds[i] = receive_color();
		}
		send_ack();
	} else {
		send_nack();
	}
}

void cmd_length() {
	uint16_t new_len = receive_word();
	
	if (new_len>0 && new_len<=MAX_LED_COUNT) {
		delete leds;
		leds = new CRGB[new_len];
		led_count = new_len;
		FastLED[0].setLeds(leds, led_count);
		send_ack();
	} else {
		send_nack();
	}
}

void cmd_clear() {
	for(uint16_t i=0; i<led_count; i++) {
		leds[i] = CRGB::Black;
	}
	send_ack();
}

void cmd_version() {
	send(FIRMWARE_VERSION_MAJOR);
	send(FIRMWARE_VERSION_MINOR);
	send_ack();
}

void cmd_get_max() {
	Serial.write(MAX_LED_COUNT >> 8);
	send((byte)(MAX_LED_COUNT & 0xFF));
	send_ack();
}

void cmd_random_color() {
	for (int i=0; i<led_count; i++) {
		leds[i] = CRGB(random8(), random8(), random8());
	}
	FastLED.show();
	send_ack();
}

void cmd_test() {
	for (int i=0; i<led_count; i++) {
		leds[i] = CRGB::Red;
	}
	FastLED.show();
	send_ack();
}

void cmd_help() {
	Serial.println("L <length>                  Set new LED strip length.");
	Serial.println("F <start> <count> <color>   Fills the given part with color.");
	Serial.println("R <start> <count> <color>[] Sets colors for the fiven area.");
	Serial.println("O                           Shows the set colors on the strip.");
	Serial.println("C                           Sets all LEDs to black.");
	Serial.println("V                           Returns the firmware version as two bytes.");
	Serial.println("M                           Returns the maximum number of LEDs.");
	Serial.println("r                           Sets all LEDs to random colors.");
	Serial.println("t                           Sets the whole strip to red.");
	Serial.println("?                           Displays this help.");
	send_ack();
}

void send_ack() {
	send('A');
}

void send_nack() {
	send('N');
}

void send(byte data) {
	Serial.write(data);
	//Serial.write(0x00);
	Serial.flush();
}

uint8_t receive_byte() {
	/*byte buffer;
	Serial.setTimeout(5);
	size_t count = Serial.readBytes(&buffer, 1);
	if (count==1) return buffer;
	else return 0;*/
	while (!Serial.available()) {}
	return Serial.read();
}

uint16_t receive_word() {
	return (receive_byte() << 8) | receive_byte();
}

CRGB receive_color() {
	return CRGB(receive_byte(), receive_byte(), receive_byte());
}

void status_led_on() {
	digitalWrite(STATUS_LED_PIN, LOW);
}

void status_led_off() {
	digitalWrite(STATUS_LED_PIN, HIGH);
}

void status_led_toggle() {
	digitalWrite(STATUS_LED_PIN, !digitalRead(STATUS_LED_PIN));
}
