#include <EEPROM.h>
#include "DHT.h"
#include "MHZ.h"
#include "TM1637Display.h"
#include "piflags.h"

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

enum Sensor {sTemperature = 0x1,
			 sHumidity    = 0x2,
			 sCO2         = 0x4,
			 sLight       = 0x8,
			};

typedef PIFlags<Sensor> Sensors;

#pragma pack(push, 1)
union State {
	byte raw;
	struct {
		byte TH : 1;
		byte CO2: 1;
		byte L  : 1;
	};
};
struct Data {
	Data() {state.raw = 0; T = H = CO2 = L = -1.f;}
	State state;
	float T;
	float H;
	float CO2;
	float L;
};
struct OutPacket {
	OutPacket() {memset(this, 0, sizeof(*this));}
	State state;
	byte H;
	short T;
	unsigned short CO2;
	unsigned short L;
};
struct Settings {
	Settings() {
		bright_idle = 1;
		bright_full = 7;
		fade_timeout_ms = 3000;
		set_temp = 20.f;
		step_temp = 0.5f;
		offset_temp = 0.f;
		disp_on = 1;
		disp_states = 0xFF;
		disp_change_ms = 5000;
		disp_co2_raw = 0;
		disp_co2_perc_0 = 400;
		disp_co2_perc_100 = 1400;
	}
	byte bright_idle;
	byte bright_full;
	word fade_timeout_ms;
	float set_temp;
	float step_temp;
	float offset_temp;
	byte disp_on;
	byte disp_states;
	word disp_change_ms;
	byte disp_co2_raw;
	word disp_co2_perc_0;
	word disp_co2_perc_100;
};
#pragma pack(pop)

enum BrightState {
	bsIdle = 1,
	bsFading,
	bsFull
};
enum DisplayPhase {
	dpT,
	dpH,
	dpCO2,
	dpTime,
	dpCount
};

#define BTN_COUNT  2
#define T_COUNT    5
#define T_MAX_MISS 5

byte address = 0xFF, src_interfase = 0;
byte bright_state = bsIdle, bright = 1;
word bounce_ms = 10;
const float disp_CO2_0 = 400.f, disp_CO2_100 = 1400.f;

const byte pin_map[BTN_COUNT] = {16, 14};
byte pin_state[BTN_COUNT], pin_state_prev[BTN_COUNT];
bool pin_bel[BTN_COUNT], btn_pressed[BTN_COUNT];
unsigned long pin_times[BTN_COUNT], last_btn_time, last_bright_time, last_read_time;
byte rec_pckt[9] = {0,0,0,0,0,0,0,0,0}, rec_bytes_cnt, rec_missed;
byte snd_pckt[13] = {0x20, 0x5E, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xEF};

const unsigned long msInDay = 24L * 60L * 60L * 1000L;
unsigned long cur_time = 0, last_time = 0;
byte time_disp[4] = {0, 0, 0, 0};

const int pin_rs485 = 4, pin_disp_clk = 5, pin_disp_dio = 6;
short cur_data = 0, cur_t_arr = -1, t_miss = 0;
unsigned long ptime = 0, co2time = 0, disptime = 0, last_disp_change = 0;
float t_arr[T_COUNT];
byte t_arr_cnt = 0;
byte disp_phase = dpT;
Data data;
Settings sett;

void readPacket();
void sendPacket(byte c, const OutPacket & p = OutPacket());
void readDHT();
void readMHZ();
void readButtons();
void brightOn();
void checkBrightness();
void showDisplay();
void writeAddress(byte a);
void writeSettings();


DHT dht(2, DHT22);
MHZ mhz(8, 9, 0, MHZ14A);
TM1637Display disp(pin_disp_clk, pin_disp_dio);


void setup() {
	pinMode(LED_BUILTIN, OUTPUT);
	digitalWrite(LED_BUILTIN, HIGH);
	address = EEPROM.read(1);
	if (address == 0xFF)
		writeAddress(0);
	EEPROM.get(2, sett);
	if (sett.bright_idle == 0xFF) {
		sett = Settings();
		writeSettings();
	}
	bright = sett.bright_idle;
	dht.begin();
	//mhz.setDebug(true);
	Serial.setTimeout(0);
	Serial.begin(9600);
#ifdef SER1
	pinMode(pin_rs485, OUTPUT);
	digitalWrite(pin_rs485, LOW);
	Serial1.setTimeout(0);
	Serial1.begin(9600);
#endif
	for (byte i = 0; i < BTN_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] = btn_pressed[i] = false;
	}
	for (byte i = 0; i < T_COUNT; ++i)
		t_arr[i] = 0.f;
	rec_bytes_cnt = rec_missed = 0;
	last_btn_time = last_bright_time = last_read_time = millis();
	showDisplay();
	data = Data();
	digitalWrite(LED_BUILTIN, LOW);
	last_read_time = 0;
	last_time = millis();
}


