#include "RotaryEncoder.h"
#include "SettingsManager.h"

const byte SettingsManager::NUM_VARIABLES = sizeof(variables) / sizeof(variables[0]);
const byte SettingsManager::NUM_SETUP_VARIABLES = SettingsManager::NUM_VARIABLES - 1;

// Constructor
SettingsManager::SettingsManager(RotaryEncoder *encoder, LCDHelper *lcdHelper, byte buttonPin) :
		encoder(encoder), lcdHelper(lcdHelper), buttonPin(buttonPin), mode(SETUP_OFF), currentVariableIndex(0), lastButtonState(HIGH), buttonPressed(false), lastInteractionTime(
				0), lastEncodePos(0), lastButtonDebounceTime(0), buttonDebounceDelay(DEFAULT_DEBOUNCE_DELAY), currentVariableEditValue(NAN) {
	pinMode(buttonPin, INPUT_PULLUP);
	onVariableChangedCallback = nullptr;
}

void SettingsManager::init() {
	loadFromEEPROM();
	//store curr pos on init so we skip first false detection
	lastEncodePos = encoder->getPosition();
}

// Load variables from EEPROM
void SettingsManager::loadFromEEPROM() {
	byte bMagicNum = EEPROM.read(EEPROM_MAGIC_NUM_ADDRESS);
	if (bMagicNum != MAGIC_NUMBER) {
		//DEBUG_PRINTLN_F(F("Magic number is incorrect! -> Corruption / first run? Saving default values to EEPROM!"));
		// Memory was never written or is corrupted, initialize with default values
		EEPROM.update(EEPROM_MAGIC_NUM_ADDRESS, MAGIC_NUMBER);

		// Save default values for all variables
		for (byte i = 0; i < NUM_VARIABLES; i++) {
			saveVariableToEEPROM(i, variables[i]);
		}
	}

	DEBUG_PRINTLN_F(F("Loading stored values from EEPROM..."));
	// Load all variables from EEPROM
	for (byte i = 0; i < NUM_VARIABLES; i++) {
		variables[i] = readVariableFromEEPROM(i);
	}
}

// Save a byte to EEPROM
void SettingsManager::saveVariableToEEPROM(byte index, byte value) {
	EEPROM.update(EEPROM_BASE_ADDRESS + index, value);
	DEBUG_PRINTLN_F(F("Storing variable [%d] value: %d to EEPROM."), index, value);

	//call the callback function
	if (onVariableChangedCallback) {
		onVariableChangedCallback(index, value);
	}
}

// Read a byte from EEPROM
byte SettingsManager::readVariableFromEEPROM(byte index) {
	return EEPROM.read(EEPROM_BASE_ADDRESS + index);
}

// Handle encoder input and button press
void SettingsManager::handleEncoderInput() {
	checkButton();
	encoder->tick(); // Update encoder state
	unsigned long currentTime = millis();

	if (mode == SETUP_OFF) {
		if (buttonPressed) {
			enterVariableDisplayMode();
		}
	} else if (mode == VARIABLE_DISPLAY) {
		if ((currentTime - lastInteractionTime) > INACTIVITY_TIMEOUT) {
			exitToSetupOff();
		}

		//check encoder rotating and which way...
		long currEncoderPos = encoder->getPosition();
		RotaryEncoder::Direction encoderDirection = encoder->getDirection();

		if (lastEncodePos != currEncoderPos) {
			//DEBUG_PRINT_F(F("VARIABLE_DISPLAY --> lastEncoderPos = %d"), lastEncodePos); DEBUG_PRINT_F(F(", currEncoderPos = %d"), currEncoderPos); DEBUG_PRINT_F(F(", direction = %d"), (int )encoderDirection);

			if (encoderDirection == RotaryEncoder::Direction::CLOCKWISE) {
				currentVariableIndex++;
				if (currentVariableIndex >= NUM_SETUP_VARIABLES) {
					currentVariableIndex = 0;
				}
			} else if (encoderDirection == RotaryEncoder::Direction::COUNTERCLOCKWISE) {
				if (currentVariableIndex > 0) {
					currentVariableIndex--;
				} else if (currentVariableIndex == 0) {
					currentVariableIndex = NUM_SETUP_VARIABLES - 1;
				}
			}
			lastEncodePos = currEncoderPos;

			DEBUG_PRINTLN_F(F(" currentVariableIndex = %d"), currentVariableIndex);

			if (encoderDirection != RotaryEncoder::Direction::NOROTATION) {
				displayCurrentVariable();
				lastInteractionTime = currentTime;
			}

		}

		if (buttonPressed) {
			enterVariableSetupMode();
		}
	} else if (mode == VARIABLE_SETUP) {
		if ((currentTime - lastInteractionTime) > INACTIVITY_TIMEOUT) {
			////go back to variable display mode
			enterVariableDisplayMode();
		}

		updateVariableValue();

		if (buttonPressed) {
			variables[currentVariableIndex] = currentVariableEditValue;
			//saveVariableToEEPROM(currentVariableIndex, variables[currentVariableIndex]);
			saveVariableToEEPROM(currentVariableIndex, currentVariableEditValue);
			//go back to variable display mode
			enterVariableDisplayMode();
		}
	}
}

