#include <EEPROM.h>

enum GroupType {None = 0, Switch, Button};
enum WriteError {Ok = 0, NoSuchGroup, InvalidType};
enum Parameter {BounceDuration = 1, Mode, DblPressDelay, LongPressDelay, LongLongPressDelay, ParametersCount};
enum WorkMode {NoEvents = 1, Events, PermissionSignal};
enum EventType {Changed = 1, Pressed = 2, Released = 3,
	DblPressed = 4, LongPressed = 5,
	Clicked = 6, LongLongPressed = 7};

struct Group {
	Group() {id = 0; for (int i = 0; i < 8; ++i) pins[i] = 0; type = None;}
	byte id;
	byte pins[8];
	byte type;
};

#define SHS

#define ADDR_G    0x10
#ifdef __AVR_ATmega2560__
#  ifdef SHS
#    define MAX_GC    64
#    define PIN_COUNT 35
#  else
#    define MAX_GC    64
#    define PIN_COUNT 64
#  endif
#else
#  ifdef __AVR_ATmega32U4__
#    define MAX_GC    32
#    define PIN_COUNT 16
#    undef SERIAL_PORT_HARDWARE1
#  else
#    define MAX_GC    32
#    define PIN_COUNT 16
#  endif
#endif

const byte pin_map[PIN_COUNT] = 
#ifdef __AVR_ATmega2560__
#  ifdef SHS
{A0, A1, A2, A3, A4, A5, A6, A7,
A8, A9, A10, A11, A12, A13, A14, A15,
53, 51,  7,  6,  5,  3,  2, 14,
15, 16, 17, 18, 19, 20, 21, 23,
25, 27, 29
/*,  2, 15, 16,
17, 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,
A14, A15*/};
#  else
{2,  3,  5,  6,  7,  8,  9, 10,
11, 12, 14, 15, 16, 17, 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, A14, A15
};
#  endif
#else
#  ifdef __AVR_ATmega32U4__
{A3, A2, A1, A0, 15, 14, 16, 10,
 9,  8,  7,  6,  5,  4,  3,  2};
#  else
{2, 3,  5,  6,  7,  8,  9,  10,
11, 12, A0, A1, A2, A3, A4, A5};
#  endif
#endif
byte address = 0xFF, cur_gc = 0, src_interfase = 0, mode = 1, ps_ind = 0;
word bounce_ms = 0, dbl_delay = 0, long_delay = 0, longlong_delay = 0;
word groups_val[MAX_GC];
byte pin_state[PIN_COUNT], pin_state_prev[PIN_COUNT];
bool pin_bel[PIN_COUNT], groups_ch[MAX_GC];
unsigned long pin_times[PIN_COUNT], groups_ms[MAX_GC], groups_wlp[MAX_GC];
Group groups[MAX_GC];
unsigned long rec_ms = 0;
const int pin_rs485 = 4;

void readPacket();
void sendPacket(byte c, byte d0 = 0, byte d1 = 0, byte d2 = 0, byte d3 = 0);
void writeAddress(byte a);
WriteError writeGroup(byte id, byte type, byte pin_byte, byte pin_value);
Group getGroup(byte id);
int groupIndex(byte id);
word groupValue(const Group & g);
bool writeParameter(Parameter p, word v);
word getParameter(Parameter p, bool * ok);
void clearGroups();
void detectEvents();


