//## INCLUDES ##
#define MY_DEBUG
#define MY_RADIO_NRF24
#define MY_NODE_ID 31

#include <MySensors.h>
#include <SPI.h>
#include <FastLED.h>

#define cID_RGB_SELECTOR 0
#define cID_CYCLE_EFFECT 1
#define cID_CYCLE_EFFECT_SPEED 2

#define PIN_RED   3
#define PIN_GREEN 5
#define PIN_BLUE  6

//## VARIABLES ##
// MySensors
#define MySensors_SketchName      "RGB LED Strip"
#define MySensors_SketchVersion   "v0.3"
MyMessage MySensors_MSG_Last_Color(cID_RGB_SELECTOR,V_VAR1);
MyMessage MySensors_MSG_RGB_Selector(cID_RGB_SELECTOR, V_LIGHT);
MyMessage MySeonsors_MSG_CYCLE_EFFECT(cID_CYCLE_EFFECT, V_LIGHT);
MyMessage MySensors_MSG_CYCLE_EFFECT_SPEED(cID_CYCLE_EFFECT_SPEED, V_DIMMER);
bool MySensors_RequestACK = false;
// Single color
int Solid_RGB_Active=0;
char Solid_RGB_Color[] = "000000";
uint16_t Solid_RGB_Brightness = 0xFF;
// Cycle effect
int Cycle_Effect_Active=0;
unsigned long Cycle_Effect_pMillis = 0;
long Cycle_Effect_Speed = 20;
static uint8_t Cycle_Effect_Current_Hue;
// Supporting
bool Status_Change = false;
bool Print_Debug = false;

