#ifdef ARDUINO

#include <Arduino.h>
#include <stdbool.h>
#include <stdint.h>

#include "arduino.h"
#include "interface.h"
#include "symbols.h"
#include "mem.h"
#include "client.h"

#include "symbols_peripherals.h"

#if defined(ARDUINO_ARCH_AVR)
	#define COMMUNICATOR Serial
	uint8_t apins[] = {A0, A1, A2, A3, A4, A5, A6, A7};
	uint8_t dpins[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
//NodeMCU stuff
#elif defined(ARDUINO_ARCH_ESP8266)
	#include <ESP8266WiFi.h>
	#include "mtaskwifi.h"
	#define PORT 8123
	WiFiServer server(PORT);
	WiFiClient client;
	#define COMMUNICATOR client
	uint8_t apins[] = {A0};
	uint8_t dpins[] = {D0, D1, D2, D3, D4, D5, D6, D7};
//Unknown arduino device
#elif defined(ARDUINO_ARCH_ESP32)
	#include <WiFi.h>
	#include "mtaskwifi.h"
	#define PORT 8123
	WiFiServer server(PORT);
	WiFiClient client;
	#define COMMUNICATOR client
	uint8_t apins[] = {A0};
	uint8_t dpins[] = {0};
//Unknown arduino device
#else
	#error Unknown arduino device
#endif

// Additional sensors that require Wire should be added using
// ||
#if defined(HAVE_LIGHTSENSOR) || defined(HAVE_AIRQUALITYSENSOR)
	#include <Wire.h>
#endif

#ifdef HAVE_OLEDSHIELD
	#ifdef ARDUINO_ESP32_DEV //Lilygo t-wristband
		#include <TFT_eSPI.h>

		TFT_eSPI display = TFT_eSPI();
	#else
		#include <SPI.h>
		#include <Adafruit_GFX.h>
		#include <Adafruit_SSD1306.h>

		#define OLED_RESET 1
		Adafruit_SSD1306 display(OLED_RESET);
	#endif
#endif

uint64_t getmillis(void)
{
	return millis();
}

void msdelay(unsigned long ms)
{
	delay(ms);
}

bool input_available(void)
{
#ifdef ARDUINO_ARCH_ESP8266
	if (!client.connected()) {
#ifdef HAVE_OLEDSHIELD
		display.print("Server disconnected");
		display.display();
		delay(1000);
#endif
		reset();
	}
#endif
	return COMMUNICATOR.available();
}

uint8_t read_byte(void)
{
	while (!input_available())
		msdelay(5);
	return COMMUNICATOR.read();
}

void write_byte(uint8_t b)
{
	COMMUNICATOR.write(b);
}

uint8_t read_apin(uint16_t t)
{
	return analogRead(t);
}

void write_apin(uint16_t p, uint8_t v)
{
#ifdef ARDUINO_ARCH_ESP32
	//TODO not implemented in the Arduino libraries for esp32 yet
	//https://github.com/espressif/arduino-esp32/issues/4
#else
	analogWrite(p, v);
#endif
}

bool read_dpin(uint16_t t)
{
	return digitalRead(t);
}

void write_dpin(uint16_t p, bool v)
{
	digitalWrite(p, v ? HIGH : LOW);
}

void set_pinmode(uint16_t p, uint8_t mode)
{
	pinMode(p, mode == PMINPUT ? INPUT :
		mode == PMOUTPUT ? OUTPUT : INPUT_PULLUP);
}

uint16_t translate_pin_s(struct mt_Pin p)
{
	switch (p.cons) {
	case mt_Pin_AnalogPin:
		return p.data.mt_AnalogPin >= sizeof(apins)
			? 0 : apins[p.data.mt_AnalogPin];
	case mt_Pin_DigitalPin:
		return p.data.mt_DigitalPin >= sizeof(dpins)
			? 0 : dpins[p.data.mt_DigitalPin];
	}
}

uint16_t translate_pin(uint16_t i)
{
	//Digital pin
	if ((i & 1) > 0) {
		i >>= 1;
		return i >= sizeof(dpins) ? 0 : dpins[i];
	//Analog pin
	} else {
		i >>= 1;
		return i >= sizeof(apins) ? 0 : apins[i];
	}
}

#if defined(HAVE_DHT)

#if defined(HAVE_DHT_DHT)
#include <dht.h>
dht DHT;
struct mt_DHTInfo dhti;
#elif defined(HAVE_DHT_SHT)
#include <WEMOS_SHT3X.h>
SHT3X sht30(0x45);
#endif

void *dht_init(struct mt_DHTInfo a)
{
#if defined(HAVE_DHT_DHT)
	if (a.cons == mt_DHTInfo_DHT_DHT) {
		dhti = a;
		return &dhti;
	}
#elif defined(HAVE_DHT_SHT)
	if (a.cons == mt_DHTInfo_DHT_SHT) {
		sht30.~SHT3X();
		new (&sht30) SHT3X(a.data.mt_DHT_SHT);
		return &sht30;
	}
#endif
	return NULL;
}

float get_dht_temp(void *p)
{
#if defined(HAVE_DHT_DHT)
	struct mt_DHTInfo dhti = *(struct mt_DHTInfo *)p;
	switch (dhti.data.mt_DHT_DHT.f1) {
	case mt_DHTtype_DHT11:
		DHT.read11(translate_pin_s(dhti.data.mt_DHT_DHT.f0));
		break;
	case mt_DHTtype_DHT21:
		DHT.read21(translate_pin_s(dhti.data.mt_DHT_DHT.f0));
		break;
	case mt_DHTtype_DHT22:
		DHT.read22(translate_pin_s(dhti.data.mt_DHT_DHT.f0));
		break;
	}
	return DHT.temperature;
#elif defined(HAVE_DHT_SHT)
	((SHT3X *)p)->get();
	return ((SHT3X *)p)->cTemp;
#endif
}

float get_dht_humidity(void *p)
{
#if defined(HAVE_DHT_DHT)
	struct mt_DHTInfo dhti = *(struct mt_DHTInfo *)p;
	switch (dhti.data.mt_DHT_DHT.f1) {
	case mt_DHTtype_DHT11:
		DHT.read11(translate_pin_s(dhti.data.mt_DHT_DHT.f0));
		break;
	case mt_DHTtype_DHT21:
		DHT.read21(translate_pin_s(dhti.data.mt_DHT_DHT.f0));
		break;
	case mt_DHTtype_DHT22:
		DHT.read22(translate_pin_s(dhti.data.mt_DHT_DHT.f0));
		break;
	}
	return DHT.humidity*10.0;
#elif defined(HAVE_DHT_SHT)
	((SHT3X *)p)->get();
	return ((SHT3X *)p)->humidity;
#endif
}
#endif

#ifdef HAVE_I2CBUTTON
#include <LOLIN_I2C_BUTTON.h>
I2C_BUTTON button(0);
void *i2c_init(uint8_t addr)
{
	button.~I2C_BUTTON();
	new (&button) I2C_BUTTON(addr);
	return &button;
}

uint8_t i2c_abutton(void *st)
{
	I2C_BUTTON *b = (I2C_BUTTON *)st;
	if (b->get() == 0) {
		return b->BUTTON_A;
	} else {
		die("Button error");
	}
}

uint8_t i2c_bbutton(void *st)
{
	I2C_BUTTON *b = (I2C_BUTTON *)st;
	if (b->get() == 0) {
		return b->BUTTON_B;
	} else {
		die("Button error");
	}
}
#endif

#ifdef HAVE_LIGHTSENSOR
#include <BH1750.h>
BH1750 lightSensor(0x23);
void *lightsensor_init(uint8_t addr) {
	lightSensor.~BH1750();
	new (&lightSensor) BH1750(addr);
	if (!lightSensor.begin(BH1750::ONE_TIME_HIGH_RES_MODE)) {
		die("Error initializing lightsensor");
	}
	return &lightSensor;
}

float get_light(void *st)
{
	return ((BH1750 *)st)->readLightLevel();
}
#endif

#ifdef HAVE_AIRQUALITYSENSOR
#include <SparkFunCCS811.h>
CCS811 airqualitySensor(0x5b);
void *airqualitysensor_init(uint8_t addr) {
	airqualitySensor.~CCS811();
	new (&airqualitySensor) CCS811(addr);
	CCS811Core::CCS811_Status_e stat = airqualitySensor.beginWithStatus();
	if (stat != CCS811Core::CCS811_Stat_SUCCESS) {
		die("Error initializing airqualitysensor");
	}
	return &airqualitySensor;
}

void set_environmental_data(void *st, float humid, float temp) {
	((CCS811 *)st)->setEnvironmentalData(humid, temp);
}

uint16_t get_tvoc(void *st) {
	((CCS811 *)st)->readAlgorithmResults();
	return ((CCS811 *)st)->getTVOC();
}

uint16_t get_co2(void *st) {
	((CCS811 *)st)->readAlgorithmResults();
	return ((CCS811 *)st)->getCO2();
}
#endif

#ifdef HAVE_LEDMATRIX
#include <WEMOS_Matrix_LED.h>
MLED mled(5);

void *ledmatrix_init(struct mt_LEDMatrixInfo x)
{
	mled.~MLED();
	new (&mled) MLED(5, translate_pin_s(x.clockPin),
		translate_pin_s(x.dataPin));
	return &mled;
}
void ledmatrix_dot(void *st, uint8_t x, uint8_t y, bool state)
{
	((MLED *)st)->dot(x, y, state);
}
void ledmatrix_intensity(void *st, uint8_t intensity)
{
	((MLED *)st)->intensity = intensity;
}
void ledmatrix_clear(void *st)
{
	((MLED *)st)->clear();
}
void ledmatrix_display(void *st)
{
	((MLED *)st)->display();
}
#endif

void real_setup(void)
{
#if defined(ARDUINO_AVR_UNO)
	Serial.begin(9600);
#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
#ifdef HAVE_OLEDSHIELD
	#ifdef ARDUINO_ESP32_DEV //Lilygo t-wristband
		display.init();
		display.fillScreen(TFT_BLACK);
	#else
		display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
		display.clearDisplay();
		display.setTextSize(1);
		display.setTextColor(WHITE);
	#endif
	display.setCursor(0, 0);
#endif

#if defined(HAVE_LIGHTSENSOR) || defined(HAVE_AIRQUALITYSENSOR)
	Wire.begin();
#endif

	const char *ssids[] = SSIDS;
	const char *wpas[] = WPAS;

	Serial.begin(115200);
	Serial.println("Peripherals:");

#ifdef HAVE_OLEDSHIELD
	Serial.println("Have OLED");
#endif
#ifdef HAVE_LEDMATRIX
	Serial.println("Have LEDMatrix");
	mled.clear();
	mled.display();
#endif
#ifdef HAVE_DHT
	Serial.println("Have DHT");
#endif
#ifdef HAVE_AIRQUALITYSENSOR
	Serial.println("Have AQS");
#endif
#ifdef HAVE_LIGHTSENSOR
	Serial.println("Have Lightsensor");
#endif
	int ssid = -1;
	do {
		Serial.println("Scanning...");
#ifdef HAVE_OLEDSHIELD
		display.println("Scanning..");
		#ifndef ARDUINO_ESP32_DEV //lilygo t-wristband
			display.display();
		#endif
#endif
		int n = WiFi.scanNetworks();
		Serial.print("Found ");
		Serial.print(n);
		Serial.println(" networks");
#ifdef HAVE_OLEDSHIELD
		display.print(n);
		display.println(" found");
		#ifndef ARDUINO_ESP32_DEV //lilygo t-wristband
			display.display();
		#endif
#endif
		if (n == 0) {
#ifdef HAVE_OLEDSHIELD
			display.println("none found");
			#ifndef ARDUINO_ESP32_DEV //lilygo t-wristband
				display.display();
			#endif
#endif
			Serial.println("No networks found");
			delay(1000);
			continue;
		}
		for (unsigned j = 0; j<sizeof(ssids)/sizeof(char *); j++) {
			for (int i = 0; i < n; i++) {
				if (strcmp(WiFi.SSID(i).c_str(),
						ssids[j]) == 0) {
#ifdef HAVE_OLEDSHIELD
					display.print("Try ");
					display.println(WiFi.SSID(i));
					#ifndef ARDUINO_ESP32_DEV
					//lilygo t-wristband
						display.display();
					#endif
#endif
					Serial.print("Connect to: ");
					Serial.println(WiFi.SSID(i));
					ssid = j;
				}
			}
		}
	} while (ssid == -1);
	WiFi.scanDelete();
	WiFi.begin(ssids[ssid], wpas[ssid]);
	while (WiFi.status() != WL_CONNECTED) {
		delay(1000);
		Serial.print(".");
#ifdef HAVE_OLEDSHIELD
		display.print(".");

		#ifndef ARDUINO_ESP32_DEV //lilygo t-wristband
			display.display();
		#endif
#endif
	}
	Serial.print("\n");
#ifdef HAVE_OLEDSHIELD
	#ifdef ARDUINO_ESP32_DEV //lilygo t-wristband
		display.fillScreen(TFT_BLACK);
	#else
		display.clearDisplay();
	#endif
	display.setCursor(0, 0);
	display.print("Connected to: ");
	display.print(ssids[ssid]);
	display.print(" ");
	display.print(WiFi.localIP());
	display.print(":");
	display.println(PORT);
	#ifndef ARDUINO_ESP32_DEV //lilygo t-wristband
		display.display();
	#endif
#endif
	Serial.println("WiFi connected");
	Serial.println("IP address: ");
	Serial.println(WiFi.localIP());

	server.begin();
	server.setNoDelay(true);
	Serial.print("Server started on port: ");
	Serial.println(PORT);

	Serial.println("Waiting for a client to connect.");
	while (!(client = server.available())) {
		delay(10);
	}

#ifdef HAVE_OLEDSHIELD
	display.println("Client:");
	display.print(client.remoteIP());
	#ifndef ARDUINO_ESP32_DEV //lilygo t-wristband
		display.display();
	#endif
#endif
	Serial.println("Client connected\n");
#endif
	#ifdef LED_BUILTIN
		pinMode(LED_BUILTIN, OUTPUT);
		digitalWrite(LED_BUILTIN, LOW);
	#endif
}

long lastping = 0;
void real_yield(void)
{
#ifdef ARDUINO_ARCH_ESP8266
	yield();
	if (millis()-lastping > 300) {
		write_byte(MTFPING);
		write_byte('\n');
		lastping = millis();
	}
#endif
}

#if LOGLEVEL > 0
void msg_log(const char *fmt, ...)
{
#if defined(ARDUINO_AVR_UNO)
	char buf[128];
	va_list args;
	va_start(args, fmt);
	vsnprintf_P(buf,128,fmt,args);
	va_end(args);
	write_byte(MTFDEBUG);
	for (uint8_t i = 0; i<128; i++) {
		if (buf[i] == '\0') {
			write_byte(i);
			break;
		}
	}
	for (uint8_t i = 0; i<128; i++) {
		if (buf[i] == '\0')
			break;
		write_byte(buf[i]);
	}
#else
	char buf[128];
	va_list args;
	va_start(args, fmt);
	vsnprintf_P(buf,128,fmt,args);
	va_end(args);
	Serial.write(buf);
	Serial.write('\r');
#endif
}
#endif

void die(const char *fmt, ...)
{
#ifdef HAVE_OLEDSHIELD
	#ifdef ARDUINO_ESP32_DEV //lilygo t-wristband
		display.fillScreen(TFT_BLACK);
	#else
		display.clearDisplay();
	#endif
	display.print("Error: ");
	display.print(fmt);
	#ifndef ARDUINO_ESP32_DEV //lilygo t-wristband
		display.display();
	#endif
#endif
	Serial.println(fmt);
	while (1) {
		msdelay(1000);
		Serial.print("die");
	}
}

void pdie(char *s)
{
	die(s);
}

void reset(void)
{
#if defined(ARDUINO_AVR_UNO)
	Serial.end();
#elif defined(ARDUINO_ARCH_ESP8266)
#ifdef HAVE_LEDMATRIX
	mled.clear();
	mled.display();
#endif
	client.stop();
#endif
	mem_reset();
	real_setup();
}

void loop()
{
	real_loop();
}

void setup()
{
	real_main();
}
#endif