void setup() {
	pinMode(LED_BUILTIN, OUTPUT);
	digitalWrite(LED_BUILTIN, HIGH);
#ifdef SERIAL_PORT_HARDWARE1
	pinMode(pin_rs485, OUTPUT);
	digitalWrite(pin_rs485, LOW);
#endif
	address = EEPROM.read(1);
	if (address == 0xFF)
		writeAddress(0);
	EEPROM.get(2 , bounce_ms);
	EEPROM.get(4 , mode);
	EEPROM.get(6 , dbl_delay);
	EEPROM.get(8 , long_delay);
	EEPROM.get(10, longlong_delay);
	if (bounce_ms      == 0xFFFF) bounce_ms      = 20;
	if (dbl_delay      == 0xFFFF) dbl_delay      = 500;
	if (long_delay     == 0xFFFF) long_delay     = 2000;
	if (longlong_delay == 0xFFFF) longlong_delay = 5000;
	for (int i = 0; i < PIN_COUNT; ++i) {
		pinMode(pin_map[i], INPUT);
		digitalWrite(pin_map[i], HIGH);
		pin_state[i] = pin_state_prev[i] = digitalRead(pin_map[i]);
		pin_times[i] = 0;
		pin_bel[i] = false;
	}
	for (int i = 0; i < MAX_GC; ++i) {
		EEPROM.get(ADDR_G + i*sizeof(Group), groups[i]);
		if (groups[i].type >= Switch && groups[i].type <= Button) {
			cur_gc++;
			groups_val[i] = groupValue(groups[i]);
		}
		groups_ch[i] = false;
		groups_ms[i] = groups_wlp[i] = 0;
	}
	int timeout = 9 * 1000 * 1.5 / (9600 / 10);
	Serial.begin(9600);
	Serial.setTimeout(timeout);
#ifdef SERIAL_PORT_HARDWARE1
	Serial1.begin(9600);
	Serial1.setTimeout(timeout);
#endif
	digitalWrite(LED_BUILTIN, LOW);
}


void loop() {
	readPacket();
	if (rec_ms > 0) {
		if (millis() - rec_ms > 1000)
			rec_ms = 0;
	}
	detectEvents();
}


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 SERIAL_PORT_HARDWARE1
	if (Serial1.available()) {
		Serial1.readBytes(pckt, 9);
		readed = true;
		src_interfase = 2;
	}
#endif
	if (readed) {
		if (pckt[0] != 0xD0 || pckt[1] != 0xC0 || pckt[8] != 0xEF) return;
		if (pckt[2] != address && pckt[2] != 0xFF) return;
		switch (pckt[3]) {
		case 0x10:
			sendPacket(0x11, MAX_GC, cur_gc, PIN_COUNT);
			break;
		case 0x12:
			{
			Group g = getGroup(pckt[4]);
			byte bv = 0;
			if (pckt[5] < 8)
				bv = g.pins[pckt[5]];
			sendPacket(0x13, g.id, g.type, pckt[5], bv);
			}
			break;
		case 0x14:
			{
			byte res = writeGroup(pckt[4], pckt[5], pckt[6], pckt[7]);
			sendPacket(0x15, pckt[4], res, cur_gc);
			}
			break;
		case 0x16:
			if (pckt[4] == 4 && pckt[5] == 3 && pckt[6] == 2 && pckt[7] == 1) {
				clearGroups();
				sendPacket(0x23);
			}
			break;
		case 0x17:
			if (pckt[4] < MAX_GC) {
				Group g = groups[pckt[4]];
				byte bv = 0;
				if (pckt[5] < 8)
					bv = g.pins[pckt[5]];
				sendPacket(0x13, g.id, g.type, pckt[5], bv);
			}
			break;
		case 0x18:
			if (writeParameter((Parameter)(pckt[4]), word(pckt[5], pckt[6])))
				sendPacket(0x23);
			break;
		case 0x19:
			{
			bool ok = false;
			word v = getParameter((Parameter)(pckt[4]), &ok);
			if (ok) sendPacket(0x1A, pckt[4], highByte(v), lowByte(v));
			else sendPacket(0x24);
			}
			break;
		case 0x1B:
			{
			int ind = groupIndex(pckt[4]);
			if (ind < 0) return;
			word v = groupValue(groups[ind]);
			//delay(1);
			sendPacket(0x1C, pckt[4], highByte(v), lowByte(v));
			}
			break;
		case 0x1D:
			permissionReceived();
			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;
		case 0x40:
			if (pckt[4] == 1 && pckt[5] == 2 && pckt[6] == 3 && pckt[7] == 4) {
				rec_ms = millis();
			}
			break;
		default: break;
		}
	}
}


