#include <EEPROM.h>

//#ifdef SERIAL_PORT_HARDWARE1
//#  define SER1
//#endif
#define USE_ACTIVE_LED

enum RelayType {None = 0, Normal, Inverse};
enum Command {On = 1, Off, OnAndOff};

struct Relay {
	Relay() {pin = 0; type = None; state = 0; groups = 0;}
	void writePin();
	byte readPin();
	bool hasGroup(byte g) const {
		return (groups & (word(1) << g)) > 0;
	}
	void setGroup(byte g, bool yes) {
		if (yes) groups |=  (word(1) << g);
		else     groups &= ~(word(1) << g);
	}
	
	byte pin;
	byte type;
	byte state;
	byte __reserve;
	word groups;
};

#define ADDR_G    0x10
/*#ifdef ARDUINO_AVR_MEGA2560
#  define PIN_COUNT 64
#else */
#  define PIN_COUNT 8
//#endif

const byte pin_map[PIN_COUNT] = 
{6, 5, 3, 2,  8,  7,  9,  10};
/*#ifdef ARDUINO_AVR_MEGA2560
{2,  3,  5,  6,  7,  8,  9, 10,
11, 12, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27,
28, 29, 30, 31, 32, 33, 34, 35,
36, 37, 38, 39, 40, 41, 42, 43,
44, 45, 46, 47, 48, 49, 50, 51,
52, 53, A0, A1, A2, A3, A4, A5,
A6, A7, A8, A9, A10, A11, A12, A13};
#endif
#ifdef ARDUINO_AVR_UNO
{2,  3,  5,  6,  8,  7,  9,  10,
11, 12, A0, A1, A2, A3, A4, A5};
#endif
#ifdef ARDUINO_AVR_MICRO
{2,  3,  4,  5,  6,  7,  8,  9,
10, 14, 15, 16, A0, A1, A2, A3};
#endif
#ifdef ARDUINO_AVR_LEONARDO
{2,  3,  4,  5,  6,  7,  8,  9
};//10, 14, 15, 16, A0, A1, A2, A3};
#endif*/
byte address = 0xFF, src_interfase = 1;
byte pin_state[PIN_COUNT];
unsigned long pin_times[PIN_COUNT], pin_delay[PIN_COUNT];
Relay relays[PIN_COUNT];
const int pin_rs485 = A3;

void readPacket();
void sendPacket(byte c, byte d0 = 0, byte d1 = 0, byte d2 = 0, byte d3 = 0);
void writeAddress(byte a);
byte writeRelay(byte id, byte type, word groups);
void changeRelay(byte i, byte state, word tm);
void clearRelays();
void switchOnAndOff();


void Relay::writePin() {
	switch (type) {
	case None:
		digitalWrite(pin, LOW);
		return;
	case Normal:
		digitalWrite(pin, state == 0 ? LOW : HIGH);
		return;
	case Inverse:
		digitalWrite(pin, state == 1 ? LOW : HIGH);
		return;
	}
}


byte Relay::readPin() {
	switch (type) {
	case None:
		return 0;
	case Normal:
		return (digitalRead(pin) == HIGH) ? 1 : 0;
	case Inverse:
		return (digitalRead(pin) == LOW) ? 1 : 0;
	}
	return 0;
}


void setup() {
	pinMode(LED_BUILTIN, OUTPUT);
	digitalWrite(LED_BUILTIN, LOW);
	address = EEPROM.read(1);
	if (address == 0xFF)
		writeAddress(0);
	for (int i = 0; i < PIN_COUNT; ++i) {
		pinMode(pin_map[i], OUTPUT);
		EEPROM.get(ADDR_G + i*sizeof(Relay), relays[i]);
		relays[i].pin = pin_map[i];
		relays[i].writePin();
		pin_state[i] = 0;
		pin_times[i] = pin_delay[i] = 0;
	}
	int timeout = 9 * 1000 * 1.5 / (9600 / 10);
	Serial.begin(9600);
	Serial.setTimeout(timeout);
#ifdef SER1
	pinMode(pin_rs485, OUTPUT);
	digitalWrite(pin_rs485, LOW);
	Serial1.begin(9600);
	Serial1.setTimeout(timeout);
#endif
	digitalWrite(LED_BUILTIN, HIGH);
}


void loop() {
	readPacket();
	switchOnAndOff();
}