// Enter VARIABLE_DISPLAY mode
void SettingsManager::enterVariableDisplayMode() {
	mode = VARIABLE_DISPLAY;
	displayCurrentVariable();
	lastInteractionTime = millis();
	currentVariableEditValue = NAN;
	//DEBUG_PRINTLN_F(F("Entering variable display mode!"));
}

// Enter VARIABLE_SETUP mode
void SettingsManager::enterVariableSetupMode() {
	mode = VARIABLE_SETUP;
	lastInteractionTime = millis();
	currentVariableEditValue = variables[currentVariableIndex];
	//DEBUG_PRINTLN_F(F("Entering variable setup mode!"));
}

// Update the value of the current variable
void SettingsManager::updateVariableValue() {
	//byte *variable = &variables[currentVariableIndex];
	//if we did not setup edited value...
	if (isnan(currentVariableEditValue)) {
		return;
	}
	const char *strUnit = nullptr;

	//Modify the variable value
	//check encoder rotating and which way...
	long currEncoderPos = encoder->getPosition();
	RotaryEncoder::Direction encoderDirection = encoder->getDirection();

	if (lastEncodePos != currEncoderPos) {
		//DEBUG_PRINT_F(F("updateVariableValue --> lastEncoderPos = %d"), lastEncodePos);
		//DEBUG_PRINT_F(F(", currEncoderPos = %d"), currEncoderPos);
		//DEBUG_PRINT_F(F(", direction = %d"), (int)encoderDirection);

		lastEncodePos = currEncoderPos;

		//some of the values are just 2 state, these cannot be simply incremented / decremented, will treat them separately
		if (currentVariableIndex != VAR_TEMP_CONTROL_MODE && currentVariableIndex != VAR_LED_ON && currentVariableIndex != VAR_PWR_RELAY_OVERRIDE
				&& currentVariableIndex != VAR_ENABLE_WDT) {
			if (encoderDirection == RotaryEncoder::Direction::CLOCKWISE) {
				currentVariableEditValue++;
			} else if (encoderDirection == RotaryEncoder::Direction::COUNTERCLOCKWISE) {
				currentVariableEditValue--;
			}
		}

		if (encoderDirection != RotaryEncoder::Direction::NOROTATION) {
			lastInteractionTime = millis();
		}

		//DEBUG_PRINTLN_F(F(" newValue = %d"), currentVariableEditValue);

		switch (currentVariableIndex) {
		case VAR_TEMP_CONTROL_MODE: // vent mode
			//we just need to switch to the other value no matter which direction the encoder turns
			if (encoderDirection != RotaryEncoder::Direction::NOROTATION) {
				currentVariableEditValue = (currentVariableEditValue == AUTO_MODE) ? MANUAL_MODE : AUTO_MODE;
			}

			if (currentVariableEditValue == AUTO_MODE) {
				lcdHelper->printFormattedTextToLCD(1, STR_FORMAT_DOUBLE_STR, SM_STR_EDIT, STR_AUTO);
			} else {
				lcdHelper->printFormattedTextToLCD(1, STR_FORMAT_DOUBLE_STR, SM_STR_EDIT, STR_MANUAL);
			}
			break;
		case VAR_ALERT_PPM:
			currentVariableEditValue = constrainValue(currentVariableEditValue, MIN_DETECT_PPM / MULTIPLIER_100, variables[VAR_SHUTDOWN_PPM] - 1);
			lcdHelper->displayWordValue(SM_STR_EDIT, currentVariableEditValue * MULTIPLIER_100, STR_UNIT_PPM, 1);

			break;
		case VAR_SHUTDOWN_PPM:
			currentVariableEditValue = constrainValue(currentVariableEditValue, (variables[VAR_ALERT_PPM] + 1), BYTE_MAX_VALUE);
			lcdHelper->displayWordValue(SM_STR_EDIT, currentVariableEditValue * MULTIPLIER_100, STR_UNIT_PPM, 1);
			break;

		case VAR_FAN_DOOR_ANGLE: // fan door angle
			currentVariableEditValue = constrainAngleValue(currentVariableEditValue);
			if (currentVariableEditValue == FAN_DOOR_ANGLE_AUTO) {
				lcdHelper->printFormattedTextToLCD(1, STR_FORMAT_DOUBLE_STR, SM_STR_EDIT, STR_AUTO);
			} else {
				strUnit = STR_UNIT_DEG;
			}
			break;
		case VAR_FAN_DOOR_SPEED:  //fan door speed
			currentVariableEditValue = constrainValue(currentVariableEditValue, MIN_FAN_DOOR_SPEED, BYTE_MAX_VALUE);
			lcdHelper->displayWordValue(SM_STR_EDIT, currentVariableEditValue * MULTIPLIER_10, STR_UNIT_MSDEG, 1);

			break;
		case VAR_FAN_PWM: // fan pwm, custom range VentilationControl::FAN_IDLE_DUTY - VentilationControl::FAN_MAX_DUTY
			//if we go outside bounds, set value to OFF
			if (currentVariableEditValue == (FAN_OFF_DUTY + 1) && encoderDirection == RotaryEncoder::Direction::CLOCKWISE) {
				currentVariableEditValue = FAN_IDLE_DUTY;
			} else if (currentVariableEditValue == BYTE_MAX_VALUE && encoderDirection == RotaryEncoder::Direction::COUNTERCLOCKWISE) {
				currentVariableEditValue = FAN_MAX_DUTY;
			}

			if (currentVariableEditValue < FAN_IDLE_DUTY || currentVariableEditValue > FAN_MAX_DUTY) {
				currentVariableEditValue = FAN_OFF_DUTY;
				lcdHelper->printFormattedTextToLCD(1, STR_FORMAT_DOUBLE_STR, SM_STR_EDIT, STR_OFF);
			} else {
				currentVariableEditValue = constrainValue(currentVariableEditValue, FAN_IDLE_DUTY, FAN_MAX_DUTY);
				strUnit = STR_UNIT_PERCENT;
			}
			break;

		case VAR_LED_ON: //LED light ON/OFF
			//we just need to switch to the other value no matter which direction the encoder turns
			if (encoderDirection != RotaryEncoder::Direction::NOROTATION) {
				currentVariableEditValue = (currentVariableEditValue == LIGHT_LED_ON) ? LIGHT_LED_OFF : LIGHT_LED_ON;
			}

			if (currentVariableEditValue == LIGHT_LED_ON) {
				lcdHelper->printFormattedTextToLCD(1, STR_FORMAT_DOUBLE_STR, SM_STR_EDIT, STR_ON);
			} else {
				lcdHelper->printFormattedTextToLCD(1, STR_FORMAT_DOUBLE_STR, SM_STR_EDIT, STR_OFF);
			}
			break;

		case VAR_LED_PWM: // led PWM, custom range 0-100
			currentVariableEditValue = constrainValue(currentVariableEditValue, 0, 100);
			strUnit = STR_UNIT_PERCENT;
			break;

		case VAR_FIRE_ALERT_MODE: // fire detected?
			//we just need to switch to the other value no matter which direction the encoder turns
			if (currentVariableEditValue == BYTE_MAX_VALUE) {
				currentVariableEditValue = static_cast<byte>(FireAlertMode::FIRE_HAZARD);
			} else if (currentVariableEditValue > static_cast<byte>(FireAlertMode::FIRE_HAZARD)) {
				currentVariableEditValue = static_cast<byte>(FireAlertMode::NORMAL);
			}

			switch (currentVariableEditValue) {
			case FireAlertMode::FIRE_HAZARD:
				lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_FAM_HAZARD)), 1);
				break;
			case FireAlertMode::FIRE_WARNING:
				lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_FAM_WARNING)), 1);
				break;
			default:
				lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_FAM_NORMAL)), 1);
			}
			break;

		case VAR_GASSENS_WARMUP_TIME:
			currentVariableEditValue = constrainValue(currentVariableEditValue, MIN_GAS_SENSE_WARMUP_TIME, BYTE_MAX_VALUE);
			lcdHelper->displayWordValue(SM_STR_EDIT, currentVariableEditValue * MULTIPLIER_10, STR_UNIT_SECONDS, 1);

			break;
		case VAR_PWR_RELAY_OVERRIDE:
			//we just need to switch to the other value no matter which direction the encoder turns
			if (encoderDirection != RotaryEncoder::Direction::NOROTATION) {
				currentVariableEditValue = (currentVariableEditValue == PWR_RELAY_OVERRIDE_ON) ? PWR_RELAY_OVERRIDE_OFF : PWR_RELAY_OVERRIDE_ON;
			}

			if (currentVariableEditValue == PWR_RELAY_OVERRIDE_ON) {
				lcdHelper->printFormattedTextToLCD(1, STR_FORMAT_DOUBLE_STR, SM_STR_EDIT, STR_YES);
			} else {
				lcdHelper->printFormattedTextToLCD(1, STR_FORMAT_DOUBLE_STR, SM_STR_EDIT, STR_NO);
			}
			break;
		case VAR_ENABLE_WDT:
			//we just need to switch to the other value no matter which direction the encoder turns
			if (encoderDirection != RotaryEncoder::Direction::NOROTATION) {
				currentVariableEditValue = (currentVariableEditValue == WDT_ON) ? WDT_OFF : WDT_ON;
			}

			if (currentVariableEditValue == WDT_ON) {
				lcdHelper->printFormattedTextToLCD(1, STR_FORMAT_DOUBLE_STR, SM_STR_EDIT, STR_ON);
			} else {
				lcdHelper->printFormattedTextToLCD(1, STR_FORMAT_DOUBLE_STR, SM_STR_EDIT, STR_OFF);
			}
			break;
		case VAR_INFO_DISPLAY_SPEED:
			if (currentVariableEditValue == 0) {
				lcdHelper->printTextToLCD(STR_OFF, 1);
			} else {
				strUnit = STR_UNIT_SECONDS;
			}
			break;
		default:
			byte bValue;
			if (currentVariableIndex == VAR_MAX_TEMP_INSIDE) {
				//make sure max inside temperature is no more than fire warning temp
				bValue = min(variables[VAR_WARNING_TEMP] - 1, INSIDE_SENSOR_MAX_TEMP);
				currentVariableEditValue = constrainValue(currentVariableEditValue, MIN_TEMP_INSIDE, bValue);
			} else if (currentVariableIndex == VAR_MAX_TEMP_DIFFERENCE) {
				//make sure max temp difference no more than max inside temp plus is what is left for inside temp sensor is able to read
				bValue = max(INSIDE_SENSOR_MAX_TEMP - variables[VAR_MAX_TEMP_INSIDE] - 1, 1);
				currentVariableEditValue = constrainValue(currentVariableEditValue, MIN_TEMP_DIFF, bValue);
			} else if (currentVariableIndex == VAR_WARNING_TEMP) {
				//warning temp can be:
				//min: VAR_MAX_TEMP_INSIDE + 1
				//max: VAR_SHUTDOWN_TEMP - 1
				bValue = min(variables[VAR_SHUTDOWN_TEMP] - 1, INSIDE_SENSOR_MAX_TEMP);
				currentVariableEditValue = constrainValue(currentVariableEditValue, variables[VAR_MAX_TEMP_INSIDE] + 1, bValue);
			} else if (currentVariableIndex == VAR_SHUTDOWN_TEMP) {
				//shutdown temp can be:
				//min: VAR_WARNING_TEMP + 1
				//max: INSIDE_SENSOR_MAX_TEMP
				bValue = max(variables[VAR_WARNING_TEMP] + 1, 1);
				currentVariableEditValue = constrainValue(currentVariableEditValue, bValue, INSIDE_SENSOR_MAX_TEMP);
			} else {
				currentVariableEditValue = constrainValue(currentVariableEditValue, 1, BYTE_MAX_VALUE);
			}
			strUnit = STR_UNIT_CELSIUS;

			break;
		}

		//DEBUG_PRINTLN_F(F("Changing value of variable: %d to: %d"), currentVariableIndex, currentVariableEditValue);

		//*variable = newValue;
		if (strUnit) {
			lcdHelper->displayByteValue(SM_STR_EDIT, currentVariableEditValue, strUnit, 1);
		}
	}
}

