#include "LCD1602Helper.h"
#include <stdio.h>
#include <string.h>

LCD1602Helper::LCD1602Helper(LiquidCrystal_I2C* lcd) : lcd(lcd) {}

/**
 * Generic function to print text to the LCD.
 * If row == 0, it splits the text across both rows (truncating after 32 characters).
 * If row == 1, it prints the text only on the second row (truncating after 16 characters).
 */
void LCD1602Helper::printTextToLCD(const char* text, byte row) {
	char row1[LCD1602_WIDTH + 1] = {0};
	char row2[LCD1602_WIDTH + 1] = {0};

	strncpy(row1, text, LCD1602_WIDTH);
	sanitizeLCDRow(row1, LCD1602_WIDTH + 1);
	if (strlen(text) > LCD1602_WIDTH) {
		//copying LCD1602_WIDTH, but the buffer is 1 byte longer, so the last byte should always stay 0 as terminating char
		strncpy(row2, text + LCD1602_WIDTH, LCD1602_WIDTH);
		sanitizeLCDRow(row2, LCD1602_WIDTH + 1);
	}

	printRows(row, row1, row2);
}

/**
 * Generic function to print formatted text to the LCD.
 * If row == 0, it splits the text across both rows (truncating after 32 characters).
 * If row == 1, it prints the text only on the second row (truncating after 16 characters).
 */
void LCD1602Helper::printFormattedTextToLCD(byte row, const char* fmt, ...) {
    char formattedText[(LCD1602_WIDTH * 2) + 1]; // Buffer to hold the formatted text, size 33 (32 characters + null-terminator)

    // Start the variable argument list
    va_list args;
    va_start(args, fmt);
    // Format the string with the provided arguments
    vsnprintf(formattedText, sizeof(formattedText), fmt, args);
    // End the variable argument list
    va_end(args);
    //make sure the last char is zero terminating
    formattedText[sizeof(formattedText) - 1] = '\0';

    // Now split the formatted text across rows as needed
    char row1[LCD1602_WIDTH + 1] = {0};
    char row2[LCD1602_WIDTH + 1] = {0};

    strncpy(row1, formattedText, LCD1602_WIDTH);
    sanitizeLCDRow(row1, LCD1602_WIDTH + 1);
    if (strlen(formattedText) > LCD1602_WIDTH) {
        strncpy(row2, formattedText + LCD1602_WIDTH, LCD1602_WIDTH);
        sanitizeLCDRow(row2, LCD1602_WIDTH + 1);
    }

    printRows(row, row1, row2);
}

/**
 * This function will make sure that the last character in the buffer is null terminating character
 *a and will replace all other null characters with space character
 */
static void LCD1602Helper::sanitizeLCDRow(char* row, size_t length) {
    if (row == nullptr || length == 0) return;

    for (size_t i = 0; i < length - 1; i++) {
        if (row[i] == '\0') {
            row[i] = ' ';  // Replace null characters with spaces
        }
    }

    row[length - 1] = '\0';  // Ensure the last character is a null terminator
}

void LCD1602Helper::printRows(byte row, const char* row1, const char* row2) {
	// Check if the line changed and update the LCD accordingly
	if (row == 0) {
		if (strcmp(row1, lastRow1) != 0) {  // Only update if changed
			printRow(row1, 0);
			strncpy(lastRow1, row1, LCD1602_WIDTH);  // Save last text
		}
		if (strcmp(row2, lastRow2) != 0) {  // Only update if changed
			printRow(row2, 1);
			strncpy(lastRow2, row2, LCD1602_WIDTH);
		}
	} else if (row == 1) {
		if (strcmp(row1, lastRow2) != 0) {  // Only update if changed
			printRow(row1, 1);
			strncpy(lastRow2, row1, LCD1602_WIDTH);
		}
	}
}

/**
 * This should be the only function that actually prints to the LCD.
 */
void LCD1602Helper::printRow(const char* text, byte row) {
	lcd->setCursor(0, row);
	lcd->print(text);
}

/**
 * Displays a float value with a title and unit on the LCD.
 */
void LCD1602Helper::displayFloatValue(const char* title, float value, const char* unit) {
    displayFloatValue(title, value, unit, 1, 0);
}

/**
 * Displays a float value with a title, unit, and precision on the LCD.
 */
void LCD1602Helper::displayFloatValue(const char* title, float value, const char* unit, byte precision, byte row) {
    char valueStr[10];  // Buffer for float value
    dtostrf(value, 0, precision, valueStr);  // Convert float to string

    char fullText[LCD1602_WIDTH * 2];  // Buffer for full text
    snprintf(fullText, sizeof(fullText), STR_FORMAT_BYTE_WORD_VALUE, title, valueStr, unit);

    printTextToLCD(fullText, row);
}

/**
 * Displays two float values with titles and units on the LCD.
 */
void LCD1602Helper::display2FloatValues(const char* title1, float value1, const char* unit1, byte precision1,
                                        const char* title2, float value2, const char* unit2, byte precision2, byte row) {
    char valueStr1[10], valueStr2[10];
    dtostrf(value1, 0, precision1, valueStr1);
    dtostrf(value2, 0, precision2, valueStr2);

    char fullText[LCD1602_WIDTH * 2];
    snprintf(fullText, sizeof(fullText), "%s%s,%s%s", title1, valueStr1, title2, valueStr2);

    printTextToLCD(fullText, row);
}

/**
 * Displays a byte value with a title and unit.
 */
void LCD1602Helper::displayByteValue(const char* title, byte value, const char* unit) {
    displayByteValue(title, value, unit, 0);
}

void LCD1602Helper::displayByteValue(const char* title, byte value, const char* unit, byte row) {
    char valueStr[4];  // Buffer for byte value, max value of 255 is 3 char + 1 or null terminator
    snprintf(valueStr, sizeof(valueStr), "%d", value);

    char fullText[LCD1602_WIDTH * 2];
    snprintf(fullText, sizeof(fullText), STR_FORMAT_BYTE_WORD_VALUE, title, valueStr, unit);

    printTextToLCD(fullText, row);
}

void LCD1602Helper::displayWordValue(const char *title, word value, const char *unit) {
	displayWordValue(title, value, unit, 0);
}

void LCD1602Helper::displayWordValue(const char *title, word value, const char *unit, byte row) {
	char valueStr[6];  // Buffer for word value, 65535 is 5 + 1 for null terminator
	snprintf(valueStr, sizeof(valueStr), "%d", value);

	char fullText[LCD1602_WIDTH * 2];
	snprintf(fullText, sizeof(fullText), STR_FORMAT_BYTE_WORD_VALUE, title, valueStr, unit);

	printTextToLCD(fullText, row);
}

/**
 * Clears the LCD display.
 */
void LCD1602Helper::clear() {
    lcd->clear();
}

/**
 * Returns the LCD object.
 */
LiquidCrystal_I2C* LCD1602Helper::getLCD() {
    return lcd;
}

void LCD1602Helper::clearRow(byte row) {
    lcd->setCursor(0, row);
    lcd->print("                ");
}