// ## Primary flow control
void setup() {
	Serial.begin(115200);
	while (!Serial) ;
	Serial.print("compiled: ");Serial.print(__DATE__);Serial.println(__TIME__);

	pinMode(PIN_RED,   OUTPUT);
	pinMode(PIN_GREEN, OUTPUT);
	pinMode(PIN_BLUE,  OUTPUT);

	Event_ColorTestBars();

	request(cID_RGB_SELECTOR, V_VAR1);
	request(cID_RGB_SELECTOR, V_LIGHT);
	request(cID_CYCLE_EFFECT, V_LIGHT);
	request(cID_CYCLE_EFFECT_SPEED, V_DIMMER);
}
void loop() {
	if (Cycle_Effect_Active == 1){
		unsigned long currentMillis = millis();
		Event_RunCycleEffect(currentMillis);
	} else if (Status_Change){
		Status_Change = false;
#ifdef MY_DEBUG
		if (Print_Debug) {Serial.println("STATUS CHANGE");}
#endif
		if (Solid_RGB_Active == 0){
			Event_SetLEDColors( CRGB::Black );
		}else if (Solid_RGB_Active == 1){
			CHSV colorHSV = rgb2hsv_approximate(str2CRGB(Solid_RGB_Color));
			Event_SetLEDColors(CHSV(colorHSV.h, colorHSV.s, Solid_RGB_Brightness));
		}
	}
}
// ## MySensors Methods
void presentation()  {
	sendSketchInfo(MySensors_SketchName, MySensors_SketchVersion);

	present(cID_RGB_SELECTOR, S_RGB_LIGHT, "RGB Color Selector", MySensors_RequestACK);
	present(cID_CYCLE_EFFECT, S_LIGHT, "RGB Cycle Effect", MySensors_RequestACK);
	present(cID_CYCLE_EFFECT_SPEED, S_DIMMER, "RGB Cycle Effect Speed", MySensors_RequestACK);
}
void receive(const MyMessage &message){
#ifdef MY_DEBUG
	if (message.isAck()){
		Serial.println("Got ack from gateway");
	}
#endif
	if (message.type == V_LIGHT){
#ifdef MY_DEBUG
		if (Print_Debug) {Serial.println("message v_light");}
#endif
		int current_Light_State = message.getString()[0] == '1';// Incoming on/off command sent from controller ("1" or "0")
		if (message.sensor==cID_CYCLE_EFFECT){// is Cycle Message
			if (current_Light_State==1){//turn cycle on
				Event_LightCycle(true, true, false);
				Event_SolidColor(false, false, true);
			} else {//turn cycle off
				Event_LightCycle(false, true, false);
				Event_SolidColor(false, false, true);
			}
		} else if (message.sensor==cID_RGB_SELECTOR){// is RGB Message
			if (current_Light_State==1){//turn RGB on
				Event_SolidColor(true, true, false);
				Event_LightCycle(false, false, true);
			} else {//turn RGB off
				Event_SolidColor(false, true, false);
				Event_LightCycle(false, false, true);
			}
		} else {
#ifdef MY_DEBUG
			Serial.print("UNKNOWN Light - Message:");
			Serial.print(message.getString());
			Serial.print(" - Sensor:");
			Serial.println(message.sensor);
#endif
		}
	} else if (message.type == V_RGB){
#ifdef MY_DEBUG
		if (Print_Debug) {Serial.println("message v_rgb");}
#endif
		String szMessage=message.getString();
		strcpy(Solid_RGB_Color, getValue(szMessage,'&',0).c_str());
		Solid_RGB_Active = 1;
	}else if (message.type == V_DIMMER) {// if DIMMER type, adjust brightness
#ifdef MY_DEBUG
		if (Print_Debug) {Serial.println("message v_dimmer");}
#endif
		if (message.sensor==cID_RGB_SELECTOR){// is single Message
			if (Solid_RGB_Active==1){//turn RGB on
				Event_SolidColor(true, true, false);
				Event_LightCycle(false, false, true);
			} else {//turn RGB off
				Event_SolidColor(false, true, false);
				Event_LightCycle(false, false, true);
			}
			Solid_RGB_Brightness = map(message.getLong(), 0, 100, 0, 255);
			CRGB colorRGB = str2CRGB(Solid_RGB_Color);
			CHSV colorHSV = rgb2hsv_approximate(colorRGB);
			colorHSV = CHSV(colorHSV.h, colorHSV.s, Solid_RGB_Brightness);
			Event_SetLEDColors(colorHSV);
#ifdef MY_DEBUG
			if (Print_Debug) {
				Serial.print("colorHSV.h:");
				Serial.println(colorHSV.h);
				Serial.print("colorHSV.s:");
				Serial.println(colorHSV.s);
				Serial.print("colorHSV.v:");
				Serial.println(colorHSV.v);
			}
#endif
			Event_SendLastColor();
		} else if (message.sensor==cID_CYCLE_EFFECT_SPEED){// is Speed dimmer Message
			Cycle_Effect_Speed = map(message.getLong(), 0, 100, 1, 202);
#ifdef MY_DEBUG
			if (Print_Debug) {
				Serial.print("Cycle_Effect_Speed: ");
				Serial.println(Cycle_Effect_Speed);
			}
#endif
		}
	}else if (message.type == V_STATUS) {           // if on/off type, toggle brightness
#ifdef MY_DEBUG
		if (Print_Debug) {Serial.println("message v_status");}
#endif
		Solid_RGB_Active = message.getInt();
		Cycle_Effect_Active = 0;
		if (Solid_RGB_Active == 0){
			if (Print_Debug) {Serial.println("Strip OFF");}
			Event_SetLEDColors( CRGB::Black );
		}else{
			if (Print_Debug) {Serial.println("Strip ON");}
			Event_SetLEDColors(strtol(Solid_RGB_Color, NULL, 16));
		}
		//Event_SendLastColor();
	}else if (message.type==V_VAR1) {            // color status
		String szMessage=message.getString();
#ifdef MY_DEBUG
		if (Print_Debug) {
			Serial.println("message v_var1");
			Serial.println(szMessage);
		}
#endif
		strcpy(Solid_RGB_Color, getValue(szMessage,'&',0).c_str());
		Solid_RGB_Active = 1;
		Cycle_Effect_Active = 0;
	}
	Status_Change = true;
}
// ## Events
void Event_LightCycle(bool t, bool s, bool u) {
	Cycle_Effect_Active = (t) ? 1 : 0;
	if (u){
		send(MySeonsors_MSG_CYCLE_EFFECT.set(Cycle_Effect_Active),MySensors_RequestACK);
	}
}
void Event_SolidColor(bool t, bool s, bool u) {
	Solid_RGB_Active = (t) ? 1 : 0;
	if (u){
		send(MySensors_MSG_RGB_Selector.set(Solid_RGB_Active),MySensors_RequestACK);
	}
}
void Event_SetLEDColors( const CRGB& rgb){
	analogWrite(PIN_RED,   rgb.r );
	analogWrite(PIN_GREEN, rgb.g );
	analogWrite(PIN_BLUE,  rgb.b );
}
void Event_SendLastColor(){
	String current_status=Solid_RGB_Color+String("&")+String(Solid_RGB_Brightness)+String("&")+String(Solid_RGB_Active);
	send(MySensors_MSG_Last_Color.set(current_status.c_str()),MySensors_RequestACK);
}
void Event_RunCycleEffect(unsigned long theMills){
	if (theMills - Cycle_Effect_pMillis >= Cycle_Effect_Speed){
		Cycle_Effect_pMillis = theMills;
		Cycle_Effect_Current_Hue = Cycle_Effect_Current_Hue + 1;
		Event_SetLEDColors( CHSV( Cycle_Effect_Current_Hue, 255, 255) );
	}
}
void Event_ColorTestBars(){// Event_ColorTestBars: flashes Red, then Green, then Blue, then Black. Helpful for diagnosing if you've mis-wired which is which.
	Event_SetLEDColors( CRGB::Red );   delay(500);
	Event_SetLEDColors( CRGB::Green ); delay(500);
	Event_SetLEDColors( CRGB::Blue );  delay(500);
	Event_SetLEDColors( CRGB::Black ); delay(500);
}
// ## Helper Functions
String getValue(String data, char separator, int index){
	int found = 0;
	int strIndex[] = {0, -1};
	int maxIndex = data.length()-1;
	for(int i=0; i<=maxIndex && found<=index; i++){
		if(data.charAt(i)==separator || i==maxIndex){
			found++;
			strIndex[0] = strIndex[1]+1;
			strIndex[1] = (i == maxIndex) ? i+1 : i;
		}
	}
	return found>index ? data.substring(strIndex[0], strIndex[1]) : "";
}

