#include <Arduino.h>
#include <LiquidCrystal_I2C.h>
#include <RotaryEncoder.h>
#include <Wire.h>
#include <avr/wdt.h>  // For AVR-based boards
#include "common.h"
#include "DHTAsyncSensors.h"
#include "FireWatch.h"
#include "LCD1602Helper.h"
#include "VentilationControl.h"
#include "MQ2GasSensorNonBlocking.h"
#include "SettingsManager.h"
#include "InfoDisplay.h"
#include "UnoPWM.h"
#include "Utils.h"
//#include <util/atomic.h>

// C++ code
//
//I2C initialization
LiquidCrystal_I2C lcd(0x27, 16, 2);
LCD1602Helper lcdHelper(&lcd); //lcd helper has some nice methods to use when doing output to I2C LCD
UnoPWM pwmController(25000); // 25kHz PWM frequency

#define DHT_SENSOR_TYPE_INSIDE DHT_TYPE_22
#define DHT_SENSOR_TYPE_OUTSIDE DHT_TYPE_11

//Definition of PINS
// Rotary Encoder Pins
constexpr byte gasSensAPIN = (byte) A0;        //pin for MQ2 analog output
constexpr byte encoderS1PIN = (byte) A1;
constexpr byte encoderS2PIN = (byte) A2;
constexpr byte fanRPMPIN = (byte) 2;       //pin for reading FAN RPM
constexpr byte gasSensDPIN = (byte) 3;         //pin for MQQ2 digital ouput
constexpr byte insideTempSensPIN = (byte) 4;     //pin for inside DHT22 sensor
constexpr byte ledPWMPIN = (byte) 5; //pin to drive MOSFET by PWM which controls LED stripe
constexpr byte encoderButtonPIN = (byte) 6;      // pin of encoder button
constexpr byte outsideTempSensPIN = (byte) 7;    //pin for outside DHT11 sensor
constexpr byte buzzerPIN = (byte) 8;           //pin for buzzer
constexpr byte fanPWMControlPIN = UnoPWM::PWM_PIN1;    // 25Khz PWM for FAN
constexpr byte statusLEDPIN = (byte) 10;		//pin for status led
constexpr byte fanDoorServoPIN = (byte)11; //pin for fan door servo, pin 11 is on Timer 2
constexpr byte fanPowerRelayPIN = (byte) 12;
constexpr byte powerRelayPIN = (byte) 13;    //relay to switch ON/OFF printer power

// Rotary Encoder
RotaryEncoder encoder(encoderS1PIN, encoderS2PIN, RotaryEncoder::LatchMode::TWO03);
// SettingsManager Instance
SettingsManager settingsManager(&encoder, &lcdHelper, encoderButtonPIN);

//MQ2 Gas Sensor
MQ2GasSensorNonBlocking gasSensor(gasSensAPIN, gasSensDPIN, &lcdHelper);

//DHT temp/humidity sensors
DHTAsyncSensors dhtSensors(insideTempSensPIN, DHT_SENSOR_TYPE_INSIDE, outsideTempSensPIN, DHT_SENSOR_TYPE_OUTSIDE, DEFAULT_MEASUREMENT_INTERVAL,
		DEFAULT_MEASUREMENT_INTERVAL);

//Create FireWatch object
FireWatch fireWatch(&dhtSensors, &settingsManager, &gasSensor, &lcdHelper, powerRelayPIN, buzzerPIN, statusLEDPIN);

//Create ventilation control object
VentilationControl ventControl(fanDoorServoPIN, fanPWMControlPIN, fanPowerRelayPIN, fanRPMPIN, &pwmController, &fireWatch);

InfoDisplay infoDisplay(&ventControl, &fireWatch, &lcdHelper, &encoder, DEFAULT_INFO_PAGE_DISPLAY_TIME);

//whether wdt is enabled or not
bool wdt_enabled = false;

/**
 * Init function
 */
void setup() {
	//debug init, will init Serial.begin if DEBUG is defined
	DEBUG_INIT(9600);

	//init display
	lcd.init();
	lcd.backlight();
	lcd.display();

	//first init settings manager so stored variables are loaded
	settingsManager.init();

	// --- Add here ---
	delay(2000);  // Allow DHT11/DHT22 to stabilize before any reads / anything happens
	// ---------------

    // Strong pull-ups to stabilize signal lines
    pinMode(insideTempSensPIN, INPUT_PULLUP);
    pinMode(outsideTempSensPIN, INPUT_PULLUP);
    digitalWrite(outsideTempSensPIN, HIGH);

	pinMode(ledPWMPIN, OUTPUT);

	//this will configure pins for gas sensor and attach interrupt to pin where gas sensor digital pin is connected
	//will trigger the function when input changes from 1 to 0
	gasSensor.attach();
	gasSensor.onDigitalInputTriggered(onGasDetected);		//set callback
	gasSensor.setSensorWarmupTime(settingsManager.getGasSensWarmupTime());

	//initialize ventilation control
	ventControl.attachServo();
	ventControl.setMaxInsideTemp((float)settingsManager.getMaxInsideTemp());
	ventControl.setMaxTempDifference((float)settingsManager.getMaxTempDifference());

	//setup PWM controller timer, it will setup pin 9 and 10 for 25kHz output
	pwmController.setupTimer();
    //DEBUG_PRINTLN_F(F("tcn1T: %d"), pwmController.getTcnt1Top());
  	//DEBUG_PRINTLN_F(F("tcn2T: %d"), pwmController.getTcnt2Top());
	//configure fan speed reading
	ventControl.attachFanInterrupt();

	//turn LEDs off
	analogWrite(ledPWMPIN, 0);

	//attach fire watch
	fireWatch.attach();

	infoDisplay.init();
	//reset info page display time to stored value
	infoDisplay.setInfoPageDisplayTime((unsigned long)settingsManager.getInfoDisplaySpeed() * MULTIPLIER_1000);

	// Other Settings Manager init
	settingsManager.onVariableChanged(onVariableChanged);

	settingsManager.handleEncoderInput(); // Initial input check to load settings

	//enable power to power relay
	fireWatch.checkStoredFireAlarm();


}

