/*
 * Firewatch.cpp
 *
 *  Created on: Feb 28, 2025
 *      Author: Murdock
 */

#include "FireWatch.h"

FireWatch::FireWatch(DHTAsyncSensors* dhtSensors, SettingsManager* settingsManager, MQ2GasSensorNonBlocking* gasSensor, LCDHelper* lcdHelper, byte powerRelayPIN, byte buzzerPIN, byte statusLEDPIN) :
	dhtSensors(dhtSensors), settingsManager(settingsManager), gasSensor(gasSensor), lcdHelper(lcdHelper), powerRelayPIN(powerRelayPIN), buzzerPIN(buzzerPIN), statusLEDPIN(statusLEDPIN),
	sirenCurrFrequency(0), sirenUpdateInterval(FIRE_ALARM_SIREN_DELAY), fireAlarmSirenCycleTime(FIRE_ALARM_SIREN_CYCLE_TIME), fireAlarmSirenMinFrequency(FIRE_ALARM_SIREN_MIN_FREQUENCY), fireAlarmSirenMaxFrequency(FIRE_ALARM_SIREN_MAX_FREQUENCY), sirenLastUpdateTime(0), sirenStartTime(0),
	alarmPPM(DEFAULT_ALARM_PPM), warningSirenFrequency(WARNING_SIREN_FREQUENCY), warningSirenBeepDuration(WARNING_SIREN_BEEP_DURATION), warningSirenPauseDuration(WARNING_SIREN_PAUSE_DURATION), isWarningBeeping(false),
	statusLEDLastUpdateTime(0), statusLEDState(false), currentAlertMode(NORMAL), bAlertModeChangedFromSettings(false)
{}

void FireWatch::attach() {
	pinMode(buzzerPIN, OUTPUT);
	pinMode(powerRelayPIN, OUTPUT);
	pinMode(statusLEDPIN, OUTPUT);
}

void FireWatch::doFireWatch() {
	FireAlertMode prevMode = currentAlertMode; // Store previous mode for transition detection

	if (dhtSensors->isInsideValid()) {
		word currPPM = gasSensor->getSensorValuePPM();

		//this is FIRE ALERT NOW
		if (dhtSensors->getInsideTemperature() >= settingsManager->getShutdownTemp() || currPPM >= settingsManager->getShutdownPPM()) {

			//some debugging
			/*#ifdef MK4SC_DEBUG
				// Debug output
				static unsigned long lastDebugTime = 0;

				if (DEBUG_TIME_ELAPSED(1000, lastDebugTime)) {
					char valueStr[10];
					dtostrf(dhtSensors->getInsideTemperature(), 0, 1, valueStr);

					DEBUG_PRINT_F(F("Fire ALARM!!! Inside temp = %s"), valueStr);
					DEBUG_PRINT_F(F(", shutdown temp = %d"), settingsManager->getShutdownTemp());
					DEBUG_PRINT_F(F(", curr PPM = %d"), gasSensor->getSensorValuePPM());
					DEBUG_PRINTLN_F(F(", alarm PPM = %d"), alarmPPM);

					DEBUG_RESET_TIME(lastDebugTime);
				}

			#endif*/

			currentAlertMode = FIRE_HAZARD;
		} //if alert threshold temperature or PPM reached
		else if (dhtSensors->getInsideTemperature() >= settingsManager->getWarningTemp() || currPPM >= settingsManager->getAlertPPM()) {
				//just trigger warning, ventilation control will take required actions
			currentAlertMode = FIRE_WARNING;
		} else {
			//if alert mode was changed from settings, than it is a test and we cannot return to NORMAL mode
			if (!bAlertModeChangedFromSettings) {
				currentAlertMode = NORMAL;
			}
		}
	}

	handleAlert(prevMode, currentAlertMode);

	updateStatusLED();
}

void FireWatch::handleAlert(FireAlertMode prevMode, FireAlertMode currMode) {
	// Detect transitions and trigger/reset alarms accordingly
	if (currMode == FIRE_HAZARD && prevMode != FIRE_HAZARD) {
		triggerFireAlarm(); // Runs only once when entering FIRE_HAZARD
	}
	else if (currMode == NORMAL && prevMode == FIRE_HAZARD) {
		resetFireAlarm(); // Runs only once when leaving FIRE_HAZARD
	}

	// Handle HIGH_TEMP_WARNING → PANEL_ON transition (to reset warning siren)
	if (currMode == NORMAL && prevMode == FIRE_WARNING) {
		sirenLastUpdateTime = 0;
		noTone(buzzerPIN); // Stop warning siren
	}

	// Keep sirens running if necessary
	if (currMode == FIRE_HAZARD) {
		toneFireAlarmSiren();
	}
	else if (currMode == FIRE_WARNING) {
		toneWarningSiren();
	}
}

/**
 * This function performs necessary actions when fire alarm is triggered. That is
 *
 * triggers buzzer
 * switches power relay OFF killing the power to 3D printer
 * stored information about detected fire
 */
void FireWatch::triggerFireAlarm() {
	DEBUG_PRINTLN_F(F("Fire ALARM detected and triggered!!!"));

	//turn on buzzer
	toneFireAlarmSiren();

	//switch power relay off
	switchPowerRelay(false, _loadFlashString(F("Fire alarm !!!!!Power relay OFF!")), 0);
	//store in settings
	settingsManager->setFireAlertMode(FireAlertMode::FIRE_HAZARD);
}

