#include "Arduino.h"
#include "MQ2GasSensor.h"

const float MQ2GasSensor::SMOKE_CURVE[] = { 2.3, 0.53, -0.44 };

// Initialize the static instance pointer
MQ2GasSensor *MQ2GasSensor::instance = nullptr;

MQ2GasSensor::MQ2GasSensor(byte sensorAnalogPIN, byte sensorDigitalPIN) :
		MQ2GasSensor(sensorAnalogPIN, sensorDigitalPIN, nullptr, DEFAULT_GAS_SENSE_WARMUP_TIME, SMOKE_CURVE) {
}

MQ2GasSensor::MQ2GasSensor(byte sensorAnalogPIN, byte sensorDigitalPIN, LCDHelper *lcdHelper) :
		MQ2GasSensor(sensorAnalogPIN, sensorDigitalPIN, lcdHelper, DEFAULT_GAS_SENSE_WARMUP_TIME, SMOKE_CURVE) {
}

MQ2GasSensor::MQ2GasSensor(byte sensorAnalogPIN, byte sensorDigitalPIN, LCDHelper *lcdHelper, unsigned int sensorWarmupTime, const float *smokeCurve) :
		sensorAnalogPIN(sensorAnalogPIN), sensorDigitalPIN(sensorDigitalPIN), lcdHelper(lcdHelper), sensorWarmupTime(sensorWarmupTime), smokeCurve(smokeCurve), loadRes(
				DEFAULT_LOAD_RES), airFactor(DEFAULT_AIR_FACTOR), sensorCalibrated(false), sensorValue(0), baselineResistance(0), currResistance(0), readSamples(
				SAMPLES_COUNT), readInterval(SAMPLE_INTERVAL) {
	instance = this;
	onDigitalInputTriggeredCallback = nullptr;
}

/**
 * This function will attach the object to the inputs given during creation, ie. the analog pin and digital pin as INPUT
 */
void MQ2GasSensor::attach() {
	pinMode(sensorAnalogPIN, INPUT);
	pinMode(sensorDigitalPIN, INPUT_PULLUP);
	attachInterrupt(digitalPinToInterrupt(sensorDigitalPIN), digitalInputTriggered, FALLING);
}

/**
 * This function will be triggered by sensor digital input
 */
void MQ2GasSensor::digitalInputTriggered() {
	if (instance && instance->onDigitalInputTriggeredCallback) {
		instance->onDigitalInputTriggeredCallback();
	}
}

/**
 * Calibrates the sensor in clean air to establish the baseline resistance.
 * Reads multiple sensor values, calculates the average resistance, 
 * and adjusts it using the air factor.
 * 
 * @return The baseline resistance of the sensor in clean air (in kΩ).
 */
bool MQ2GasSensor::calibrateSensor() {
	//TODO: finish getSensorResistance non-blocking implementation
	float val = 0;
	val = getSensorResistance(CALIBRATION_SAMPLES_COUNT, CALIBRATION_SAMPLE_INTERVAL); // Calculate the average resistance over 50 samples with 500 ms intervals
	baselineResistance = val / airFactor;    // Adjust the resistance using the air factor
	return true;
}

/**
 * Calculates the sensor resistance by reading the analog input multiple times.
 * Uses the load resistance and the ADC value to compute resistance.
 * 
 * @param samples Number of samples to take for averaging.
 * @param interval Delay (in milliseconds) between each sample.
 * @return The average resistance value of the sensor (in kΩ).
 *
 * TODO: finish getSensorResistance non-blocking implementation
 */
float MQ2GasSensor::getSensorResistance(byte samples, word interval) {
	float res = 0;

	for (int i = 0; i < samples; i++) {
		int adc_value = analogRead(sensorAnalogPIN); // Read the analog value from the sensor
		res += adc_value > 0 ? ((float) loadRes * (1023 - adc_value) / adc_value) : 0;    // Calculate resistance
		delay(interval); // Wait for the specified interval before the next sample
	}

	res /= samples; // Calculate the average resistance
	return res;
}

bool MQ2GasSensor::isSensorCalibrated() const {
	return sensorCalibrated;
}

word MQ2GasSensor::getSensorValue() {
	//reda the raw value
	sensorValue = analogRead(sensorAnalogPIN);
	return sensorValue;
}

word MQ2GasSensor::getSensorValuePPM() {
	// Calculate the smoke concentration (ppm) using the logarithmic sensor curve
	if (currResistance > 0) {
		return (word) pow(10, (((log(currResistance) - smokeCurve[1]) / smokeCurve[2]) + smokeCurve[0]));
	} else {
		return 0;  // or some other error handling logic
	}
}

float MQ2GasSensor::getSensorResistance() {
	// Measure the sensor resistance over 5 samples with 50 ms intervals
	if (sensorCalibrated) {
		currResistance = getSensorResistance(readSamples, readInterval);
		currResistance /= baselineResistance; // Normalize the resistance with the baseline resistance
		return currResistance;
	} else {
		return NAN;
	}
}

void MQ2GasSensor::calibrateGasSensor() {
	if (!sensorCalibrated && (millis() >= sensorWarmupTime)) {
		if (lcdHelper) {
			lcdHelper->printTextToLCD("Calibrating", 0);
			lcdHelper->printTextToLCD("gas sensor...", 1);
		}

		// Perform sensor calibration to determine baseline resistance
		if (calibrateSensor()) {
			if (lcdHelper) {
				lcdHelper->printTextToLCD("Calibration done", 0);
				lcdHelper->displayFloatValue("Res=", baselineResistance, STR_UNIT_KOHM, 1, 1);

				DEBUG_PRINTLN_F(F("Gas sensor calibrated! Res=%.1f %s"), baselineResistance, STR_UNIT_KOHM);
			}

			sensorCalibrated = true;
		}
	}
}

void MQ2GasSensor::onDigitalInputTriggered(void (*callback)()) {
	onDigitalInputTriggeredCallback = callback;
}

unsigned int MQ2GasSensor::getSensorWarmupTime() const {
	return sensorWarmupTime;
}

MQ2GasSensor::~MQ2GasSensor() {
	instance = nullptr;
}