/**
 * Main loop
 */
void loop() {
	//check WATCH DOG TMER
	check_wdt();

	//calibrate gas sensor
	byte gasSensState = gasSensor.calibrateGasSensor();
	//if calibrating right now, skip everything else
	if (gasSensState == MQ2GasSensorNonBlocking::SENSOR_STATE_CALIBRATING) {
		return;
	}

	// Continuously check encoder input
	//encoder.tick(); // Update encoder state, encoder is used by settings manager and info display
	settingsManager.handleEncoderInput();

	//read sensors
	readSensorValues();

	//do ventilation control
	ventControl.controlVentilation();

	//do fire watch protection
	fireWatch.doFireWatch();

	controlLighting();

	//make sure setup has priority so other code does not kick us out of setup
	if (settingsManager.isSetupOff()) {

		if (!fireWatch.getAlertMode() != FireAlertMode::FIRE_HAZARD) {
			//lastly, display info on LCD
			infoDisplay.displayInfoPage();
		}
	}
}


/**
 * Reads values of all sensors
 *
 */
void readSensorValues() {
	//read values from inside temperature/humidity sensor
	dhtSensors.measureInsideEnvironment();

	//read values from outside temperature/humidity sensor
	dhtSensors.measureOutsideEnvironment();

	//measure gas sensor resistance
	if (gasSensor.isSensorCalibrated()) {
		//measure the resistance
		gasSensor.measureSensorResistance();
	}
}

/**
 * This function will be called when gas sensor digital output is triggered
 *
 * Have to be careful here as this is code that can run in different thread than the main loop
 */
void onGasDetected() {
	//just set the variable here and deal with it in the functions that are called from within the loop so that we're safe
	//from all the hassle that thus function will be called from function that is being called from interrupt, thus from "interrupt" and
	//thus may be processed along the main code with unexpected behavior if we call other stuff from there
	//bFireHazardDetected is bool and thus writing value to it is atomic
	fireWatch.setAlertMode(FireAlertMode::FIRE_HAZARD, false);
}

/**
 * This function will be called when some variable is changed using SettingsManager
 */
void onVariableChanged(byte variable, byte newValue) {
	switch (variable) {
	case SettingsManager::VAR_LED_ON:
		//just react to OFF, the other changes will be handled by controlLighting function called from loop
		if (newValue == LIGHT_LED_OFF) {
			//turn LED light off
			analogWrite(ledPWMPIN, LOW);
		}
		break;
	case SettingsManager::VAR_FIRE_ALERT_MODE:
		//If On, it should not be caused by sensors, but by user input
		//switch the value of bFireHazardDetected
		//and let the doFireProtectionCheck() handle the rest
		fireWatch.setAlertMode(static_cast<FireAlertMode>(newValue), (newValue == FireAlertMode::NORMAL) ? false : true);

		//if OFF then we can turn on the power relay
		fireWatch.switchPowerRelayOn();
		break;
	case SettingsManager::VAR_PWR_RELAY_OVERRIDE:
		if (newValue == PWR_RELAY_OVERRIDE_ON) {
			//make sure power is forced on
			fireWatch.switchPowerRelayOn();
		}
		break;
	case SettingsManager::VAR_MAX_TEMP_INSIDE:
		ventControl.setMaxInsideTemp((float)newValue);
		break;
	case SettingsManager::VAR_MAX_TEMP_DIFFERENCE:
		ventControl.setMaxTempDifference((float)newValue);
		break;
	case SettingsManager::VAR_INFO_DISPLAY_SPEED:
		infoDisplay.setInfoPageDisplayTime((unsigned long)newValue * MULTIPLIER_1000);
		break;
	default:
		break;
	}
}

void controlLighting() {
	//if LED should be ON and we are running sufficiently long
	if (settingsManager.getLEDOn() == LIGHT_LED_ON && (millis() >= LED_ON_DELAY)) {
		//write LED PWM output, it comes in percent from settings manager,	need to re-map it to 255
		byte ledPWMValue = settingsManager.getLEDPWM();

		analogWrite(ledPWMPIN, map(ledPWMValue, 0, 100, 0, 255));
	} else {
		//light should be off
		analogWrite(ledPWMPIN, LOW);
	}
}

/**
 * This functions checks if WDT should be enabled and enables it if it is not, disabled if should be disabled and resets each time called when enabled
 * It only has effect after gas sensor is calibrated
 */
void check_wdt() {
	if (gasSensor.isSensorCalibrated()) {
		byte enable_wdt = settingsManager.getEnableWDT();
		//if WDT should be enabled, enable it
		if (enable_wdt == WDT_ON && !wdt_enabled) {
			wdt_enable(WDTO_2S);
			wdt_enabled = true;
		} else if (enable_wdt == WDT_OFF && wdt_enabled) {
			//o if should be disabled?
			wdt_disable();
			wdt_enabled = false;
		}

		//if enabled, reset..
		if (wdt_enabled) {
			wdt_reset();
		}
	}
}