// Exit to SETUP_OFF
void SettingsManager::exitToSetupOff() {
	mode = SETUP_OFF;
	DEBUG_PRINTLN_F(F("Leaving setup mode"));
}

// Display the current variable name and value
void SettingsManager::displayCurrentVariable() {

	switch (currentVariableIndex) {
	case VAR_TEMP_CONTROL_MODE:
		lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_TEMP_CTRL_MODE)), 0);
		lcdHelper->printFormattedTextToLCD(1, "%s", variables[VAR_TEMP_CONTROL_MODE] == AUTO_MODE ? STR_AUTO : STR_MANUAL);
		break;
	case VAR_MAX_TEMP_INSIDE:
		lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_MAX_TEMP_INSIDE)), 0);
		lcdHelper->displayByteValue("", variables[VAR_MAX_TEMP_INSIDE], STR_UNIT_CELSIUS, 1);
		break;
	case VAR_MAX_TEMP_DIFFERENCE:
		lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_MAX_TEMP_DIFF)), 0);
		lcdHelper->displayByteValue("", variables[VAR_MAX_TEMP_DIFFERENCE], STR_UNIT_CELSIUS, 1);
		break;
	case VAR_WARNING_TEMP:
		lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_WARNING_TEMP)), 0);
		lcdHelper->displayByteValue("", variables[VAR_WARNING_TEMP], STR_UNIT_CELSIUS, 1);
		break;
	case VAR_SHUTDOWN_TEMP:
		lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_SHUTDOWN_TEMP)), 0);
		lcdHelper->displayByteValue("", variables[VAR_SHUTDOWN_TEMP], STR_UNIT_CELSIUS, 1);
		break;
	case VAR_ALERT_PPM:
		lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_ALERT_PPM)), 0);
		lcdHelper->displayWordValue("", variables[VAR_ALERT_PPM] * MULTIPLIER_100, STR_UNIT_PPM, 1);
		break;
	case VAR_SHUTDOWN_PPM:
		lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_SHUTDOWN_PPM)), 0);
		lcdHelper->displayWordValue("", variables[VAR_SHUTDOWN_PPM] * MULTIPLIER_100, STR_UNIT_PPM, 1);
		break;
	case VAR_FAN_DOOR_ANGLE:
		lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_FAN_DOOR_ANGLE)), 0);
		if (variables[VAR_FAN_DOOR_ANGLE] == FAN_DOOR_ANGLE_AUTO) {
			lcdHelper->printTextToLCD(STR_AUTO, 1);
		} else {
			lcdHelper->displayByteValue("", variables[VAR_FAN_DOOR_ANGLE], STR_UNIT_DEG, 1);
		}
		break;
	case VAR_FAN_DOOR_SPEED:
		lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_FAN_DOOR_SPEED)), 0);
		lcdHelper->displayWordValue("", variables[VAR_FAN_DOOR_SPEED] * MULTIPLIER_10, STR_UNIT_MSDEG, 1);
		break;
	case VAR_FAN_PWM:
		lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_FAN_PWM_OUT)), 0);
		if (variables[VAR_FAN_PWM] >= FAN_IDLE_DUTY && variables[VAR_FAN_PWM] <= FAN_MAX_DUTY) {
			lcdHelper->displayByteValue("", variables[VAR_FAN_PWM], STR_UNIT_PERCENT, 1);
		} else {
			lcdHelper->printTextToLCD(STR_OFF, 1);
		}
		break;
	case VAR_LED_ON:
		lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_LED_ON)), 0);
		if (variables[VAR_LED_ON] == LIGHT_LED_ON) {
			lcdHelper->printTextToLCD(STR_ON, 1);
		} else {
			lcdHelper->printTextToLCD(STR_OFF, 1);
		}
		break;
	case VAR_LED_PWM:
		lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_LED_PWM_OUT)), 0);
		lcdHelper->displayByteValue("", variables[VAR_LED_PWM], STR_UNIT_PERCENT, 1);
		break;
	case VAR_GASSENS_WARMUP_TIME:
		lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_GAS_SENSE_WARMUP_TIME)), 0);
		lcdHelper->displayWordValue("", (word) variables[VAR_GASSENS_WARMUP_TIME] * MULTIPLIER_10, STR_UNIT_SECONDS, 1);
		break;
	case VAR_PWR_RELAY_OVERRIDE:
		lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_PWR_RELAY_OVERRIDE)), 0);
		if (variables[VAR_PWR_RELAY_OVERRIDE] == PWR_RELAY_OVERRIDE_ON) {
			lcdHelper->printTextToLCD(STR_YES, 1);
		} else {
			lcdHelper->printTextToLCD(STR_NO, 1);
		}
		break;
	case VAR_ENABLE_WDT:
		lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_ENABLE_WDT)), 0);
		if (variables[VAR_ENABLE_WDT] == WDT_ON) {
			lcdHelper->printTextToLCD(STR_ON, 1);
		} else {
			lcdHelper->printTextToLCD(STR_OFF, 1);
		}
		break;
	case VAR_INFO_DISPLAY_SPEED:
		lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_INFO_DISPLAY_SPEED)), 0);
		if (variables[VAR_INFO_DISPLAY_SPEED] == 0) {
			lcdHelper->printTextToLCD(STR_OFF, 1);
		} else {
			lcdHelper->displayByteValue("", variables[VAR_INFO_DISPLAY_SPEED], STR_UNIT_SECONDS, 1);
		}
		break;
	case VAR_FIRE_ALERT_MODE:
		lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_FIRE_ALERT_MODE)), 0);
		byte alarmMode = variables[VAR_FIRE_ALERT_MODE];

		if (alarmMode == FireAlertMode::FIRE_HAZARD) {
			lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_FAM_HAZARD)), 1);
		} else if (alarmMode == FireAlertMode::FIRE_WARNING) {
			lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_FAM_WARNING)), 1);
		} else if (alarmMode == FireAlertMode::NORMAL) {
			lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_FAM_NORMAL)), 1);
		} else {
			lcdHelper->printTextToLCD(_loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_INVALID)), 1);
		}
		break;
		//TODO For some reason any "case" after this one does not work...
	default:
		DEBUG_PRINTLN_F(reinterpret_cast<const __FlashStringHelper*>(SM_STR_INVALID_VAR_INDEX), currentVariableIndex)
		;
		lcdHelper->printFormattedTextToLCD(0, _loadFlashString(reinterpret_cast<const __FlashStringHelper*>(SM_STR_INVALID_VAR_INDEX)), currentVariableIndex);
	}
}

