#ifdef ARDUINO

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

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

#include "communication_interface.h"
#include "bctypes.h"

#if defined(HAVE_LIGHTSENSOR) || defined(HAVE_AIRQUALITYSENSOR)\
	|| defined(HAVE_GESTURESENSOR) || defined(HAVE_DHT_SHT)\
	|| defined(HAVE_OLEDSHIELD)
#define MTASK_USE_WIRE
#include <Wire.h>
bool wire_began = false;
#endif /* defined(HAVE_LIGHTSENSOR) || defined(HAVE_AIRQUALITYSENSOR)\
	|| defined(HAVE_GESTURESENSOR) || defined(HAVE_DHT_SHT)\
	|| defined(HAVE_OLEDSHIELD) */

#if defined(ARDUINO_ARCH_AVR)
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};

// placement new is not defined for avr arduino
void * operator new (size_t size, void * ptr) {
	return ptr;
	(void)size;
}

#elif defined(ARDUINO_ARCH_ESP8266)
uint8_t apins[] = {A0};
uint8_t dpins[] = {D0, D1, D2, D3, D4, D5, D6, D7, D8};
#include <ESP8266WiFi.h>
#elif defined(ARDUINO_ARCH_ESP32)
uint8_t apins[] = {A0};
uint8_t dpins[] = {0};
#include <WiFi.h>
#elif defined(ARDUINO_ARCH_SAMD)
uint8_t apins[] = {A0, A1 ,A2 ,A3 ,A4 ,A5};
uint8_t dpins[] = {0, 0, 0, 0, 0, 5, 6, 0, 0, 9, 10, 11, 12, 13};
uint32_t timer_offset = 0;
#include <ArduinoLowPower.h>
#include <new>
#else /* ARDUINO_ARCH_AVR */
#error Unknown arduino device
#endif /* ARDUINO_ARCH_AVR */

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

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

#define SCREEN_WIDTH 64 // OLED display width, in pixels
#define SCREEN_HEIGHT 48 // OLED display height, in pixels

//#define OLED_RESET -1
#define OLED_RESET 0
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
#endif /* ARDUINO_ESP32_DEV */
#endif /* HAVE_OLEDSHIELD */

#ifdef ARDUINO_ARCH_ESP8266
extern "C" void esp_schedule();
#endif /* ARDUINO_ARCH_ESP8266 */

volatile bool wake_up = false;

uint32_t getmillis(void)
{
	uint32_t m = millis();
#ifdef ARDUINO_ARCH_SAMD
	m += timer_offset;
#endif /* ARDUINO_ARCH_ESP8266 */
	return m;
}

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

// TODO implement sleep for UNO
void mssleep(unsigned long ms)
{
#ifdef ARDUINO_ARCH_AVR
	wake_up = false;
	uint32_t start = millis();
	while (!wake_up && millis() - start < ms) {
		yield();
	}
#elif defined(ARDUINO_ARCH_SAMD)
#ifdef COMM_MQTT
	bool disable_wifi = (active_interrupts() && ms > 15563) ||
			    (!active_interrupts() && ms > 14681);
	if (disable_wifi) {
		stop_communication(true);
		ms -= 3438; // Average time to reconnect
	}
#endif /* COMM_MQTT */
	if (active_interrupts() || ms < 1000) {
		wake_up = false;
		uint32_t start = millis();
		while (!wake_up && millis() - start < ms) {
			LowPower.idle(1);
		}
	} else {
		LowPower.deepSleep(ms);
		timer_offset += ms;
	}
#ifdef COMM_MQTT
	if (disable_wifi) {
		start_communication(get_comm_settings(), get_link_settings());
	}
#endif /* COMM_MQTT */
#else /* !ARDUINO_ARCH_AVR */
	delay(ms);
#endif /* !ARDUINO_ARCH_AVR */
}

void force_wake_up()
{
#ifdef ARDUINO_ARCH_ESP8266
	esp_schedule();
#else /* ARDUINO_ARCH_ESP8266 */
	wake_up = true;
#endif /* ARDUINO_ARCH_ESP8266 */
}

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
	(void)p;
	(void)v;
#else /* ARDUINO_ARCH_ESP32 */
	analogWrite(p, v);
#endif /* ARDUINO_ARCH_ESP32 */
}

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, enum PinMode mode)
{
	pinMode(p, mode == PMInput_c ? INPUT :
		mode == PMOutput_c ? OUTPUT : INPUT_PULLUP);
}