void readPacket() {
	byte pckt[9] = {0,0,0,0,0,0,0,0,0};
	bool readed = false;
	if (Serial.available()) {
		Serial.readBytes(pckt, 9);
		readed = true;
		src_interfase = 1;
	}
#ifdef SER1
	if (Serial1.available()) {
		Serial1.readBytes(pckt, 9);
		readed = true;
		src_interfase = 2;
	}
#endif
	if (readed) {
		if (pckt[0] != 0xE0 || pckt[1] != 0xA0 || pckt[8] != 0xEF) return;
		if (pckt[2] != address && pckt[2] != 0xFF) return;
		byte i = pckt[4];
		switch (pckt[3]) {
		case 0x10:
			sendPacket(0x11, PIN_COUNT);
			break;
		case 0x12:
			if (i < PIN_COUNT) {
				word rg = relays[i].groups;
				sendPacket(0x13, i, relays[i].type, highByte(rg), lowByte(rg));
			}
			break;
		case 0x14:
			if (i < PIN_COUNT) {
				byte res = writeRelay(i, pckt[5], word(pckt[6], pckt[7]));
				sendPacket(0x15, i, res);
			}
			break;
		case 0x16:
			if (i == 4 && pckt[5] == 3 && pckt[6] == 2 && pckt[7] == 1) {
				clearRelays();
				sendPacket(0x23);
			}
			break;
		case 0x17:
			if (i == 0xFF) {
				for (int j = 0; j < PIN_COUNT; ++j) {
					if (relays[j].type != None)
						sendPacket(0x18, j, relays[j].readPin());
				}
			} else if (i < PIN_COUNT) {
				sendPacket(0x18, i, relays[i].readPin());
			}
			break;
		case 0x20:
			{
			word dms = word(pckt[6], pckt[7]);
			if (i < 0x80) {
				if (i < PIN_COUNT)
					changeRelay(i, pckt[5], dms);
			} else {
				byte g = i & 0x7F;
				if (g < 16) {
					for (int j = 0; j < PIN_COUNT; ++j)
						if (relays[j].hasGroup(g))
							changeRelay(j, pckt[5], dms);
				}
			}
			}
			break;
		case 0x22:
			sendPacket(0x23);
			break;
		case 0x25:
			if (pckt[4] == pckt[5] && pckt[4] == pckt[6] && pckt[4] == pckt[7])
				writeAddress(pckt[4]);
			sendPacket(0x23);
			break;
		default: break;
		}
	}
}


void switchOnAndOff() {
	for (int i = 0; i < PIN_COUNT; ++i) {
		if (pin_state[i] != 1) continue;
		unsigned long ms = millis();
		if (pin_delay[i] <= (ms - pin_times[i])) {
			pin_state[i] = 0;
			pin_times[i] = pin_delay[i] = 0;
			relays[i].state = 0;
			relays[i].writePin();
			EEPROM.put(ADDR_G + i*sizeof(Relay), relays[i]);
		}
	}
}


void sendPacket(byte c, byte d0, byte d1, byte d2, byte d3) {
	byte pckt[9] = {0xE0, 0xA0, address, c, d0, d1, d2, d3, 0xEF};
	digitalWrite(LED_BUILTIN, LOW);
	if (src_interfase == 1) {
		if (Serial)
			Serial.write(pckt, 9);
	}
#ifdef SER1
	if (src_interfase == 2) {
		digitalWrite(pin_rs485, HIGH);
		Serial1.write(pckt, 9);
		Serial1.flush();
		//delay(9);
		digitalWrite(pin_rs485, LOW);
		Serial.print("send packet ");
		Serial.println(c);
	}
#endif
	digitalWrite(LED_BUILTIN, HIGH);
}


void writeAddress(byte a) {
	address = a;
	EEPROM.write(1, address);
}


byte writeRelay(byte id, byte type, word groups) {
	if (type > Inverse) 2;
	relays[id].type = type;
	relays[id].groups = groups;
	relays[id].writePin();
	EEPROM.put(ADDR_G + id*sizeof(Relay), relays[id]);
	return 0;
}


void clearRelays() {
	for (int i = 0; i < PIN_COUNT; ++i) {
		relays[i] = Relay();
		relays[i].pin = pin_map[i];
		EEPROM.put(ADDR_G + i*sizeof(Relay), relays[i]);
	}
}


void changeRelay(byte i, byte state, word tm) {
	digitalWrite(LED_BUILTIN, LOW);
	if (state < 2) {
		pin_state[i] = 0;
		pin_times[i] = pin_delay[i] = 0;
		relays[i].state = state;
		relays[i].writePin();
	} else if (state == 2) {
		pin_state[i] = 1;
		relays[i].state = 1;
		relays[i].writePin();
		pin_times[i] = millis();
		pin_delay[i] = tm;
		/*if (ok) sendPacket(0x1A, pckt[4], highByte(v), lowByte(v));
		if (writeParameter((Parameter)(pckt[4]), ))
			sendPacket(0x23);*/
	}
	EEPROM.put(ADDR_G + i*sizeof(Relay), relays[i]);
	digitalWrite(LED_BUILTIN, HIGH);
}