// Check if in SETUP_OFF mode
bool SettingsManager::isSetupOff() const {
	return mode == SETUP_OFF;
}

// Check for button press
void SettingsManager::checkButton() {
	bool buttonState = digitalRead(buttonPin) == LOW; // Button is active LOW

	// Check if the button state changed
	if (buttonState && !lastButtonState) {
		unsigned long currentTime = millis();

		// Only register the button press if enough time has passed since the last press
		if (currentTime - lastButtonDebounceTime > buttonDebounceDelay) {
			buttonPressed = true;
			lastButtonDebounceTime = currentTime; // Update the last press time
			//DEBUG_PRINTLN_F(F("Button was pressed!"));
		} else {
			buttonPressed = false;
		}
	} else {
		buttonPressed = false;
	}

	lastButtonState = buttonState;
}

// Constrain value between min and max
byte SettingsManager::constrainValue(byte value, byte min, byte max) {
	if (value < min)
		return min;
	if (value > max)
		return max;
	return value;
}

// Constrain angle value with special handling for AUTO (255)
/*byte SettingsManager::constrainAngleValue(byte value) {
 //door angle is between 0-90, anything else means AUTO mode
 if (value > 90) {
 return FAN_DOOR_ANGLE_AUTO;   // if maximum, set back to AUTO
 }
 if (value < 0) {
 return FAN_DOOR_ANGLE_AUTO;   // If below 0, set to AUTO
 }
 if (value > 90 && value < FAN_DOOR_ANGLE_AUTO) { //if going below 255, return 90 right away so we dont have to twist the control through 255-90 values
 return 90;
 }
 return value;
 }*/