uint16_t translate_pin_s(struct Pin p)
{
	switch (p.cons) {
	case AnalogPin_c:
		return p.data.AnalogPin >= sizeof(apins)
			? 0 : apins[p.data.AnalogPin];
	case DigitalPin_c:
		return p.data.DigitalPin >= sizeof(dpins)
			? 0 : dpins[p.data.DigitalPin];
	}
	return 0;
}

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];
	}
	return 0;
}

int translate_interrupt_mode(enum InterruptMode mode)
{
	switch (mode) {
		case Change_c:
			return CHANGE;
			break;
		case Rising_c:
			return RISING;
			break;
		case Falling_c:
			return FALLING;
			break;
		case Low_c:
			return LOW;
			break;
		case High_c:
			return HIGH;
			break;
		default:
			die("Unknown interrupt mode");
	}
	return 0;
}

void attach_interrupt(void (*isr)(), uint16_t pin, enum InterruptMode mode)
{
	attachInterrupt(digitalPinToInterrupt(pin), isr,
			translate_interrupt_mode(mode));
}

void detach_interrupt(uint16_t pin)
{
	detachInterrupt(digitalPinToInterrupt(pin));
}

#if defined(HAVE_DHT)

#if defined(HAVE_DHT_DHT)

#include <dhtnew.h>
DHTNEW dht(4);
//struct DHTInfo dhti;

#elif defined(HAVE_DHT_SHT)
#include <WEMOS_SHT3X.h>
SHT3X sht30(0x45);
#endif /* HAVE_DHT_DHT */

void *dht_init(struct DHTInfo a)
{
#if defined(HAVE_DHT_DHT)
	if (a.cons == DHT_DHT_c) {
		dht.~DHTNEW();
		new (&dht) DHTNEW((uint8_t)translate_pin_s(a.data.DHT_DHT.f0));
		//type is autodetected here
		//dht.setType(...)
		return &dht;
	}
#elif defined(HAVE_DHT_SHT)
	if (a.cons == DHT_SHT_c) {
		sht30.~SHT3X();
		new (&sht30) SHT3X(a.data.DHT_SHT);
		msg_log(SC("init sht with: 0x%02x\n"), a.data.DHT_SHT);
		return &sht30;
	}
#endif /* HAVE_DHT_DHT */
	return NULL;
}

float get_dht_temp(void *p)
{
#if defined(HAVE_DHT_DHT)
	((DHTNEW *)p)->read();
	return ((DHTNEW *)p)->getTemperature();
#elif defined(HAVE_DHT_SHT)
	((SHT3X *)p)->get();
	return ((SHT3X *)p)->cTemp;
#endif /* HAVE_DHT_DHT */
}

float get_dht_humidity(void *p)
{
#if defined(HAVE_DHT_DHT)
	((DHTNEW *)p)->read();
	return ((DHTNEW *)p)->getHumidity();
#elif defined(HAVE_DHT_SHT)
	((SHT3X *)p)->get();
	return ((SHT3X *)p)->humidity;
#endif /* HAVE_DHT_DHT */
}
#endif /* HAVE_DHT */

#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) {
		die("Button error");
	}
	return b->BUTTON_A;
}

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

#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 /* HAVE_LIGHTSENSOR */

#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 initialising 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 /* HAVE_AIRQUALITYSENSOR */

#ifdef HAVE_GESTURESENSOR
#include "paj7620.h"
void *gesturesensor_init(uint8_t addr)
{
	uint8_t error = paj7620Init();
	if (error) {
		die("Error initialisinggesture paj7620 sensor");
	}
	return (void *)(uintptr_t)addr;
}

uint8_t get_gesture(void *st)
{
	uint8_t data = 0, error;
	if ((error = paj7620ReadReg((int)st, 1 , &data))) {
		die("Error reading gesture paj7620 sensor");
	} else {
		switch (data) {
		case GES_RIGHT_FLAG:
			data = 1;
			break;
		case GES_LEFT_FLAG:
			data = 2;
			break;
		case GES_UP_FLAG:
			data = 3;
			break;
		case GES_DOWN_FLAG:
			data = 4;
			break;
		case GES_FORWARD_FLAG:
			data = 5;
			break;
		case GES_BACKWARD_FLAG:
			data = 6;
			break;
		case GES_CLOCKWISE_FLAG:
			data = 7;
			break;
		case GES_COUNT_CLOCKWISE_FLAG:
			data = 8;
			break;
		default:
			data = 0;
			break;
		}
	}
	return data;
}
#endif /* HAVE_GESTURESENSOR */

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