void loop() {
	unsigned long time = millis();
	unsigned long dtime = time - last_time;
	if (dtime >= 250) {
		cur_time = (cur_time + dtime) % msInDay;
		last_time = time;
		unsigned long h = cur_time / 1000;
		unsigned long s = h % 60; h -= s; h /= 60;
		unsigned long m = h % 60; h -= m; h /= 60;
		time_disp[0] = disp.encodeDigit(h / 10);
		time_disp[1] = disp.encodeDigit(h % 10) | 0x80;
		time_disp[2] = disp.encodeDigit(m / 10);
		time_disp[3] = disp.encodeDigit(m % 10);
		//Serial.print(h);
		//Serial.print(":");
		//Serial.print(m);
		//Serial.print(":");
		//Serial.print(s);
		//Serial.print("\n");
	}
	unsigned long time_co2 = time + ((unsigned long)address * 500);
	unsigned long dt = time - ptime;
	unsigned long dt_co2 = time_co2 - co2time;
	unsigned long dt_disp = time - disptime;
	if (dht.prepareRead())
		readDHT();
	mhz.readCO2UART();
	if (dt >= 2100) {
		ptime = time;
		dht.beginPrepareRead();
		readMHZ();
		/*
		Data d = currentData();
		Serial.print("T = "); Serial.print(d.T);
		Serial.print(", H = "); Serial.print(d.H);
		Serial.print(", CO2 = "); Serial.print(d.CO2);
		Serial.println("");
		*/
	}
	if (dt_co2 >= 5000) {
		co2time = time_co2;
		mhz.sendCO2UARTRequest();
	}
	if (dt_disp >= 110) {
		disptime = time;
		showDisplay();
	}
	readPacket();
	readButtons();
}


void readDHT() {
	if (!dht.readyRead())
		return;
	float t = dht.readTemperature();
	float h = dht.readHumidity();
	if (isnan(t) || isnan(h)) {
		if (t_miss >= T_MAX_MISS) {
			data.state.TH = 0;
		} else
			t_miss++;
		return;
	}
	t_miss = 0;
	if (t_arr_cnt < T_COUNT)
		++t_arr_cnt;
	cur_t_arr = (++cur_t_arr) % T_COUNT;
	t_arr[cur_t_arr] = t;
	t = 0.f;
	for (byte i = 0; i < T_COUNT; ++i)
		t += t_arr[i];
	t /= t_arr_cnt;
	data.T = t + sett.offset_temp;
	data.H = h;
	data.state.TH = 1;
}


void readMHZ() {
	if (mhz.isPreHeating()) return;
	int ppm_uart = mhz.getLastPPM();
	data.CO2 = ppm_uart;
	data.state.CO2 = 1;
}


void readButtons() {
	bool was_on = (bright_state != bsIdle);
	for (byte i = 0; i < BTN_COUNT; ++i) {
		btn_pressed[i] = false;
		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;
			pin_times[i] = 0;
			pin_bel[i] = false;
		} else {
			if (ps != pin_state[i]) {
				pin_times[i] = ms;
				pin_bel[i] = true;
			}
			continue;
		}
		if (pin_state[i] == ps) continue;
		pin_state[i] = pin_state_prev[i] = ps;
		if (ps == 0) {
			btn_pressed[i] = true;
			last_btn_time = ms;
			brightOn();
		}
	}
	if (!was_on) return;
	if (btn_pressed[0]) sett.set_temp -= sett.step_temp;
	if (btn_pressed[1]) sett.set_temp += sett.step_temp;
}


void brightOn() {
	last_bright_time = millis();
	bright_state = bsFull;
	showDisplay();
}


void checkBrightness() {
	unsigned long ms = millis();
	switch (bright_state) {
	case bsIdle:
		bright = sett.bright_idle;
		break;
	case bsFading:
		if ((ms - last_bright_time) >= 400) {
			bright--;
			last_bright_time = ms;
		}
		if (bright == sett.bright_idle)
			bright_state = bsIdle;
		break;
	case bsFull:
		bright = sett.bright_full;
		if ((ms - last_bright_time) >= sett.fade_timeout_ms) {
			writeSettings();
			bright_state = bsFading;
		}
		break;
	}
}