byte SettingsManager::constrainAngleValue(byte value) {
	if (value > MAX_FAN_DOOR_ANGLE) {
		return FAN_DOOR_ANGLE_AUTO; // Going above 90 switches to AUTO
	}
	if (value == FAN_DOOR_ANGLE_AUTO) {
		return FAN_DOOR_ANGLE_AUTO; // Keep AUTO if untouched
	}

	// If in AUTO mode and decreasing, go to 90
	if (value == (FAN_DOOR_ANGLE_AUTO - 1)) {
		return 90;
	}

	return constrainValue(value, MIN_FAN_DOOR_ANGLE, MAX_FAN_DOOR_ANGLE); // Ensure value is between 0-90
}

byte SettingsManager::getTempControlMode() const {
	return variables[VAR_TEMP_CONTROL_MODE];
}

byte SettingsManager::getMaxInsideTemp() const {
	return variables[VAR_MAX_TEMP_INSIDE];
}

byte SettingsManager::getMaxTempDifference() const {
	return variables[VAR_MAX_TEMP_DIFFERENCE];
}

byte SettingsManager::getWarningTemp() const {
	return variables[VAR_WARNING_TEMP];
}

byte SettingsManager::getShutdownTemp() const {
	return variables[VAR_SHUTDOWN_TEMP];
}