int x2i(char *s) {
	int x = 0;
	for(;;) {
		char c = *s;
		if (c >= '0' && c <= '9') {
			x *= 16;
			x += c - '0';
		}else if (c >= 'A' && c <= 'F') {
			x *= 16;
			x += (c - 'A') + 10;
		}else {
			break;
		}
		s++;
	}
	return x;
}
char* str2char(String command){
	if(command.length()!=0){
		char *p = const_cast<char*>(command.c_str());
		return p;
	}
}
CRGB str2CRGB(String s){
	String r = str2char(s.substring(0,2));
	String g = str2char(s.substring(2,4));
	String b = str2char(s.substring(4,6));
	uint8_t red = x2i(r.c_str());
	uint8_t green = x2i(g.c_str());
	uint8_t blue = x2i(b.c_str());
#ifdef MY_DEBUG
	if (Print_Debug) {
		Serial.print("r:");
		Serial.println(r);
		Serial.print("g:");
		Serial.println(g);
		Serial.print("b:");
		Serial.println(b);
		Serial.print("red:");
		Serial.println(red);
		Serial.print("green:");
		Serial.println(green);
		Serial.print("blue:");
		Serial.println(blue);
	}
#endif
	CRGB colorRGB = CRGB(red, green, blue);
	return colorRGB;
}