void detectEvents() {
	for (int i = 0; i < PIN_COUNT; ++i) {
		byte ps = digitalRead(pin_map[i]);
		unsigned long ms = millis();
		if (pin_bel[i]) { // bounce elimination
			if (ps != pin_state_prev[i]) {
				pin_state_prev[i] = ps;
				pin_times[i] = ms;
			}
			if (ms - pin_times[i] <= bounce_ms)
				continue;
			//Serial.println("bounce end");
			pin_times[i] = 0;
			pin_bel[i] = false;
		} else {
			if (ps != pin_state[i]) {
				//Serial.println("bounce begin");
				pin_times[i] = ms;
				pin_bel[i] = true;
			}
			continue;
		}
		//sendPacket(0xAA, i, pin_map[i], ps);
		//Serial.print("pinDetect ");
		//Serial.println(ps);
		if (pin_state[i] == ps) continue;
		pin_state[i] = pin_state_prev[i] = ps;
		if (rec_ms > 0) {
			sendPacket(0x41, i);
			continue;
		}
		if (mode != NoEvents) {
			for (int j = 0; j < cur_gc; ++j) {
				Group & g(groups[j]);
				if ((g.pins[i / 8] & bit(i % 8)) > 0) {
					word val = 0;
					byte bn = 0;
					for (int b = 0; b < PIN_COUNT; ++b) {
						if ((g.pins[b / 8] & bit(b % 8)) > 0) {
							if (pin_state[b] == LOW)
								val |= bit(bn);
							bn++;
						}
					}
					if (g.type == Button) {
						byte et = Pressed;
						unsigned long gms = groups_ms[j];
						if (groups_val[j] < val) { // press
							groups_wlp[j] = 0;
							groups_ms[j] = ms;
							if ((gms > 0) && (ms - gms <= dbl_delay))
								et = DblPressed;
						} else {
							/*if ((gms > 0) && (ms - gms >= long_delay)) et = LongPressed;
							else*/ et = Released;
						}
						if (mode == Events) {
							sendPacket(0x31, g.id, et, highByte(val), lowByte(val));
							if (et == Released && (groups_wlp[j] == 0))
								sendPacket(0x31, g.id, Clicked, highByte(val), lowByte(val));
						}
					} else {
						if (mode == Events)
							sendPacket(0x31, g.id, Changed, highByte(val), lowByte(val));
						if (mode == PermissionSignal) {
							if (groups_val[j] != val)
								groups_ch[j] = true;
						}
					}
					groups_val[j] = val;
				}
			}
		}
	}
	if (mode != NoEvents) {
		for (int j = 0; j < cur_gc; ++j) {
			Group & g(groups[j]);
			unsigned long ms = millis();
			word val = groups_val[j];
			if (g.type == Button && val > 0) {
				byte et = 0;
				unsigned long gms = groups_ms[j];
				if ((gms > 0) && (ms - gms >= long_delay)) {
					if (groups_wlp[j] == 1) {
						if (ms - gms >= longlong_delay) {
							et = LongLongPressed;
							groups_ms[j] = 0;
							groups_wlp[j] = 2;
						}
					}
					if (groups_wlp[j] == 0) {
						et = LongPressed;
						groups_wlp[j] = 1;
					}
				}
				if (et > 0) {
					if (mode == Events)
						sendPacket(0x31, g.id, et, highByte(val), lowByte(val));
				}
			}
		}
	}
}


void sendPacket(byte c, byte d0, byte d1, byte d2, byte d3) {
	byte pckt[9] = {0xD0, 0xC0, address, c, d0, d1, d2, d3, 0xEF};
	digitalWrite(LED_BUILTIN, HIGH);
	if (Serial)
		Serial.write(pckt, 9);
#ifdef SERIAL_PORT_HARDWARE1
	digitalWrite(pin_rs485, HIGH);
	Serial1.write(pckt, 9);
	Serial1.flush();
	//delay(9);
	digitalWrite(pin_rs485, LOW);
#endif
	digitalWrite(LED_BUILTIN, LOW);
}


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