/**
 * This function will reset fire alarm. It will:
 *
 * Set the bFireHazardTriggered and bFireHazardDetected to false
 * turn off buzzer
 * turn ON power relay that provides power to 3D printer
 */
void FireWatch::resetFireAlarm() {
	//reset buzzer
	noTone(buzzerPIN);

	DEBUG_PRINTLN_F(F("Fire ALARM reset!!!"));

	//switch power relay ON
	switchPowerRelay(true, _loadFlashString(F("Fire alarm resetPower relay ON!")), 0);
}

/**
 * This function will switch power relay ON/OFF
 *
 * @param bOn Whether to turn the relay ON or OFF (true for ON)
 * @param lcdMessage Message to display on LCD screen
 * @param waitTime wait time to delay so message can be read on the display
 */
void FireWatch::switchPowerRelay(bool bOn, const char *lcdMessage, word waitTime) {
	bool bMadeSwitch = false;

	if (bOn) {
		if (digitalRead(powerRelayPIN) == LOW) {
			digitalWrite(powerRelayPIN, HIGH);
			bMadeSwitch = true;
		}
	} else {
		//turn OFF is no override set
		if (settingsManager->getPowerRelayOverride() != PWR_RELAY_OVERRIDE_ON) {
			if (digitalRead(powerRelayPIN) == HIGH) {
				digitalWrite(powerRelayPIN, LOW);
				bMadeSwitch = true;
			}
		}
	}

	if (bMadeSwitch) {
		if (lcdMessage) {
			lcdHelper->printTextToLCD(lcdMessage, 0);
		}

		if (waitTime > 0) {
			//wait before displaying anything else
			delay(waitTime);
		}
	}
}

/**
 * This should create something similar to firefighter wail siren
 */
void FireWatch::toneFireAlarmSiren() {
	unsigned long currTime = millis();
	if (currTime - sirenLastUpdateTime >= sirenUpdateInterval) {
		sirenLastUpdateTime = currTime;

		// Compute phase based on current time
		float phase = (currTime % fireAlarmSirenCycleTime) / (float)fireAlarmSirenCycleTime;
		float multiplier = 0.5 * (1.0 - cos(phase * TWO_PI)); // Smooth wail shape
		int frequency = fireAlarmSirenMinFrequency + (fireAlarmSirenMaxFrequency - fireAlarmSirenMinFrequency) * multiplier;

		tone(buzzerPIN, frequency);
	}
}

/**
 * This should generate like beep-beep-beep warning siren
 */
void FireWatch::toneWarningSiren() {
	unsigned long currTime = millis();

	if (isWarningBeeping) {
		// If it's time to stop beeping
		if (currTime - sirenLastUpdateTime >= warningSirenBeepDuration) {
			noTone(buzzerPIN); // Stop the beep
			sirenLastUpdateTime = currTime; // Reset timer
			isWarningBeeping = false; // Switch to pause mode
		}
	} else {
		// If it's time to start beeping again
		if (currTime - sirenLastUpdateTime >= warningSirenPauseDuration) {
			tone(buzzerPIN, warningSirenFrequency); // Start beep
			sirenLastUpdateTime = currTime; // Reset timer
			isWarningBeeping = true; // Switch to beep mode
		}
	}
}

void FireWatch::updateStatusLED() {
	unsigned long currTime = millis();
	word interval = 0;


	if (currentAlertMode == FireAlertMode::FIRE_WARNING || settingsManager->getFireAlertMode() == FireAlertMode::FIRE_WARNING) {
		interval = 1000; // 1 second blink
	} else if (currentAlertMode == FireAlertMode::FIRE_HAZARD || settingsManager->getFireAlertMode() == FireAlertMode::FIRE_HAZARD) {
		interval = 200; // 200 ms blink
	} else if (currentAlertMode == FireAlertMode::NORMAL) {
		digitalWrite(statusLEDPIN, HIGH);
		return; // continuous ON
	}

	// blinking the LED
	if (currTime - statusLEDLastUpdateTime >= interval) {
		statusLEDLastUpdateTime = currTime;
		statusLEDState = !statusLEDState;
		digitalWrite(statusLEDPIN, statusLEDState);
	}
}


void FireWatch::checkStoredFireAlarm() {
	if (settingsManager->getFireAlertMode() != FireAlertMode::FIRE_HAZARD) {
		switchPowerRelay(true, _loadFlashString(F("Switching PWR Relay ON...")), 1000);
	} else {
		switchPowerRelay(false, _loadFlashString(F("Stored fire ALM!Check and reset!")), 1000);
	}
}

bool FireWatch::isPowerRelayOn() {
	int relayOutput = digitalRead(powerRelayPIN);
	return relayOutput == HIGH;
}

void FireWatch::switchPowerRelayOn() {
	if (currentAlertMode != FIRE_HAZARD) {
		switchPowerRelay(true, nullptr, 0);
	}
}

void FireWatch::switchPowerRelayOff() {
	switchPowerRelay(false, nullptr, 0);
}

void FireWatch::setAlertMode(FireAlertMode currMode, bool bChangedFromSettings) {
	//handle alert witch switch from previous (class actually stored mode) to new mode - currMode
	handleAlert(currentAlertMode, currMode);
	currentAlertMode = currMode;		//change current mode for next call of doFireWatch
	bAlertModeChangedFromSettings = bChangedFromSettings;
}