void showDisplay() {
	checkBrightness();
	bool is_on = (bright_state != bsIdle);
	
	unsigned long ms = millis();
	byte _cnt = 0;
	if (ms - last_disp_change >= sett.disp_change_ms) {
		last_disp_change = ms;
		while (true) {
			disp_phase = (++disp_phase) % dpCount;
			if ((sett.disp_states & (1 << disp_phase)) > 0) break;
			if (++_cnt >= 5) {
				disp_phase = dpT;
				break;
			}
		}
	}
	byte cur_phase = disp_phase;
	if (is_on) cur_phase = dpT;
	
	byte d[4] = {0, 0, 0, 0};
	if ((sett.disp_on > 0) || is_on) {
		if (data.state.TH == 0) {
			if (t_miss >= T_MAX_MISS) {
				d[1] = 0b01111001;
				d[2] = 0b00110001;
				d[3] = 0b00110001;
			} else {
				d[1] = 0b01110100;
				d[2] = 0b00000110;
			}
		} else {
			switch (cur_phase) {
			case dpT: {
				int v = round((is_on ? sett.set_temp : data.T) * 10.f);
				int av = abs(v);
				byte dc = 2;
				if (av >= 100) dc++;
				for (byte i = 0; i < dc; ++i) {
					d[3 - i] = disp.encodeDigit(av % 10);
					av /= 10;
				}
				d[2] |= 0x80;
				if (v < 0) d[3 - dc] = 0b01000000;
				} break;
			case dpH: {
				int v = round(data.H);
				byte dc = 1;
				if (v >= 10) dc++;
				if (v >= 100) dc++;
				for (byte i = 0; i < dc; ++i) {
					d[3 - i] = disp.encodeDigit(v % 10);
					v /= 10;
				}
				d[0] = 0b01110100;
				} break;
			case dpCO2: {
				int v = round(data.CO2);
				if (sett.disp_co2_raw == 1) {
					v = round(float(v) / 10.f);
				} else {
					float CO2_range = (float)sett.disp_co2_perc_100 - (float)sett.disp_co2_perc_0;
					if (CO2_range > 1)
						v = round(100.f * (data.CO2 - (float)sett.disp_co2_perc_0) / CO2_range);
					if (v < 0)   v = 0;
					if (v > 100) v = 100;
				}
				byte dc = 1;
				if (v >= 10) dc++;
				if (v >= 100) dc++;
				for (byte i = 0; i < dc; ++i) {
					d[3 - i] = disp.encodeDigit(v % 10);
					v /= 10;
				}
				if (sett.disp_co2_raw == 1)
					d[1] |= 0x80;
				d[0] = 0b00111001;
				} break;
			case dpTime:
				for (byte i = 0; i < 4; ++i)
					d[i] = time_disp[i];
				break;
			}
		}
	}
	disp.setSegments(d);
	disp.setBrightness(bright);
}


bool readSerial(Stream * s, byte interface) {
	if (s->available()) {
		unsigned long cm = millis();
		if ((cm - last_read_time >= 5) && (rec_missed > 1))
			rec_bytes_cnt = 0;
		last_read_time = cm;
		int ab = min(s->available(), 9 - rec_bytes_cnt);
		if (ab < 0) {
			rec_bytes_cnt = 0;
			return false;
		}
		s->readBytes(&(rec_pckt[rec_bytes_cnt]), ab);
		rec_bytes_cnt += ab;
		src_interfase = interface;
		return true;
	}
	return false;
}