word SettingsManager::getAlertPPM() const {
	return variables[VAR_ALERT_PPM] * MULTIPLIER_100;
}

word SettingsManager::getShutdownPPM() const {
	return variables[VAR_SHUTDOWN_PPM] * MULTIPLIER_100;
}

byte SettingsManager::getFanDoorAngle() const {
	return variables[VAR_FAN_DOOR_ANGLE];
}

word SettingsManager::getFanDoorSpeed() const {
	return variables[VAR_FAN_DOOR_SPEED] * MULTIPLIER_10;
}

byte SettingsManager::getFanPWM() const {
	return variables[VAR_FAN_PWM];
}

byte SettingsManager::getLEDOn() const {
	return variables[VAR_LED_ON];
}

byte SettingsManager::getLEDPWM() const {
	return variables[VAR_LED_PWM];
}

FireAlertMode SettingsManager::getFireAlertMode() const {
	return static_cast<FireAlertMode>(variables[VAR_FIRE_ALERT_MODE]);
}

unsigned long SettingsManager::getGasSensWarmupTime() const {
	unsigned long warmupTime = (unsigned long) variables[VAR_GASSENS_WARMUP_TIME] * MULTIPLIER_10 * MULTIPLIER_1000;
	return warmupTime;
}

byte SettingsManager::getPowerRelayOverride() const {
	return variables[VAR_PWR_RELAY_OVERRIDE];
}

