#include <Arduino.h>
#include "LiquidCrystal_I2C.h"
#include "WiFiSettings.h"
#include "ArduinoJson.h"
#include <SparkFun_SCD4x_Arduino_Library.h>
#include <HTTPClient.h>
#include <SPIFFS.h>
#include "wifiFix.h"

String weather_lat = "";
String weather_long = "";
String weather_apikey = "";
String weather_final_url = "";

SCD4x scd4x;
LiquidCrystal_I2C LCD(0x27,2,1,0,4,5,6,7,3,POSITIVE);

WiFiClient* wifi = new WiFiClientFixed();
HTTPClient* http = new HTTPClient();

#define PIN_LED_GREEN 25
#define PIN_LED_YELLOW 33
#define PIN_LED_RED 32

int backlightState = HIGH;

volatile bool interruptFired = false;
unsigned long lastInterrupt = 0;
unsigned long lastUpdated = 0;
unsigned long lastWeatherUpdate = 0;

bool weatherUpdateSuccess = false;
int lastWeatherPressure = 0;
int avgAtmosphericPressure = 1013;

unsigned const int updateTimer = 5000;
unsigned const int interruptTimer = 500;

void led(int red, int green, int yellow)
{
	analogWrite(PIN_LED_RED, red);
	analogWrite(PIN_LED_GREEN, green);
	analogWrite(PIN_LED_YELLOW, yellow);
}

void IRAM_ATTR BacklightToggle() {
	if (!interruptFired)
		interruptFired = true;
}

void UpdateWeatherData() {
	if (lastWeatherUpdate != 0 && millis() - lastWeatherUpdate < 60000) {
		return;
	}

	lastWeatherUpdate = millis();

	Serial.print("Attempting to get weather data from ");
	Serial.println(weather_final_url);

	http->begin(*wifi, weather_final_url);
	int httpResponseCode = http->GET();
	if (httpResponseCode == 200) {
		String payload = http->getString();
		http->end();
		Serial.println(payload);
		StaticJsonDocument<1024> doc;
		DeserializationError error = deserializeJson(doc, payload.c_str());

		if (error) {
			weatherUpdateSuccess = false;
			lastWeatherPressure = avgAtmosphericPressure;
		}

		float coord_lon = doc["coord"]["lon"]; // 3.45
		float coord_lat = doc["coord"]["lat"]; // 1.23

		JsonObject weather_0 = doc["weather"][0];
		int weather_0_id = weather_0["id"]; // 804
		const char* weather_0_main = weather_0["main"]; // "Clouds"
		const char* weather_0_description = weather_0["description"]; // "overcast clouds"
		const char* weather_0_icon = weather_0["icon"]; // "04d"

		const char* base = doc["base"]; // "stations"

		JsonObject main = doc["main"];
		float main_temp = main["temp"]; // 301.92
		float main_feels_like = main["feels_like"]; // 306.28
		float main_temp_min = main["temp_min"]; // 301.92
		float main_temp_max = main["temp_max"]; // 301.92
		int main_pressure = main["pressure"]; // 1012
		int main_humidity = main["humidity"]; // 75
		int main_sea_level = main["sea_level"]; // 1012
		int main_grnd_level = main["grnd_level"]; // 1012

		int visibility = doc["visibility"]; // 10000

		JsonObject wind = doc["wind"];
		float wind_speed = wind["speed"]; // 4.71
		int wind_deg = wind["deg"]; // 219
		float wind_gust = wind["gust"]; // 4.52

		int clouds_all = doc["clouds"]["all"]; // 100

		long dt = doc["dt"]; // 1684079803

		long sys_sunrise = doc["sys"]["sunrise"]; // 1684042647
		long sys_sunset = doc["sys"]["sunset"]; // 1684086466

		int timezone = doc["timezone"]; // 0
		int id = doc["id"]; // 0
		const char* name = doc["name"]; // nullptr
		int cod = doc["cod"]; // 200

		lastWeatherPressure = main_pressure;
		weatherUpdateSuccess = true;

		Serial.println(String("Got pressure: ") + lastWeatherPressure);
	}
	else {
		http->end();

		lastWeatherPressure = avgAtmosphericPressure;
		weatherUpdateSuccess = false;
		Serial.println(String("Failed getting pressure (Error code: ") + httpResponseCode + String("), using avg atmospheric pressure: ") + lastWeatherPressure);
	}
}