void *ledmatrix_init(struct 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 /* HAVE_LEDMATRIX */

#ifdef HAVE_OLEDSHIELD
void print_to_display(const char *msg)
{
	display.print(msg);
}

void flush_display()
{
	display.display();
}

void clear_display()
{
#ifdef ARDUINO_ESP32_DEV //lilygo t-wristband
	display.fillScreen(TFT_BLACK);
#else /* ARDUINO_ESP32_DEV */
	display.clearDisplay();
#endif /* ARDUINO_ESP32_DEV */
	display.setCursor(0, 0);
}
#endif /* HAVE_OLEDSHIELD */

void real_setup()
{
//Report peripherals only when the link method is not serial
#if !defined(LINK_SERIAL) && LOGLEVEL > 0
	Serial.begin(BAUDRATE);
#ifdef ARDUINO_ARCH_SAMD
	while (!Serial);
#endif /* ARDUINO_ARCH_SAMD */
	Serial.println("Peripherals:");

#ifdef HAVE_OLEDSHIELD
	Serial.println("Have OLED");
#endif /* HAVE_OLEDSHIELD */
#ifdef HAVE_LEDMATRIX
	Serial.println("Have LEDMatrix");
	mled.clear();
	mled.display();
#endif /* HAVE_LEDMATRIX */
#ifdef HAVE_DHT
	Serial.println("Have DHT");
#endif /* HAVE_DHT */
#ifdef HAVE_AIRQUALITYSENSOR
	Serial.println("Have AQS");
#endif /* HAVE_AIRQUALITYSENSOR */
#ifdef HAVE_LIGHTSENSOR
	Serial.println("Have Lightsensor");
#endif /* HAVE_LIGHTSENSOR */
#endif /* !defined(LINK_SERIAL) && LOGLEVEL > 0 */

#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) \
	|| defined(ARDUINO_ARCH_SAMD)
#ifdef HAVE_OLEDSHIELD
#ifdef ARDUINO_ESP32_DEV //Lilygo t-wristband
	display.init();
	display.fillScreen(TFT_BLACK);
#else /* ARDUINO_ESP32_DEV */
	if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C))
		die(SC("SSD1306 allocation failed"));
	display.display();
	delay(500);
	display.clearDisplay();
	display.setTextSize(1);
	display.setTextColor(WHITE);
#endif /* ARDUINO_ESP32_DEV */
	display.setCursor(0, 0);
#endif /* HAVE_OLEDSHIELD */

#ifdef MTASK_USE_WIRE
	if (!wire_began) {
		Wire.begin();
		wire_began = true;
	}
#endif /* MTASK_USE_WIRE */

#endif /* defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
	|| defined(ARDUINO_ARCH_SAMD) */
	start_communication(get_comm_settings(), get_link_settings());

#ifdef LED_BUILTIN
	pinMode(LED_BUILTIN, OUTPUT);
	digitalWrite(LED_BUILTIN, LOW);
#endif /* LED_BUILTIN */
}

void real_yield(void)
{
#ifdef ARDUINO_ARCH_ESP8266
	yield();
#endif /* ARDUINO_ARCH_ESP8266 */
	communication_yield();
}

unsigned int get_random()
{
#ifdef ARDUINO_ARCH_ESP8266
	return RANDOM_REG32;
#else /* ARDUINO_ARCH_ESP8266 */
	return random(INT_MAX);
#endif /* ARDUINO_ARCH_ESP8266 */
}

#if LOGLEVEL > 0
void msg_log(const char *fmt, ...)
{
	char buf[128];
	va_list args;
	va_start(args, fmt);
#ifdef ARDUINO_ARCH_SAMD
	vsnprintf(buf,128,fmt,args);
#else
	vsnprintf_P(buf,128,fmt,args);
#endif
	va_end(args);
	Serial.write(buf);
	Serial.write('\r');
}
#endif /* LOGLEVEL > 0 */

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

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

void reset(void)
{
#ifdef HAVE_LEDMATRIX
	mled.clear();
	mled.display();
#endif /* HAVE_LEDMATRIX */
	stop_communication(false);
#if defined(ARDUINO_ARCH_ESP8266)
	ESP.restart();
#elif defined(ARDUINO_ARCH_AVR)
	asm volatile ("  jmp 0");
#else /* ARDUINO_ARCH_ESP8266 */
	mem_reset();
	real_setup();
#endif /* ARDUINO_ARCH_ESP8266 */
}

void loop()
{
	real_loop();
}

void setup()
{
	real_main();
}
#endif /* ARDUINO */