byte SettingsManager::getEnableWDT() const {
	return variables[VAR_ENABLE_WDT];
}

byte SettingsManager::getInfoDisplaySpeed() const {
	return variables[VAR_INFO_DISPLAY_SPEED];
}

void SettingsManager::setFireAlertMode(FireAlertMode alertMode) {
	if (alertMode >= FireAlertMode::NORMAL && alertMode <= FireAlertMode::FIRE_HAZARD) {
		variables[VAR_FIRE_ALERT_MODE] = alertMode;
		//saveVariableToEEPROM(VAR_FIRE_DETECTED, value);
		//will not use saveVariableToEEPROM(VAR_FIRE_DETECTED, value) as it would trigger callback which would then cause endless loop with main code
		EEPROM.update(EEPROM_BASE_ADDRESS + VAR_FIRE_ALERT_MODE, alertMode);
	}
}

void SettingsManager::setLastFanDoorPosition(byte doorPosition) {
	variables[VAR_LAST_FAN_DOOR_POSITION] = doorPosition;
	//save last door position
	saveVariableToEEPROM(VAR_LAST_FAN_DOOR_POSITION, doorPosition);
}

byte SettingsManager::getLastFanDoorPosition() const {
	return variables[VAR_LAST_FAN_DOOR_POSITION];
}

void SettingsManager::onVariableChanged(void (*callback)(byte variable, byte newValue)) {
	onVariableChangedCallback = callback;
}