void UpdateSensorCompensation(){
	if (weatherUpdateSuccess) {
		scd4x.setAmbientPressure(lastWeatherPressure * 100); // It wants the data in Pascal!
	}
}

void setup()
{
	Serial.begin(115200);

	LCD.begin(16, 2);
	LCD.clear();
	LCD.setBacklight(backlightState);
	LCD.print("WiFi init...");

	SPIFFS.begin(true);
	WiFiSettings.hostname = "co2-scd4x-";

	weather_apikey = WiFiSettings.string("OpenWeatherMap API key", "hskjdfghjkdfhgyuw");
	weather_lat = WiFiSettings.string("Location: latitude", "1.23");
	weather_long = WiFiSettings.string("Location: longitude", "3.45");
	weather_final_url = "http://api.openweathermap.org/data/2.5/weather?lat=" + weather_lat + "&lon=" + weather_long + "&appid=" + weather_apikey;

	WiFiSettings.connect();

	scd4x.enableDebugging();

	if (!scd4x.begin(Wire)) {
		Serial.println("Could not find SCD4x? Check wiring");
		LCD.clear();
		LCD.print("SCD4x not found");
		LCD.setCursor(0, 1);
		LCD.print("Check wiring");
		while (true);
	}

	scd4x.stopPeriodicMeasurement();

	LCD.setCursor(0, 0);
	LCD.print("SCD4x SELF TEST ");

	if (!scd4x.performSelfTest()) {
		Serial.println("SCD4x self test failed");
		LCD.setCursor(0, 1);
		LCD.print("FAIL");
		while (true);
	}

	scd4x.setAutomaticSelfCalibrationEnabled(true);

	LCD.setCursor(0, 1);
	LCD.print("PASS");
	delay(1000);

	Serial.println("SCD4x init:");
	char mySerial[16];
	scd4x.getSerialNumber(mySerial);
	Serial.print("Serial no: ");
	Serial.println(mySerial);

	LCD.setCursor(6, 0);
	LCD.print("SERIAL #  ");
	LCD.setCursor(0, 1);
	LCD.print(mySerial);
	delay(1000);

	Serial.print("Altitude: ");
	Serial.println(scd4x.getSensorAltitude());
	Serial.print("Self-cal enabled: ");
	Serial.println(scd4x.getAutomaticSelfCalibrationEnabled() ? "Yes" : "No");
	Serial.print("Temperature Offset: ");
	Serial.println(scd4x.getTemperatureOffset());

	UpdateWeatherData();
	UpdateSensorCompensation();

	scd4x.startPeriodicMeasurement();

	pinMode(23, INPUT_PULLUP);
	attachInterrupt(23, BacklightToggle, FALLING);
	LCD.clear();
}

void loop() {
	if (interruptFired) {
		if (millis() - lastInterrupt > interruptTimer || lastInterrupt == 0) {
			backlightState = !backlightState;
			Serial.printf("Setting backlight to %i \n", backlightState);
			LCD.setBacklight(backlightState);
			lastInterrupt = millis();
		}

		interruptFired = false;
	}

	if (millis() - lastUpdated > updateTimer || lastUpdated == 0) {
		lastUpdated = millis();
	} else {
		return;
	}

	UpdateWeatherData();
	UpdateSensorCompensation();

	int co2 = scd4x.getCO2();
	float temp = scd4x.getTemperature();
	float rh = scd4x.getHumidity();

	LCD.setCursor(0, 0);
	LCD.print(temp, 1);
	LCD.print(" C   ");
	LCD.setCursor(9, 0);
	LCD.print(rh, 1);
	LCD.print("%rH");
	LCD.setCursor(0, 1);
	LCD.print(co2);
	LCD.print("ppm   ");
	LCD.setCursor(9, 1);

	if (weatherUpdateSuccess)
		LCD.print(lastWeatherPressure);
	else
		LCD.print("----");

	LCD.print("hPa");

	Serial.print(co2);
	Serial.print(" ppm, ");
	Serial.print(temp);
	Serial.print(" ºC, ");
	Serial.print(rh);
	Serial.println(" %rH");

	if (co2 < 1000)
		led(0, 1, 0);
	else if (co2 < 1500)
		led(0, 0, 5);
	else
		led(5, 0, 0);
}