WriteError writeGroup(byte id, byte type, byte pin_byte, byte pin_value) {
	if (type < Switch || type > Button) InvalidType;
	bool found = false;
	for (int i = 0; i < MAX_GC; ++i) {
		if (groups[i].id == id && groups[i].type != None) {
			groups[i].pins[pin_byte] = pin_value;
			groups[i].type = type;
			EEPROM.put(ADDR_G + i*sizeof(Group), groups[i]);
			found = true;
			break;
		}
	}
	if (found) return Ok;
	if (cur_gc >= MAX_GC) return NoSuchGroup;
	groups[cur_gc].id = id;
	groups[cur_gc].pins[pin_byte] = pin_value;
	groups[cur_gc].type = type;
	EEPROM.put(ADDR_G + cur_gc*sizeof(Group), groups[cur_gc]);
	cur_gc++;
	return Ok;
}


void permissionReceived() {
	for (int i = 0; i < cur_gc; ++i) {
		if (ps_ind >= cur_gc)
			ps_ind = 0;
		if (groups_ch[ps_ind]) {
			groups_ch[ps_ind] = false;
			sendPacket(0x1C, groups[ps_ind].id, highByte(groups_val[ps_ind]), lowByte(groups_val[ps_ind]));
			ps_ind++;
			break;
		}
		ps_ind++;
	}
}


Group getGroup(byte id) {
	for (int i = 0; i < MAX_GC; ++i) {
		if (groups[i].type != None)
			if (groups[i].id == id)
				return groups[i];
	}
	return Group();
}


int groupIndex(byte id) {
	for (int i = 0; i < cur_gc; ++i) {
		if (groups[i].type != None)
			if (groups[i].id == id)
				return i;
	}
	return -1;
}


word groupValue(const Group & g) {
	//word gp = word(lowByte(g.pins), highByte(g.pins));
	word val = 0;
	byte bn = 0;
	for (int b = 0; b < PIN_COUNT; ++b) {
		if ((g.pins[b / 8] & bit(b % 8)) > 0) {
			if (pin_state[b] == LOW)
				val |= bit(bn);
			bn++;
		}
	}
	return val;
}


bool writeParameter(Parameter p, word v) {
	if (p < BounceDuration || p >= ParametersCount)
		return false;
	switch (p) {
	case BounceDuration:
		bounce_ms = v;
		EEPROM.put(2, v);
		break;
	case Mode:
		mode = v;
		EEPROM.put(4, mode);
		break;
	case DblPressDelay:
		dbl_delay = v;
		EEPROM.put(6, dbl_delay);
		break;
	case LongPressDelay:
		long_delay = v;
		EEPROM.put(8, long_delay);
		break;
	case LongLongPressDelay:
		longlong_delay = v;
		EEPROM.put(10, longlong_delay);
		break;
	default: return false;
	};
	return true;
}


word getParameter(Parameter p, bool * ok) {
	if (p < BounceDuration || p >= ParametersCount) {
		if (ok) *ok = false;
		return 0;
	}
	if (ok) *ok = true;
	switch (p) {
	case BounceDuration: return bounce_ms;
	case Mode: return mode;
	case DblPressDelay: return dbl_delay;
	case LongPressDelay: return long_delay;
	case LongLongPressDelay: return longlong_delay;
	default: break;
	};
	return 0;
}


void clearGroups() {
	for (int i = 0; i < MAX_GC; ++i) {
		groups[i].id == 0;
		for (int j = 0; j < 8; ++j)
			groups[i].pins[j] = 0;
		groups[i].type = None;
		EEPROM.put(ADDR_G + i*sizeof(Group), groups[i]);
	}
	cur_gc = 0;
}