void readPacket() {
	bool readed = false;
	readed |= readSerial(&Serial , 1);
#ifdef SER1
	readed |= readSerial(&Serial1, 2);
#endif
	if (!readed || (rec_bytes_cnt < 9)) {
		rec_missed++;
		return;
	}
	rec_bytes_cnt = rec_missed = 0;
	if (rec_pckt[0] != 0x20 || rec_pckt[1] != 0x5E || rec_pckt[8] != 0xEF) return;
	if (rec_pckt[2] != address && rec_pckt[2] != 0xFF) return;
	OutPacket p;
	byte * pb = (byte*)&p;
	word w;
	short sw;
	switch (rec_pckt[3]) {
	case 0x10:
		p.state = data.state;
		p.T   = max(0, round(data.T * 10));
		p.H   = max(0, round(data.H));
		p.CO2 = max(0, data.CO2);
		p.L   = max(0, data.L);
		sendPacket(0x11, p);
		break;
		
	case 0x12:
		sett.bright_idle = rec_pckt[4];
		sett.bright_full = rec_pckt[5];
		sett.fade_timeout_ms = word(rec_pckt[6], rec_pckt[7]);
		writeSettings();
	case 0x13:
		pb[0] = sett.bright_idle;
		pb[1] = sett.bright_full;
		pb[2] = highByte(sett.fade_timeout_ms);
		pb[3] =  lowByte(sett.fade_timeout_ms);
		sendPacket(0x14, p);
		break;
		
	case 0x15:
		sett.step_temp =  ((short)word(rec_pckt[4], rec_pckt[5])) / 10.f;
		writeSettings();
	case 0x16:
		w = round(sett.step_temp * 10.f);
		pb[0] = highByte(w);
		pb[1] =  lowByte(w);
		sendPacket(0x17, p);
		break;
		
	case 0x18:
		sett.set_temp = ((short)word(rec_pckt[4], rec_pckt[5])) / 10.f;
		writeSettings();
	case 0x19:
		sw = round(sett.set_temp * 10.f);
		pb[0] = highByte(sw);
		pb[1] =  lowByte(sw);
		sendPacket(0x1A, p);
		break;
		
	case 0x1B:
		sett.offset_temp = ((short)word(rec_pckt[4], rec_pckt[5])) / 10.f;
		writeSettings();
	case 0x1C:
		sw = round(sett.offset_temp * 10.f);
		pb[0] = highByte(sw);
		pb[1] =  lowByte(sw);
		sendPacket(0x1D, p);
		break;
		
	case 0x1E:
		sett.disp_on = rec_pckt[4];
		if ((rec_pckt[6] != 0) && (rec_pckt[7] != 0)) {
			sett.disp_states = rec_pckt[5];
			sett.disp_change_ms = word(rec_pckt[6], rec_pckt[7]);
		}
		writeSettings();
	case 0x1F:
		pb[0] = sett.disp_on;
		pb[1] = sett.disp_states;
		pb[2] = highByte(sett.disp_change_ms);
		pb[3] =  lowByte(sett.disp_change_ms);
		sendPacket(0x20, p);
		break;
		
	case 0x30:
		sett.disp_co2_raw = rec_pckt[4];
		writeSettings();
	case 0x31:
		pb[0] = sett.disp_co2_raw;
		sendPacket(0x32, p);
		break;
		
	case 0x33:
		sett.disp_co2_perc_0   = word(rec_pckt[4], rec_pckt[5]);
		sett.disp_co2_perc_100 = word(rec_pckt[6], rec_pckt[7]);
		writeSettings();
	case 0x34:
		pb[0] = highByte(sett.disp_co2_perc_0);
		pb[1] =  lowByte(sett.disp_co2_perc_0);
		pb[2] = highByte(sett.disp_co2_perc_100);
		pb[3] =  lowByte(sett.disp_co2_perc_100);
		sendPacket(0x35, p);
		break;
		
	case 0x36:
		cur_time = *(unsigned long*)(&(rec_pckt[4]));
		last_time = millis();
		sendPacket(0x37, p);
		break;
		
	case 0x22:
		sendPacket(0x23);
		break;
	case 0x25:
		if (rec_pckt[4] == rec_pckt[5] && rec_pckt[4] == rec_pckt[6] && rec_pckt[4] == rec_pckt[7])
			writeAddress(rec_pckt[4]);
		sendPacket(0x23);
		break;
	default: break;
	}
}


void sendPacket(byte c, const OutPacket & p) {
	snd_pckt[2] = address;
	snd_pckt[3] = c;
	memcpy(&(snd_pckt[4]), &p, sizeof(p));
	digitalWrite(LED_BUILTIN, HIGH);
	if (src_interfase == 1) {
		if (Serial)
			Serial.write(snd_pckt, 13);
	} else {
#ifdef SER1
		digitalWrite(pin_rs485, HIGH);
		delay(2); /// WARNING!
		Serial1.write(snd_pckt, 13);
		Serial1.flush();
		//delay(9);
		digitalWrite(pin_rs485, LOW);
#endif
	}
	digitalWrite(LED_BUILTIN, LOW);
}


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


void writeSettings() {
	EEPROM.put(2, sett);
}
